├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── images ├── demo-extract-url.gif └── youtube.png ├── index.js ├── manifest.json ├── package.json ├── shim.js ├── src ├── archive.rs ├── extract.rs ├── lib.rs ├── obsidian.rs ├── request.rs ├── settings.rs ├── shim.rs └── transform │ ├── github.rs │ ├── mod.rs │ ├── oembed.rs │ └── oembed │ └── metadata.rs └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .rts2_cache_cjs 3 | main.* 4 | *.log 5 | /target 6 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.15" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ascii" 16 | version = "0.9.3" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" 19 | 20 | [[package]] 21 | name = "autocfg" 22 | version = "0.1.7" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" 25 | 26 | [[package]] 27 | name = "bit-set" 28 | version = "0.5.2" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" 31 | dependencies = [ 32 | "bit-vec", 33 | ] 34 | 35 | [[package]] 36 | name = "bit-vec" 37 | version = "0.6.3" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 40 | 41 | [[package]] 42 | name = "bitflags" 43 | version = "1.2.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 46 | 47 | [[package]] 48 | name = "bumpalo" 49 | version = "3.6.1" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" 52 | 53 | [[package]] 54 | name = "byteorder" 55 | version = "1.4.3" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 58 | 59 | [[package]] 60 | name = "cesu8" 61 | version = "1.1.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 64 | 65 | [[package]] 66 | name = "cfg-if" 67 | version = "1.0.0" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 70 | 71 | [[package]] 72 | name = "cloudabi" 73 | version = "0.0.3" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 76 | dependencies = [ 77 | "bitflags", 78 | ] 79 | 80 | [[package]] 81 | name = "combine" 82 | version = "3.8.1" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" 85 | dependencies = [ 86 | "ascii", 87 | "byteorder", 88 | "either", 89 | "memchr", 90 | "unreachable", 91 | ] 92 | 93 | [[package]] 94 | name = "convert_case" 95 | version = "0.4.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 98 | 99 | [[package]] 100 | name = "cssparser" 101 | version = "0.27.2" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" 104 | dependencies = [ 105 | "cssparser-macros", 106 | "dtoa-short", 107 | "itoa", 108 | "matches", 109 | "phf 0.8.0", 110 | "proc-macro2 1.0.26", 111 | "quote 1.0.9", 112 | "smallvec", 113 | "syn 1.0.69", 114 | ] 115 | 116 | [[package]] 117 | name = "cssparser-macros" 118 | version = "0.6.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" 121 | dependencies = [ 122 | "quote 1.0.9", 123 | "syn 1.0.69", 124 | ] 125 | 126 | [[package]] 127 | name = "derive_more" 128 | version = "0.99.17" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" 131 | dependencies = [ 132 | "convert_case", 133 | "proc-macro2 1.0.26", 134 | "quote 1.0.9", 135 | "rustc_version", 136 | "syn 1.0.69", 137 | ] 138 | 139 | [[package]] 140 | name = "dtoa" 141 | version = "0.4.8" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" 144 | 145 | [[package]] 146 | name = "dtoa-short" 147 | version = "0.3.3" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" 150 | dependencies = [ 151 | "dtoa", 152 | ] 153 | 154 | [[package]] 155 | name = "ego-tree" 156 | version = "0.6.2" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591" 159 | 160 | [[package]] 161 | name = "either" 162 | version = "1.6.1" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 165 | 166 | [[package]] 167 | name = "error-chain" 168 | version = "0.12.4" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" 171 | dependencies = [ 172 | "version_check", 173 | ] 174 | 175 | [[package]] 176 | name = "fancy-regex" 177 | version = "0.10.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "0678ab2d46fa5195aaf59ad034c083d351377d4af57f3e073c074d0da3e3c766" 180 | dependencies = [ 181 | "bit-set", 182 | "regex", 183 | ] 184 | 185 | [[package]] 186 | name = "frontmatter" 187 | version = "0.4.0" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "8fc799a66fc39c6d56fa3bdc8afc1fd7efa8ebff71f4ff091596faffb96c84fa" 190 | dependencies = [ 191 | "yaml-rust", 192 | ] 193 | 194 | [[package]] 195 | name = "fuchsia-cprng" 196 | version = "0.1.1" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 199 | 200 | [[package]] 201 | name = "futf" 202 | version = "0.1.4" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" 205 | dependencies = [ 206 | "mac", 207 | "new_debug_unreachable", 208 | ] 209 | 210 | [[package]] 211 | name = "futures" 212 | version = "0.3.14" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253" 215 | dependencies = [ 216 | "futures-channel", 217 | "futures-core", 218 | "futures-executor", 219 | "futures-io", 220 | "futures-sink", 221 | "futures-task", 222 | "futures-util", 223 | ] 224 | 225 | [[package]] 226 | name = "futures-channel" 227 | version = "0.3.14" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25" 230 | dependencies = [ 231 | "futures-core", 232 | "futures-sink", 233 | ] 234 | 235 | [[package]] 236 | name = "futures-core" 237 | version = "0.3.14" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815" 240 | 241 | [[package]] 242 | name = "futures-executor" 243 | version = "0.3.14" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d" 246 | dependencies = [ 247 | "futures-core", 248 | "futures-task", 249 | "futures-util", 250 | ] 251 | 252 | [[package]] 253 | name = "futures-io" 254 | version = "0.3.14" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04" 257 | 258 | [[package]] 259 | name = "futures-macro" 260 | version = "0.3.14" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b" 263 | dependencies = [ 264 | "proc-macro-hack", 265 | "proc-macro2 1.0.26", 266 | "quote 1.0.9", 267 | "syn 1.0.69", 268 | ] 269 | 270 | [[package]] 271 | name = "futures-sink" 272 | version = "0.3.14" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23" 275 | 276 | [[package]] 277 | name = "futures-task" 278 | version = "0.3.14" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc" 281 | 282 | [[package]] 283 | name = "futures-util" 284 | version = "0.3.14" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025" 287 | dependencies = [ 288 | "futures-channel", 289 | "futures-core", 290 | "futures-io", 291 | "futures-macro", 292 | "futures-sink", 293 | "futures-task", 294 | "memchr", 295 | "pin-project-lite", 296 | "pin-utils", 297 | "proc-macro-hack", 298 | "proc-macro-nested", 299 | "slab", 300 | ] 301 | 302 | [[package]] 303 | name = "fxhash" 304 | version = "0.2.1" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 307 | dependencies = [ 308 | "byteorder", 309 | ] 310 | 311 | [[package]] 312 | name = "getopts" 313 | version = "0.2.21" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 316 | dependencies = [ 317 | "unicode-width", 318 | ] 319 | 320 | [[package]] 321 | name = "getrandom" 322 | version = "0.1.16" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 325 | dependencies = [ 326 | "cfg-if", 327 | "libc", 328 | "wasi 0.9.0+wasi-snapshot-preview1", 329 | ] 330 | 331 | [[package]] 332 | name = "getrandom" 333 | version = "0.2.6" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" 336 | dependencies = [ 337 | "cfg-if", 338 | "libc", 339 | "wasi 0.10.0+wasi-snapshot-preview1", 340 | ] 341 | 342 | [[package]] 343 | name = "html2md" 344 | version = "0.2.10" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "bc63d976a14675a0be3c02eb5d0d233f5d51cf6cb6e8277521abef883380f190" 347 | dependencies = [ 348 | "html5ever 0.25.1", 349 | "jni", 350 | "lazy_static", 351 | "markup5ever_rcdom", 352 | "percent-encoding 2.1.0", 353 | "regex", 354 | ] 355 | 356 | [[package]] 357 | name = "html5ever" 358 | version = "0.22.5" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "c213fa6a618dc1da552f54f85cba74b05d8e883c92ec4e89067736938084c26e" 361 | dependencies = [ 362 | "log", 363 | "mac", 364 | "markup5ever 0.7.5", 365 | "proc-macro2 0.4.30", 366 | "quote 0.6.13", 367 | "syn 0.15.44", 368 | ] 369 | 370 | [[package]] 371 | name = "html5ever" 372 | version = "0.25.1" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" 375 | dependencies = [ 376 | "log", 377 | "mac", 378 | "markup5ever 0.10.0", 379 | "proc-macro2 1.0.26", 380 | "quote 1.0.9", 381 | "syn 1.0.69", 382 | ] 383 | 384 | [[package]] 385 | name = "html5ever" 386 | version = "0.26.0" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" 389 | dependencies = [ 390 | "log", 391 | "mac", 392 | "markup5ever 0.11.0", 393 | "proc-macro2 1.0.26", 394 | "quote 1.0.9", 395 | "syn 1.0.69", 396 | ] 397 | 398 | [[package]] 399 | name = "idna" 400 | version = "0.1.5" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" 403 | dependencies = [ 404 | "matches", 405 | "unicode-bidi", 406 | "unicode-normalization", 407 | ] 408 | 409 | [[package]] 410 | name = "itoa" 411 | version = "0.4.7" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 414 | 415 | [[package]] 416 | name = "jni" 417 | version = "0.14.0" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "1981310da491a4f0f815238097d0d43d8072732b5ae5f8bd0d8eadf5bf245402" 420 | dependencies = [ 421 | "cesu8", 422 | "combine", 423 | "error-chain", 424 | "jni-sys", 425 | "log", 426 | "walkdir", 427 | ] 428 | 429 | [[package]] 430 | name = "jni-sys" 431 | version = "0.3.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 434 | 435 | [[package]] 436 | name = "js-sys" 437 | version = "0.3.57" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" 440 | dependencies = [ 441 | "wasm-bindgen", 442 | ] 443 | 444 | [[package]] 445 | name = "lazy_static" 446 | version = "1.4.0" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 449 | 450 | [[package]] 451 | name = "libc" 452 | version = "0.2.126" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 455 | 456 | [[package]] 457 | name = "linked-hash-map" 458 | version = "0.5.4" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" 461 | 462 | [[package]] 463 | name = "log" 464 | version = "0.4.14" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 467 | dependencies = [ 468 | "cfg-if", 469 | ] 470 | 471 | [[package]] 472 | name = "mac" 473 | version = "0.1.1" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 476 | 477 | [[package]] 478 | name = "markup5ever" 479 | version = "0.7.5" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "897636f9850c3eef4905a5540683ed53dc9393860f0846cab2c2ddf9939862ff" 482 | dependencies = [ 483 | "phf 0.7.24", 484 | "phf_codegen 0.7.24", 485 | "serde", 486 | "serde_derive", 487 | "serde_json", 488 | "string_cache 0.7.5", 489 | "string_cache_codegen 0.4.4", 490 | "tendril", 491 | ] 492 | 493 | [[package]] 494 | name = "markup5ever" 495 | version = "0.10.0" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "aae38d669396ca9b707bfc3db254bc382ddb94f57cc5c235f34623a669a01dab" 498 | dependencies = [ 499 | "log", 500 | "phf 0.8.0", 501 | "phf_codegen 0.8.0", 502 | "serde", 503 | "serde_derive", 504 | "serde_json", 505 | "string_cache 0.8.1", 506 | "string_cache_codegen 0.5.1", 507 | "tendril", 508 | ] 509 | 510 | [[package]] 511 | name = "markup5ever" 512 | version = "0.11.0" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" 515 | dependencies = [ 516 | "log", 517 | "phf 0.10.1", 518 | "phf_codegen 0.10.0", 519 | "string_cache 0.8.1", 520 | "string_cache_codegen 0.5.1", 521 | "tendril", 522 | ] 523 | 524 | [[package]] 525 | name = "markup5ever_rcdom" 526 | version = "0.1.0" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "f015da43bcd8d4f144559a3423f4591d69b8ce0652c905374da7205df336ae2b" 529 | dependencies = [ 530 | "html5ever 0.25.1", 531 | "markup5ever 0.10.0", 532 | "tendril", 533 | "xml5ever", 534 | ] 535 | 536 | [[package]] 537 | name = "matches" 538 | version = "0.1.8" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 541 | 542 | [[package]] 543 | name = "memchr" 544 | version = "2.3.4" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 547 | 548 | [[package]] 549 | name = "new_debug_unreachable" 550 | version = "1.0.4" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 553 | 554 | [[package]] 555 | name = "nodrop" 556 | version = "0.1.14" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" 559 | 560 | [[package]] 561 | name = "obsidian-rust-plugin" 562 | version = "0.1.0" 563 | dependencies = [ 564 | "fancy-regex", 565 | "frontmatter", 566 | "futures", 567 | "html2md", 568 | "js-sys", 569 | "lazy_static", 570 | "readability", 571 | "scraper", 572 | "serde", 573 | "serde_json", 574 | "thiserror", 575 | "url", 576 | "wasm-bindgen", 577 | "wasm-bindgen-futures", 578 | "web-sys", 579 | "yaml-rust", 580 | ] 581 | 582 | [[package]] 583 | name = "percent-encoding" 584 | version = "1.0.1" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 587 | 588 | [[package]] 589 | name = "percent-encoding" 590 | version = "2.1.0" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 593 | 594 | [[package]] 595 | name = "phf" 596 | version = "0.7.24" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" 599 | dependencies = [ 600 | "phf_shared 0.7.24", 601 | ] 602 | 603 | [[package]] 604 | name = "phf" 605 | version = "0.8.0" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" 608 | dependencies = [ 609 | "phf_macros", 610 | "phf_shared 0.8.0", 611 | "proc-macro-hack", 612 | ] 613 | 614 | [[package]] 615 | name = "phf" 616 | version = "0.10.1" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" 619 | dependencies = [ 620 | "phf_shared 0.10.0", 621 | ] 622 | 623 | [[package]] 624 | name = "phf_codegen" 625 | version = "0.7.24" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" 628 | dependencies = [ 629 | "phf_generator 0.7.24", 630 | "phf_shared 0.7.24", 631 | ] 632 | 633 | [[package]] 634 | name = "phf_codegen" 635 | version = "0.8.0" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" 638 | dependencies = [ 639 | "phf_generator 0.8.0", 640 | "phf_shared 0.8.0", 641 | ] 642 | 643 | [[package]] 644 | name = "phf_codegen" 645 | version = "0.10.0" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" 648 | dependencies = [ 649 | "phf_generator 0.10.0", 650 | "phf_shared 0.10.0", 651 | ] 652 | 653 | [[package]] 654 | name = "phf_generator" 655 | version = "0.7.24" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" 658 | dependencies = [ 659 | "phf_shared 0.7.24", 660 | "rand 0.6.5", 661 | ] 662 | 663 | [[package]] 664 | name = "phf_generator" 665 | version = "0.8.0" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" 668 | dependencies = [ 669 | "phf_shared 0.8.0", 670 | "rand 0.7.3", 671 | ] 672 | 673 | [[package]] 674 | name = "phf_generator" 675 | version = "0.10.0" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 678 | dependencies = [ 679 | "phf_shared 0.10.0", 680 | "rand 0.8.5", 681 | ] 682 | 683 | [[package]] 684 | name = "phf_macros" 685 | version = "0.8.0" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" 688 | dependencies = [ 689 | "phf_generator 0.8.0", 690 | "phf_shared 0.8.0", 691 | "proc-macro-hack", 692 | "proc-macro2 1.0.26", 693 | "quote 1.0.9", 694 | "syn 1.0.69", 695 | ] 696 | 697 | [[package]] 698 | name = "phf_shared" 699 | version = "0.7.24" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" 702 | dependencies = [ 703 | "siphasher 0.2.3", 704 | ] 705 | 706 | [[package]] 707 | name = "phf_shared" 708 | version = "0.8.0" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" 711 | dependencies = [ 712 | "siphasher 0.3.5", 713 | ] 714 | 715 | [[package]] 716 | name = "phf_shared" 717 | version = "0.10.0" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 720 | dependencies = [ 721 | "siphasher 0.3.5", 722 | ] 723 | 724 | [[package]] 725 | name = "pin-project-lite" 726 | version = "0.2.6" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" 729 | 730 | [[package]] 731 | name = "pin-utils" 732 | version = "0.1.0" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 735 | 736 | [[package]] 737 | name = "ppv-lite86" 738 | version = "0.2.10" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 741 | 742 | [[package]] 743 | name = "precomputed-hash" 744 | version = "0.1.1" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 747 | 748 | [[package]] 749 | name = "proc-macro-hack" 750 | version = "0.5.19" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 753 | 754 | [[package]] 755 | name = "proc-macro-nested" 756 | version = "0.1.7" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" 759 | 760 | [[package]] 761 | name = "proc-macro2" 762 | version = "0.4.30" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 765 | dependencies = [ 766 | "unicode-xid 0.1.0", 767 | ] 768 | 769 | [[package]] 770 | name = "proc-macro2" 771 | version = "1.0.26" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" 774 | dependencies = [ 775 | "unicode-xid 0.2.1", 776 | ] 777 | 778 | [[package]] 779 | name = "quote" 780 | version = "0.6.13" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 783 | dependencies = [ 784 | "proc-macro2 0.4.30", 785 | ] 786 | 787 | [[package]] 788 | name = "quote" 789 | version = "1.0.9" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 792 | dependencies = [ 793 | "proc-macro2 1.0.26", 794 | ] 795 | 796 | [[package]] 797 | name = "rand" 798 | version = "0.6.5" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" 801 | dependencies = [ 802 | "autocfg", 803 | "libc", 804 | "rand_chacha 0.1.1", 805 | "rand_core 0.4.2", 806 | "rand_hc 0.1.0", 807 | "rand_isaac", 808 | "rand_jitter", 809 | "rand_os", 810 | "rand_pcg 0.1.2", 811 | "rand_xorshift", 812 | "winapi", 813 | ] 814 | 815 | [[package]] 816 | name = "rand" 817 | version = "0.7.3" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 820 | dependencies = [ 821 | "getrandom 0.1.16", 822 | "libc", 823 | "rand_chacha 0.2.2", 824 | "rand_core 0.5.1", 825 | "rand_hc 0.2.0", 826 | "rand_pcg 0.2.1", 827 | ] 828 | 829 | [[package]] 830 | name = "rand" 831 | version = "0.8.5" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 834 | dependencies = [ 835 | "libc", 836 | "rand_chacha 0.3.1", 837 | "rand_core 0.6.3", 838 | ] 839 | 840 | [[package]] 841 | name = "rand_chacha" 842 | version = "0.1.1" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" 845 | dependencies = [ 846 | "autocfg", 847 | "rand_core 0.3.1", 848 | ] 849 | 850 | [[package]] 851 | name = "rand_chacha" 852 | version = "0.2.2" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 855 | dependencies = [ 856 | "ppv-lite86", 857 | "rand_core 0.5.1", 858 | ] 859 | 860 | [[package]] 861 | name = "rand_chacha" 862 | version = "0.3.1" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 865 | dependencies = [ 866 | "ppv-lite86", 867 | "rand_core 0.6.3", 868 | ] 869 | 870 | [[package]] 871 | name = "rand_core" 872 | version = "0.3.1" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 875 | dependencies = [ 876 | "rand_core 0.4.2", 877 | ] 878 | 879 | [[package]] 880 | name = "rand_core" 881 | version = "0.4.2" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 884 | 885 | [[package]] 886 | name = "rand_core" 887 | version = "0.5.1" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 890 | dependencies = [ 891 | "getrandom 0.1.16", 892 | ] 893 | 894 | [[package]] 895 | name = "rand_core" 896 | version = "0.6.3" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 899 | dependencies = [ 900 | "getrandom 0.2.6", 901 | ] 902 | 903 | [[package]] 904 | name = "rand_hc" 905 | version = "0.1.0" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" 908 | dependencies = [ 909 | "rand_core 0.3.1", 910 | ] 911 | 912 | [[package]] 913 | name = "rand_hc" 914 | version = "0.2.0" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 917 | dependencies = [ 918 | "rand_core 0.5.1", 919 | ] 920 | 921 | [[package]] 922 | name = "rand_isaac" 923 | version = "0.1.1" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" 926 | dependencies = [ 927 | "rand_core 0.3.1", 928 | ] 929 | 930 | [[package]] 931 | name = "rand_jitter" 932 | version = "0.1.4" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" 935 | dependencies = [ 936 | "libc", 937 | "rand_core 0.4.2", 938 | "winapi", 939 | ] 940 | 941 | [[package]] 942 | name = "rand_os" 943 | version = "0.1.3" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 946 | dependencies = [ 947 | "cloudabi", 948 | "fuchsia-cprng", 949 | "libc", 950 | "rand_core 0.4.2", 951 | "rdrand", 952 | "winapi", 953 | ] 954 | 955 | [[package]] 956 | name = "rand_pcg" 957 | version = "0.1.2" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" 960 | dependencies = [ 961 | "autocfg", 962 | "rand_core 0.4.2", 963 | ] 964 | 965 | [[package]] 966 | name = "rand_pcg" 967 | version = "0.2.1" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" 970 | dependencies = [ 971 | "rand_core 0.5.1", 972 | ] 973 | 974 | [[package]] 975 | name = "rand_xorshift" 976 | version = "0.1.1" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" 979 | dependencies = [ 980 | "rand_core 0.3.1", 981 | ] 982 | 983 | [[package]] 984 | name = "rdrand" 985 | version = "0.4.0" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 988 | dependencies = [ 989 | "rand_core 0.3.1", 990 | ] 991 | 992 | [[package]] 993 | name = "readability" 994 | version = "0.1.4" 995 | source = "git+https://github.com/trashhalo/readability.git#7387aefeecf68bbbb239e1749eeff996c33bd1f3" 996 | dependencies = [ 997 | "html5ever 0.22.5", 998 | "lazy_static", 999 | "regex", 1000 | "url", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "regex" 1005 | version = "1.4.5" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" 1008 | dependencies = [ 1009 | "aho-corasick", 1010 | "memchr", 1011 | "regex-syntax", 1012 | ] 1013 | 1014 | [[package]] 1015 | name = "regex-syntax" 1016 | version = "0.6.23" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" 1019 | 1020 | [[package]] 1021 | name = "rustc_version" 1022 | version = "0.4.0" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1025 | dependencies = [ 1026 | "semver", 1027 | ] 1028 | 1029 | [[package]] 1030 | name = "ryu" 1031 | version = "1.0.5" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 1034 | 1035 | [[package]] 1036 | name = "same-file" 1037 | version = "1.0.6" 1038 | source = "registry+https://github.com/rust-lang/crates.io-index" 1039 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1040 | dependencies = [ 1041 | "winapi-util", 1042 | ] 1043 | 1044 | [[package]] 1045 | name = "scraper" 1046 | version = "0.13.0" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "5684396b456f3eb69ceeb34d1b5cb1a2f6acf7ca4452131efa3ba0ee2c2d0a70" 1049 | dependencies = [ 1050 | "cssparser", 1051 | "ego-tree", 1052 | "getopts", 1053 | "html5ever 0.26.0", 1054 | "matches", 1055 | "selectors", 1056 | "smallvec", 1057 | "tendril", 1058 | ] 1059 | 1060 | [[package]] 1061 | name = "selectors" 1062 | version = "0.22.0" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" 1065 | dependencies = [ 1066 | "bitflags", 1067 | "cssparser", 1068 | "derive_more", 1069 | "fxhash", 1070 | "log", 1071 | "matches", 1072 | "phf 0.8.0", 1073 | "phf_codegen 0.8.0", 1074 | "precomputed-hash", 1075 | "servo_arc", 1076 | "smallvec", 1077 | "thin-slice", 1078 | ] 1079 | 1080 | [[package]] 1081 | name = "semver" 1082 | version = "1.0.9" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" 1085 | 1086 | [[package]] 1087 | name = "serde" 1088 | version = "1.0.125" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" 1091 | dependencies = [ 1092 | "serde_derive", 1093 | ] 1094 | 1095 | [[package]] 1096 | name = "serde_derive" 1097 | version = "1.0.125" 1098 | source = "registry+https://github.com/rust-lang/crates.io-index" 1099 | checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" 1100 | dependencies = [ 1101 | "proc-macro2 1.0.26", 1102 | "quote 1.0.9", 1103 | "syn 1.0.69", 1104 | ] 1105 | 1106 | [[package]] 1107 | name = "serde_json" 1108 | version = "1.0.64" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" 1111 | dependencies = [ 1112 | "itoa", 1113 | "ryu", 1114 | "serde", 1115 | ] 1116 | 1117 | [[package]] 1118 | name = "servo_arc" 1119 | version = "0.1.1" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" 1122 | dependencies = [ 1123 | "nodrop", 1124 | "stable_deref_trait", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "siphasher" 1129 | version = "0.2.3" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" 1132 | 1133 | [[package]] 1134 | name = "siphasher" 1135 | version = "0.3.5" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27" 1138 | 1139 | [[package]] 1140 | name = "slab" 1141 | version = "0.4.3" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" 1144 | 1145 | [[package]] 1146 | name = "smallvec" 1147 | version = "1.8.0" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 1150 | 1151 | [[package]] 1152 | name = "stable_deref_trait" 1153 | version = "1.2.0" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1156 | 1157 | [[package]] 1158 | name = "string_cache" 1159 | version = "0.7.5" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "89c058a82f9fd69b1becf8c274f412281038877c553182f1d02eb027045a2d67" 1162 | dependencies = [ 1163 | "lazy_static", 1164 | "new_debug_unreachable", 1165 | "phf_shared 0.7.24", 1166 | "precomputed-hash", 1167 | "serde", 1168 | "string_cache_codegen 0.4.4", 1169 | "string_cache_shared", 1170 | ] 1171 | 1172 | [[package]] 1173 | name = "string_cache" 1174 | version = "0.8.1" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a" 1177 | dependencies = [ 1178 | "lazy_static", 1179 | "new_debug_unreachable", 1180 | "phf_shared 0.8.0", 1181 | "precomputed-hash", 1182 | "serde", 1183 | ] 1184 | 1185 | [[package]] 1186 | name = "string_cache_codegen" 1187 | version = "0.4.4" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "f0f45ed1b65bf9a4bf2f7b7dc59212d1926e9eaf00fa998988e420fd124467c6" 1190 | dependencies = [ 1191 | "phf_generator 0.7.24", 1192 | "phf_shared 0.7.24", 1193 | "proc-macro2 1.0.26", 1194 | "quote 1.0.9", 1195 | "string_cache_shared", 1196 | ] 1197 | 1198 | [[package]] 1199 | name = "string_cache_codegen" 1200 | version = "0.5.1" 1201 | source = "registry+https://github.com/rust-lang/crates.io-index" 1202 | checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" 1203 | dependencies = [ 1204 | "phf_generator 0.8.0", 1205 | "phf_shared 0.8.0", 1206 | "proc-macro2 1.0.26", 1207 | "quote 1.0.9", 1208 | ] 1209 | 1210 | [[package]] 1211 | name = "string_cache_shared" 1212 | version = "0.3.0" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" 1215 | 1216 | [[package]] 1217 | name = "syn" 1218 | version = "0.15.44" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" 1221 | dependencies = [ 1222 | "proc-macro2 0.4.30", 1223 | "quote 0.6.13", 1224 | "unicode-xid 0.1.0", 1225 | ] 1226 | 1227 | [[package]] 1228 | name = "syn" 1229 | version = "1.0.69" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" 1232 | dependencies = [ 1233 | "proc-macro2 1.0.26", 1234 | "quote 1.0.9", 1235 | "unicode-xid 0.2.1", 1236 | ] 1237 | 1238 | [[package]] 1239 | name = "tendril" 1240 | version = "0.4.2" 1241 | source = "registry+https://github.com/rust-lang/crates.io-index" 1242 | checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33" 1243 | dependencies = [ 1244 | "futf", 1245 | "mac", 1246 | "utf-8", 1247 | ] 1248 | 1249 | [[package]] 1250 | name = "thin-slice" 1251 | version = "0.1.1" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" 1254 | 1255 | [[package]] 1256 | name = "thiserror" 1257 | version = "1.0.24" 1258 | source = "registry+https://github.com/rust-lang/crates.io-index" 1259 | checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" 1260 | dependencies = [ 1261 | "thiserror-impl", 1262 | ] 1263 | 1264 | [[package]] 1265 | name = "thiserror-impl" 1266 | version = "1.0.24" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" 1269 | dependencies = [ 1270 | "proc-macro2 1.0.26", 1271 | "quote 1.0.9", 1272 | "syn 1.0.69", 1273 | ] 1274 | 1275 | [[package]] 1276 | name = "time" 1277 | version = "0.1.44" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 1280 | dependencies = [ 1281 | "libc", 1282 | "wasi 0.10.0+wasi-snapshot-preview1", 1283 | "winapi", 1284 | ] 1285 | 1286 | [[package]] 1287 | name = "tinyvec" 1288 | version = "1.2.0" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" 1291 | dependencies = [ 1292 | "tinyvec_macros", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "tinyvec_macros" 1297 | version = "0.1.0" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1300 | 1301 | [[package]] 1302 | name = "unicode-bidi" 1303 | version = "0.3.5" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" 1306 | dependencies = [ 1307 | "matches", 1308 | ] 1309 | 1310 | [[package]] 1311 | name = "unicode-normalization" 1312 | version = "0.1.17" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" 1315 | dependencies = [ 1316 | "tinyvec", 1317 | ] 1318 | 1319 | [[package]] 1320 | name = "unicode-width" 1321 | version = "0.1.9" 1322 | source = "registry+https://github.com/rust-lang/crates.io-index" 1323 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 1324 | 1325 | [[package]] 1326 | name = "unicode-xid" 1327 | version = "0.1.0" 1328 | source = "registry+https://github.com/rust-lang/crates.io-index" 1329 | checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 1330 | 1331 | [[package]] 1332 | name = "unicode-xid" 1333 | version = "0.2.1" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 1336 | 1337 | [[package]] 1338 | name = "unreachable" 1339 | version = "1.0.0" 1340 | source = "registry+https://github.com/rust-lang/crates.io-index" 1341 | checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" 1342 | dependencies = [ 1343 | "void", 1344 | ] 1345 | 1346 | [[package]] 1347 | name = "url" 1348 | version = "1.7.2" 1349 | source = "registry+https://github.com/rust-lang/crates.io-index" 1350 | checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" 1351 | dependencies = [ 1352 | "idna", 1353 | "matches", 1354 | "percent-encoding 1.0.1", 1355 | ] 1356 | 1357 | [[package]] 1358 | name = "utf-8" 1359 | version = "0.7.5" 1360 | source = "registry+https://github.com/rust-lang/crates.io-index" 1361 | checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" 1362 | 1363 | [[package]] 1364 | name = "version_check" 1365 | version = "0.9.3" 1366 | source = "registry+https://github.com/rust-lang/crates.io-index" 1367 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 1368 | 1369 | [[package]] 1370 | name = "void" 1371 | version = "1.0.2" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 1374 | 1375 | [[package]] 1376 | name = "walkdir" 1377 | version = "2.3.2" 1378 | source = "registry+https://github.com/rust-lang/crates.io-index" 1379 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" 1380 | dependencies = [ 1381 | "same-file", 1382 | "winapi", 1383 | "winapi-util", 1384 | ] 1385 | 1386 | [[package]] 1387 | name = "wasi" 1388 | version = "0.9.0+wasi-snapshot-preview1" 1389 | source = "registry+https://github.com/rust-lang/crates.io-index" 1390 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1391 | 1392 | [[package]] 1393 | name = "wasi" 1394 | version = "0.10.0+wasi-snapshot-preview1" 1395 | source = "registry+https://github.com/rust-lang/crates.io-index" 1396 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1397 | 1398 | [[package]] 1399 | name = "wasm-bindgen" 1400 | version = "0.2.80" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" 1403 | dependencies = [ 1404 | "cfg-if", 1405 | "serde", 1406 | "serde_json", 1407 | "wasm-bindgen-macro", 1408 | ] 1409 | 1410 | [[package]] 1411 | name = "wasm-bindgen-backend" 1412 | version = "0.2.80" 1413 | source = "registry+https://github.com/rust-lang/crates.io-index" 1414 | checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" 1415 | dependencies = [ 1416 | "bumpalo", 1417 | "lazy_static", 1418 | "log", 1419 | "proc-macro2 1.0.26", 1420 | "quote 1.0.9", 1421 | "syn 1.0.69", 1422 | "wasm-bindgen-shared", 1423 | ] 1424 | 1425 | [[package]] 1426 | name = "wasm-bindgen-futures" 1427 | version = "0.4.23" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "81b8b767af23de6ac18bf2168b690bed2902743ddf0fb39252e36f9e2bfc63ea" 1430 | dependencies = [ 1431 | "cfg-if", 1432 | "js-sys", 1433 | "wasm-bindgen", 1434 | "web-sys", 1435 | ] 1436 | 1437 | [[package]] 1438 | name = "wasm-bindgen-macro" 1439 | version = "0.2.80" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" 1442 | dependencies = [ 1443 | "quote 1.0.9", 1444 | "wasm-bindgen-macro-support", 1445 | ] 1446 | 1447 | [[package]] 1448 | name = "wasm-bindgen-macro-support" 1449 | version = "0.2.80" 1450 | source = "registry+https://github.com/rust-lang/crates.io-index" 1451 | checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" 1452 | dependencies = [ 1453 | "proc-macro2 1.0.26", 1454 | "quote 1.0.9", 1455 | "syn 1.0.69", 1456 | "wasm-bindgen-backend", 1457 | "wasm-bindgen-shared", 1458 | ] 1459 | 1460 | [[package]] 1461 | name = "wasm-bindgen-shared" 1462 | version = "0.2.80" 1463 | source = "registry+https://github.com/rust-lang/crates.io-index" 1464 | checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" 1465 | 1466 | [[package]] 1467 | name = "web-sys" 1468 | version = "0.3.57" 1469 | source = "registry+https://github.com/rust-lang/crates.io-index" 1470 | checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" 1471 | dependencies = [ 1472 | "js-sys", 1473 | "wasm-bindgen", 1474 | ] 1475 | 1476 | [[package]] 1477 | name = "winapi" 1478 | version = "0.3.9" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1481 | dependencies = [ 1482 | "winapi-i686-pc-windows-gnu", 1483 | "winapi-x86_64-pc-windows-gnu", 1484 | ] 1485 | 1486 | [[package]] 1487 | name = "winapi-i686-pc-windows-gnu" 1488 | version = "0.4.0" 1489 | source = "registry+https://github.com/rust-lang/crates.io-index" 1490 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1491 | 1492 | [[package]] 1493 | name = "winapi-util" 1494 | version = "0.1.5" 1495 | source = "registry+https://github.com/rust-lang/crates.io-index" 1496 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1497 | dependencies = [ 1498 | "winapi", 1499 | ] 1500 | 1501 | [[package]] 1502 | name = "winapi-x86_64-pc-windows-gnu" 1503 | version = "0.4.0" 1504 | source = "registry+https://github.com/rust-lang/crates.io-index" 1505 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1506 | 1507 | [[package]] 1508 | name = "xml5ever" 1509 | version = "0.16.1" 1510 | source = "registry+https://github.com/rust-lang/crates.io-index" 1511 | checksum = "0b1b52e6e8614d4a58b8e70cf51ec0cc21b256ad8206708bcff8139b5bbd6a59" 1512 | dependencies = [ 1513 | "log", 1514 | "mac", 1515 | "markup5ever 0.10.0", 1516 | "time", 1517 | ] 1518 | 1519 | [[package]] 1520 | name = "yaml-rust" 1521 | version = "0.4.5" 1522 | source = "registry+https://github.com/rust-lang/crates.io-index" 1523 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 1524 | dependencies = [ 1525 | "linked-hash-map", 1526 | ] 1527 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "obsidian-rust-plugin" 3 | version = "0.1.0" 4 | authors = ["Stephen Solka "] 5 | edition = "2018" 6 | 7 | [build] 8 | rustflags = ["--cfg=web_sys_unstable_apis"] 9 | 10 | [target.wasm32-unknown-unknown] 11 | rustflags = ["--cfg=web_sys_unstable_apis"] 12 | 13 | [lib] 14 | crate-type = ["cdylib"] 15 | 16 | [profile.release] 17 | lto = true 18 | opt-level = 'z' 19 | 20 | [dependencies] 21 | wasm-bindgen = { version = "^0.2", features = ["serde-serialize"] } 22 | wasm-bindgen-futures = "0.4.22" 23 | js-sys = "0.3.49" 24 | url = "1" 25 | html2md = "0.2.10" 26 | thiserror = "1.0.24" 27 | frontmatter = "^0.4" 28 | yaml-rust = "^0.4" 29 | serde = { version = "^1.0", features = ["derive"] } 30 | serde_json = "^1.0" 31 | futures = "^0.3.14" 32 | fancy-regex = "0.10.0" 33 | lazy_static = "1.4.0" 34 | scraper = "0.13.0" 35 | 36 | [dependencies.readability] 37 | git = "https://github.com/trashhalo/readability.git" 38 | version = "^0" 39 | default-features = false 40 | 41 | [dependencies.web-sys] 42 | version = "0.3.57" 43 | features = [ 44 | 'Window', 45 | 'console', 46 | 'Navigator', 47 | 'Clipboard' 48 | ] 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obsidian Plugin: Convert a URL into markdown 2 | 3 | ![Demo](images/demo-extract-url.gif) 4 | 5 | Transforms a URL to markdown view if the website allows it. 6 | 7 | # Installation 8 | 9 | Available in the community plugin store in options. 10 | 11 | # Modes 12 | 13 | Operates in 2 modes. 14 | 15 | 1. **Selection** - If you select a URL in the document and execute these commands it will replace the selection with the markdown content. 16 | 2. **Document** - If you add front mater with the key of `link` to your document then it is treated as a linked document. Then calling extract will look for the link and replace the content of the document with the extracted content. 17 | 3. **Archive** - Extract every `[foo](https://url.com)` url found in the doucment. Replace external links to internal ones. Files created in `archive` folder. 18 | 19 | ## Document mode example 20 | 21 | ```markdown 22 | --- 23 | link: "https://bart.degoe.de/building-a-full-text-search-engine-150-lines-of-code/" 24 | --- 25 | 26 | everything below the --- will be replaced when calling extract 27 | ``` 28 | 29 | # Commands 30 | 31 | - **Extract**: Replace url or document with readable markdown extracted from the sites html content 32 | - **Title Only**: Replace url or document with a markdown anchor with the title extracted from the page content 33 | - **Import from Clipboard**: Extract content from url that is found in your clipboard and dump it at your cursor. 34 | 35 | # Youtube 36 | 37 | If your system has `youtube-dl` installed extra details like channel name and description will be extracted for youtube urls. 38 | 39 | ![youtube](images/youtube.png) 40 | -------------------------------------------------------------------------------- /images/demo-extract-url.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trashhalo/obsidian-extract-url/069c8c468594044ddc74c8d21f01bf9aefbf8194/images/demo-extract-url.gif -------------------------------------------------------------------------------- /images/youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trashhalo/obsidian-extract-url/069c8c468594044ddc74c8d21f01bf9aefbf8194/images/youtube.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { Plugin, PluginSettingTab } from "obsidian"; 2 | import rustPlugin from "./pkg/obsidian_rust_plugin_bg.wasm"; 3 | import * as plugin from "./pkg/obsidian_rust_plugin.js"; 4 | 5 | class RustSettingsTab extends PluginSettingTab { 6 | constructor(app, plugin, wasm) { 7 | super(app, plugin); 8 | this.plugin = plugin; 9 | this.wasm = wasm; 10 | } 11 | 12 | display() { 13 | this.wasm.settings(this); 14 | } 15 | } 16 | 17 | export default class RustPlugin extends Plugin { 18 | async onload() { 19 | const buffer = Uint8Array.from(atob(rustPlugin), (c) => c.charCodeAt(0)); 20 | await plugin.default(Promise.resolve(buffer)); 21 | plugin.onload(this); 22 | this.addSettingTab(new RustSettingsTab(this.app, this, plugin)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "extract-url", 3 | "name": "Extract url content", 4 | "version": "0.12.1", 5 | "description": "Extract url converting content into markdown", 6 | "author": "Stephen Solka", 7 | "authorUrl": "https://github.com/trashhalo", 8 | "isDesktopOnly": false, 9 | "minAppVersion": "0.12.15" 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-extract-url", 3 | "version": "0.1.0", 4 | "description": "Extract url converting content into markdown", 5 | "main": "main.js", 6 | "scripts": { 7 | "esbuild-dev": "esbuild index.js --platform=node --external:obsidian --external:electron --loader:.wasm=base64 --bundle --outfile=main.js", 8 | "esbuild-release": "esbuild index.js --platform=node --external:obsidian --external:electron --loader:.wasm=base64 --bundle --outfile=main.js --minify", 9 | "build": "export RUSTFLAGS=--cfg=web_sys_unstable_apis && wasm-pack build --target web && yarn esbuild-release", 10 | "dev": "export RUSTFLAGS=--cfg=web_sys_unstable_apis && wasm-pack build --dev --target web && yarn esbuild-dev" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "GPL", 15 | "devDependencies": { 16 | "esbuild": "^0.12.25" 17 | }, 18 | "dependencies": { 19 | "hasbin": "^1.2.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /shim.js: -------------------------------------------------------------------------------- 1 | const { Platform } = require('obsidian'); 2 | 3 | function hasBin(bin) { 4 | if(Platform.isDesktop) { 5 | const hasBin = require('hasbin'); 6 | return hasBin.sync(bin); 7 | } else { 8 | return false; 9 | } 10 | } 11 | 12 | function nodeExec(bin, cb) { 13 | if(Platform.isDesktop) { 14 | const childProcess = require('child_process'); 15 | return childProcess.exec(bin, cb); 16 | } else { 17 | throw new Error('platform not supported'); 18 | } 19 | } 20 | 21 | function clipboardReadText() { 22 | if(Platform.isDesktop) { 23 | const electron = require('electron'); 24 | return electron.clipboard.readText(); 25 | } else { 26 | throw new Error('platform not supported'); 27 | } 28 | } 29 | 30 | module.exports = { 31 | hasBin, 32 | nodeExec, 33 | clipboardReadText 34 | } -------------------------------------------------------------------------------- /src/archive.rs: -------------------------------------------------------------------------------- 1 | use crate::extract; 2 | use crate::extract::Markdown; 3 | use crate::obsidian; 4 | use crate::settings::Settings; 5 | use fancy_regex::Captures; 6 | use fancy_regex::Regex; 7 | use js_sys::Promise; 8 | use js_sys::{Error, JsString}; 9 | use lazy_static::lazy_static; 10 | use std::collections::HashMap; 11 | use std::collections::HashSet; 12 | use thiserror::Error; 13 | use url::Url; 14 | use wasm_bindgen::prelude::*; 15 | use wasm_bindgen::JsCast; 16 | use wasm_bindgen_futures::{future_to_promise, JsFuture}; 17 | 18 | lazy_static! { 19 | static ref URL_REGEX: Regex = 20 | Regex::new(r"(?.*)\]\((?Phttp.*)\)").unwrap(); 21 | static ref TITLE_REGEX: Regex = Regex::new(r"[^a-zA-Z0-9\-\s]").unwrap(); 22 | } 23 | 24 | #[wasm_bindgen] 25 | pub struct ArchiveCommand { 26 | id: JsString, 27 | name: JsString, 28 | } 29 | 30 | #[wasm_bindgen] 31 | impl ArchiveCommand { 32 | #[wasm_bindgen(getter)] 33 | pub fn id(&self) -> JsString { 34 | self.id.clone() 35 | } 36 | 37 | #[wasm_bindgen(setter)] 38 | pub fn set_id(&mut self, id: &str) { 39 | self.id = JsString::from(id) 40 | } 41 | 42 | #[wasm_bindgen(getter)] 43 | pub fn name(&self) -> JsString { 44 | self.name.clone() 45 | } 46 | 47 | #[wasm_bindgen(setter)] 48 | pub fn set_name(&mut self, name: &str) { 49 | self.name = JsString::from(name) 50 | } 51 | 52 | #[wasm_bindgen(method)] 53 | pub fn callback(&self) -> Promise { 54 | future_to_promise(async move { 55 | let plugin = obsidian::plugin(); 56 | let settings = crate::settings::load_settings(&plugin).await.unwrap(); 57 | let res = archive_document(&plugin, &settings).await; 58 | if let Err(e) = res { 59 | let msg = format!("error: {}", e); 60 | obsidian::Notice::new(&msg); 61 | Err(JsValue::from(Error::new(&msg))) 62 | } else { 63 | Ok(JsValue::undefined()) 64 | } 65 | }) 66 | } 67 | } 68 | #[derive(Error, Debug)] 69 | enum ArchiveError { 70 | #[error("expected to have a file open but none were active")] 71 | NoActiveFile, 72 | 73 | #[error("unexpected error `{0}`")] 74 | JsError(String), 75 | 76 | #[error("unknown error from js")] 77 | UnknownJsError, 78 | 79 | #[error("unknown syntax expected a url")] 80 | UnknownSyntax, 81 | 82 | #[error("error extracting content. {0}")] 83 | Parse(#[from] extract::ExtractError), 84 | } 85 | 86 | pub fn command_archive() -> ArchiveCommand { 87 | ArchiveCommand { 88 | id: JsString::from("archive-url"), 89 | name: JsString::from("Archive"), 90 | } 91 | } 92 | 93 | impl std::convert::From for ArchiveError { 94 | fn from(err: JsValue) -> Self { 95 | let err_val: &Result = &err.dyn_into(); 96 | if let Ok(err_val) = err_val { 97 | ArchiveError::JsError(err_val.to_string().as_string().unwrap()) 98 | } else { 99 | ArchiveError::UnknownJsError 100 | } 101 | } 102 | } 103 | 104 | async fn archive_document( 105 | plugin: &obsidian::Plugin, 106 | settings: &Settings, 107 | ) -> Result<(), ArchiveError> { 108 | let app = plugin.app(); 109 | let workspace = app.workspace(); 110 | let vault = app.vault(); 111 | let file_manager = app.file_manager(); 112 | if let Some(active) = workspace.get_active_file() { 113 | let content_js: JsString = JsFuture::from(vault.read(&active)).await?.dyn_into()?; 114 | let mut content = String::from(content_js); 115 | let mut markdown_map: HashMap = HashMap::new(); 116 | for url in document_to_urls(&content)? { 117 | let msg = format!("archiving: {}", &url); 118 | obsidian::Notice::new(&msg); 119 | let md = url_to_markdown(&url).await?; 120 | let file = persist_markdown(settings, &vault, &url, &md).await?; 121 | markdown_map.insert(url, file); 122 | } 123 | content = URL_REGEX 124 | .replace_all(&content, |caps: &Captures| { 125 | let url = caps.name("url").unwrap(); 126 | let md = markdown_map.get(url.as_str()).unwrap(); 127 | let text = match caps.name("text") { 128 | Some(x) => Some(x.as_str()), 129 | None => Some(md.title.as_str()), 130 | }; 131 | file_manager.generate_markdown_link(&md.file, &settings.archive_path(), None, text) 132 | }) 133 | .into(); 134 | JsFuture::from(vault.modify(&active, &content)).await?; 135 | obsidian::Notice::new(&"archive complete"); 136 | Ok(()) 137 | } else { 138 | Err(ArchiveError::NoActiveFile) 139 | } 140 | } 141 | 142 | fn document_to_urls(doc: &str) -> Result, ArchiveError> { 143 | let mut urls = HashSet::new(); 144 | for item in URL_REGEX.captures_iter(doc) { 145 | let url = item 146 | .unwrap() 147 | .name("url") 148 | .ok_or(ArchiveError::UnknownSyntax)? 149 | .as_str(); 150 | urls.insert(String::from(url)); 151 | } 152 | Ok(urls.into_iter()) 153 | } 154 | 155 | async fn url_to_markdown(url: &str) -> Result { 156 | Ok(extract::convert_url_to_markdown(false, url).await?) 157 | } 158 | 159 | struct MarkdownFile { 160 | title: String, 161 | pub file: obsidian::TFile, 162 | } 163 | 164 | #[wasm_bindgen(inline_js = r#" export function now() { return (+Date.now()).toString(); }"#)] 165 | extern "C" { 166 | fn now() -> String; 167 | } 168 | 169 | fn markdown_to_filename(url: &str, title: &str) -> String { 170 | if let Ok(parsed) = Url::parse(&url) { 171 | if let Some(domain) = parsed.domain() { 172 | let no_dots = domain.replace(".", "_"); 173 | return format!("{}.{}.md", &no_dots, title); 174 | } 175 | } 176 | format!("{}.md", title) 177 | } 178 | 179 | async fn persist_markdown( 180 | settings: &Settings, 181 | vault: &obsidian::Vault, 182 | url: &str, 183 | md: &Markdown, 184 | ) -> Result { 185 | let adapter = vault.adapter(); 186 | let archive_path = settings.archive_path(); 187 | JsFuture::from(adapter.mkdir(archive_path)?).await?; 188 | let title = &TITLE_REGEX.replace_all(&md.title, ""); 189 | let filename = markdown_to_filename(&url, &title); 190 | let path = format!("{}/{}", archive_path, filename); 191 | let tfile: obsidian::TFile; 192 | if let Some(a_file) = vault.get_abstract_file_by_path(&path)? { 193 | let is_file: Result = a_file.dyn_into(); 194 | if let Ok(file) = is_file { 195 | tfile = file; 196 | } else { 197 | tfile = JsFuture::from(vault.create(&path, &md.content)?) 198 | .await? 199 | .dyn_into()?; 200 | } 201 | } else { 202 | tfile = JsFuture::from(vault.create(&path, &md.content)?) 203 | .await? 204 | .dyn_into()?; 205 | } 206 | Ok(MarkdownFile { 207 | title: md.title.to_owned(), 208 | file: tfile, 209 | }) 210 | } 211 | 212 | #[cfg(test)] 213 | mod tests { 214 | use super::*; 215 | 216 | #[test] 217 | fn url_regex() { 218 | assert!(URL_REGEX.is_match("[foo](https://foo.com)").unwrap()); 219 | assert!(!URL_REGEX.is_match("# [foo](https://foo.com)").unwrap()); 220 | assert!(URL_REGEX.is_match("# \n[foo](https://foo.com)").unwrap()); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/extract.rs: -------------------------------------------------------------------------------- 1 | use crate::obsidian; 2 | use crate::request; 3 | use crate::transform; 4 | use js_sys::{Error, JsString, Promise}; 5 | use thiserror::Error; 6 | use url::Url; 7 | use wasm_bindgen::prelude::*; 8 | use wasm_bindgen::JsCast; 9 | use wasm_bindgen_futures::{future_to_promise, JsFuture}; 10 | use web_sys::console; 11 | use web_sys::window; 12 | use yaml_rust::emitter::{EmitError, YamlEmitter}; 13 | use yaml_rust::scanner::ScanError; 14 | 15 | #[wasm_bindgen] 16 | pub struct ExtractCommand { 17 | title_only: bool, 18 | use_clipboard: bool, 19 | id: JsString, 20 | name: JsString, 21 | } 22 | 23 | #[wasm_bindgen] 24 | impl ExtractCommand { 25 | #[wasm_bindgen(getter)] 26 | pub fn id(&self) -> JsString { 27 | self.id.clone() 28 | } 29 | 30 | #[wasm_bindgen(setter)] 31 | pub fn set_id(&mut self, id: &str) { 32 | self.id = JsString::from(id) 33 | } 34 | 35 | #[wasm_bindgen(getter)] 36 | pub fn name(&self) -> JsString { 37 | self.name.clone() 38 | } 39 | 40 | #[wasm_bindgen(setter)] 41 | pub fn set_name(&mut self, name: &str) { 42 | self.name = JsString::from(name) 43 | } 44 | 45 | #[wasm_bindgen(method)] 46 | pub fn callback(&self) -> Promise { 47 | let title_only = self.title_only; 48 | let use_clipboard = self.use_clipboard; 49 | future_to_promise(async move { 50 | let plugin = obsidian::plugin(); 51 | let res = extract_url(&plugin, title_only, use_clipboard).await; 52 | if let Err(e) = res { 53 | let msg = format!("error: {}", e); 54 | obsidian::Notice::new(&msg); 55 | Err(JsValue::from(Error::new(&msg))) 56 | } else { 57 | Ok(JsValue::undefined()) 58 | } 59 | }) 60 | } 61 | } 62 | 63 | pub fn command_extract_url() -> ExtractCommand { 64 | ExtractCommand { 65 | id: JsString::from("extract-url"), 66 | name: JsString::from("Extract"), 67 | title_only: false, 68 | use_clipboard: false, 69 | } 70 | } 71 | 72 | pub fn command_extract_url_title_only() -> ExtractCommand { 73 | ExtractCommand { 74 | id: JsString::from("extract-title-from-url"), 75 | name: JsString::from("Title Only"), 76 | title_only: true, 77 | use_clipboard: false, 78 | } 79 | } 80 | 81 | pub fn command_import_url() -> ExtractCommand { 82 | ExtractCommand { 83 | id: JsString::from("import-url"), 84 | name: JsString::from("Import From Clipboard"), 85 | title_only: false, 86 | use_clipboard: true, 87 | } 88 | } 89 | 90 | #[derive(Error, Debug)] 91 | pub enum ExtractError { 92 | #[error("url did not parse. {0}")] 93 | Parse(#[from] url::ParseError), 94 | 95 | #[error("url had not content")] 96 | NoContent, 97 | 98 | #[error("fetch error `{0}`")] 99 | Fetch(String), 100 | 101 | #[error("select a url to extract or add link to your frontmatter. {0}")] 102 | NoUrlFrontmatter(#[from] FrontmatterError), 103 | 104 | #[error("expected view to be MarkdownView but was not")] 105 | WrongView, 106 | 107 | #[error("error serializing front matter. {0}")] 108 | FrontmatterWrite(#[from] EmitError), 109 | 110 | #[error("error transforming content. {0}")] 111 | Transform(#[from] transform::TransformError), 112 | 113 | #[error("no clipboard available")] 114 | NoClipboard, 115 | 116 | #[error("no url in clipboard")] 117 | NoClipboardContent, 118 | } 119 | 120 | impl std::convert::From for ExtractError { 121 | fn from(err: JsValue) -> Self { 122 | if let Some(err_val) = err.as_string() { 123 | ExtractError::Fetch(format!("fetch error {}", err_val)) 124 | } else { 125 | ExtractError::Fetch(String::from("fetch error")) 126 | } 127 | } 128 | } 129 | 130 | impl std::convert::From for ExtractError { 131 | fn from(_from: obsidian::View) -> Self { 132 | ExtractError::WrongView 133 | } 134 | } 135 | 136 | async fn read_clipboard() -> Result { 137 | Ok(JsFuture::from( 138 | window() 139 | .ok_or(ExtractError::NoClipboard)? 140 | .navigator() 141 | .clipboard() 142 | .ok_or(ExtractError::NoClipboard)? 143 | .read_text(), 144 | ) 145 | .await? 146 | .as_string() 147 | .ok_or(ExtractError::NoClipboardContent)?) 148 | } 149 | 150 | async fn extract_url( 151 | plugin: &obsidian::Plugin, 152 | title_only: bool, 153 | use_clipboard: bool, 154 | ) -> Result<(), ExtractError> { 155 | if let Some(md_view) = plugin 156 | .app() 157 | .workspace() 158 | .get_active_view_of_type(&obsidian::MARKDOWN_VIEW) 159 | { 160 | let view: obsidian::MarkdownView = md_view.dyn_into()?; 161 | let editor = view.source_mode().cm_editor(); 162 | let url_str = if use_clipboard { 163 | read_clipboard().await? 164 | } else { 165 | editor.get_selection() 166 | }; 167 | if url_str == "" { 168 | let (url_str, content) = extract_link_from_yaml(&view.get_view_data())?; 169 | let md = convert_url_to_markdown(title_only, &url_str).await?; 170 | let mut buf = String::new(); 171 | let mut emit = YamlEmitter::new(&mut buf); 172 | emit.dump(&content)?; 173 | editor.set_value(&format!("{}\n---\n{}", buf, md.content)); 174 | Ok(()) 175 | } else { 176 | let md = convert_url_to_markdown(title_only, &url_str).await?; 177 | editor.replace_selection(&md.content); 178 | Ok(()) 179 | } 180 | } else { 181 | Err(ExtractError::WrongView) 182 | } 183 | } 184 | 185 | pub struct Markdown { 186 | pub title: String, 187 | pub content: String, 188 | } 189 | 190 | pub async fn convert_url_to_markdown( 191 | title_only: bool, 192 | url: &str, 193 | ) -> Result { 194 | let params = request::request_params(url); 195 | let body: String = JsFuture::from(request::request(params)?) 196 | .await? 197 | .as_string() 198 | .ok_or_else(|| ExtractError::NoContent)?; 199 | 200 | if cfg!(debug_assertions) { 201 | console::log_2(&"body".into(), &JsValue::from_str(&body)); 202 | } 203 | 204 | let ref url = Url::parse(url)?; 205 | Ok(transform::transform_url(url, title_only, body).await?) 206 | } 207 | 208 | #[derive(Error, Debug)] 209 | pub enum FrontmatterError { 210 | #[error("root document not a hash")] 211 | NotHash, 212 | 213 | #[error("key link not available")] 214 | NoLink, 215 | 216 | #[error("no frontmatter found in document")] 217 | NoYaml, 218 | 219 | #[error("document failed to parse")] 220 | NotParseable(#[from] ScanError), 221 | 222 | #[error("link expected to be string type")] 223 | LinkNotString, 224 | } 225 | 226 | fn extract_link_from_yaml(view: &str) -> Result<(String, frontmatter::Yaml), FrontmatterError> { 227 | let fm = (frontmatter::parse(view)?).ok_or_else(|| FrontmatterError::NoYaml)?; 228 | if let frontmatter::Yaml::Hash(hash) = &fm { 229 | let link_y = hash 230 | .get(&frontmatter::Yaml::String(String::from("link"))) 231 | .ok_or_else(|| FrontmatterError::NoLink)?; 232 | if let frontmatter::Yaml::String(link) = link_y { 233 | Ok((link.clone(), fm)) 234 | } else { 235 | Err(FrontmatterError::LinkNotString) 236 | } 237 | } else { 238 | Err(FrontmatterError::NotHash) 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod archive; 2 | mod extract; 3 | mod obsidian; 4 | mod request; 5 | mod settings; 6 | mod shim; 7 | mod transform; 8 | use crate::settings::*; 9 | use js_sys::{JsString, Object, Reflect}; 10 | use thiserror::Error; 11 | use wasm_bindgen::prelude::*; 12 | 13 | #[wasm_bindgen] 14 | pub async fn onload(plugin: obsidian::Plugin) { 15 | plugin.addCommand(JsValue::from(extract::command_extract_url())); 16 | plugin.addCommand(JsValue::from(extract::command_extract_url_title_only())); 17 | plugin.addCommand(JsValue::from(extract::command_import_url())); 18 | plugin.addCommand(JsValue::from(archive::command_archive())); 19 | } 20 | 21 | #[wasm_bindgen] 22 | pub async fn settings(settings_tab: obsidian::RustSettingsTab) { 23 | let container = settings_tab.container_el(); 24 | container.empty(); 25 | if let Err(e) = settings_internal(&container).await { 26 | let opts = Object::new(); 27 | Reflect::set( 28 | &opts, 29 | &JsString::from("text"), 30 | &JsString::from(format!("{:?}", e)), 31 | ) 32 | .unwrap(); 33 | container.create_el("p", opts.into()); 34 | } 35 | } 36 | 37 | #[derive(Error, Debug)] 38 | pub enum SettingPageError { 39 | #[error("error loading settings. {0}")] 40 | Transform(#[from] SettingsError), 41 | } 42 | 43 | pub async fn settings_internal<'a>(container: &obsidian::Element) -> Result<(), SettingPageError> { 44 | let plugin = obsidian::plugin(); 45 | let settings = crate::settings::load_settings(&plugin).await?; 46 | let setting = obsidian::Setting::new(container); 47 | setting.set_name("archive path"); 48 | setting.set_desc("location to store scraped content"); 49 | setting.add_text(&|text| { 50 | text.set_placeholder("archive"); 51 | text.set_value(&settings.archive_path()); 52 | let f = Closure::wrap(Box::new(move |value| { 53 | wasm_bindgen_futures::spawn_local(async move { 54 | let plugin = obsidian::plugin(); 55 | let mut settings = crate::settings::load_settings(&plugin).await.unwrap(); 56 | settings.archive_path = Some(value); 57 | if let Err(e) = save_settings(&plugin, settings).await { 58 | let msg = format!("error: {}", e); 59 | obsidian::Notice::new(&msg); 60 | } 61 | }); 62 | }) as Box); 63 | text.on_change(f.into_js_value()); 64 | }); 65 | Ok(()) 66 | } 67 | -------------------------------------------------------------------------------- /src/obsidian.rs: -------------------------------------------------------------------------------- 1 | use js_sys::Function; 2 | use js_sys::Promise; 3 | use wasm_bindgen::prelude::*; 4 | 5 | #[wasm_bindgen(module = "obsidian")] 6 | extern "C" { 7 | pub type Plugin; 8 | pub type Notice; 9 | pub type App; 10 | pub type Workspace; 11 | pub type WorkspaceLeaf; 12 | pub type Vault; 13 | pub type DataAdapter; 14 | pub type View; 15 | pub type MarkdownView; 16 | pub type MarkdownSourceView; 17 | pub type CmEditor; 18 | pub type TFile; 19 | pub type TAbstractFile; 20 | pub type FileManager; 21 | pub type RustSettingsTab; 22 | pub type Element; 23 | pub type Setting; 24 | pub type TextComponent; 25 | 26 | #[wasm_bindgen(js_name=MarkdownView)] 27 | pub static MARKDOWN_VIEW: Function; 28 | 29 | #[wasm_bindgen(js_namespace=Platform, js_name=isDesktop)] 30 | pub static DESKTOP: bool; 31 | 32 | #[wasm_bindgen(method)] 33 | pub fn addCommand(this: &Plugin, command: JsValue); 34 | 35 | #[wasm_bindgen(method, getter)] 36 | pub fn app(this: &Plugin) -> App; 37 | 38 | #[wasm_bindgen(method, getter)] 39 | pub fn vault(this: &App) -> Vault; 40 | 41 | #[wasm_bindgen(method, getter)] 42 | pub fn workspace(this: &App) -> Workspace; 43 | 44 | #[wasm_bindgen(method, getter, js_name=fileManager)] 45 | pub fn file_manager(this: &App) -> FileManager; 46 | 47 | #[wasm_bindgen(method, js_name=getActiveFile)] 48 | pub fn get_active_file(this: &Workspace) -> Option; 49 | 50 | #[wasm_bindgen(method, js_name=getActiveViewOfType)] 51 | pub fn get_active_view_of_type(this: &Workspace, t: &Function) -> Option; 52 | 53 | #[wasm_bindgen(method, getter, js_name=sourceMode)] 54 | pub fn source_mode(this: &MarkdownView) -> MarkdownSourceView; 55 | 56 | #[wasm_bindgen(method, js_name=getViewData)] 57 | pub fn get_view_data(this: &MarkdownView) -> String; 58 | 59 | #[wasm_bindgen(method, getter, js_name=cmEditor)] 60 | pub fn cm_editor(this: &MarkdownSourceView) -> CmEditor; 61 | 62 | #[wasm_bindgen(method, js_name=getSelection)] 63 | pub fn get_selection(this: &CmEditor) -> String; 64 | 65 | #[wasm_bindgen(method, js_name=replaceSelection)] 66 | pub fn replace_selection(this: &CmEditor, text: &str); 67 | 68 | #[wasm_bindgen(method, js_name=setValue)] 69 | pub fn set_value(this: &CmEditor, value: &str); 70 | 71 | #[wasm_bindgen(constructor)] 72 | pub fn new(message: &str) -> Notice; 73 | 74 | #[wasm_bindgen(method)] 75 | pub fn read(this: &Vault, file: &TFile) -> Promise; 76 | 77 | #[wasm_bindgen(method)] 78 | pub fn modify(this: &Vault, file: &TFile, data: &str) -> Promise; 79 | 80 | #[wasm_bindgen(method, getter)] 81 | pub fn adapter(this: &Vault) -> DataAdapter; 82 | 83 | #[wasm_bindgen(method, catch)] 84 | pub fn exists(this: &DataAdapter, path: &str) -> Result; 85 | 86 | #[wasm_bindgen(method, catch)] 87 | pub fn mkdir(this: &DataAdapter, path: &str) -> Result; 88 | 89 | #[wasm_bindgen(method, catch)] 90 | pub fn create(this: &Vault, path: &str, data: &str) -> Result; 91 | 92 | #[wasm_bindgen(method, catch, js_name=getAbstractFileByPath)] 93 | pub fn get_abstract_file_by_path( 94 | this: &Vault, 95 | path: &str, 96 | ) -> Result, JsValue>; 97 | 98 | #[wasm_bindgen(method, js_name=generateMarkdownLink)] 99 | pub fn generate_markdown_link( 100 | this: &FileManager, 101 | file: &TFile, 102 | source_path: &str, 103 | subpath: Option<&str>, 104 | alias: Option<&str>, 105 | ) -> String; 106 | 107 | #[wasm_bindgen(method, getter)] 108 | pub fn app(this: &RustSettingsTab) -> App; 109 | 110 | #[wasm_bindgen(method, getter)] 111 | pub fn plugin(this: &RustSettingsTab) -> Plugin; 112 | 113 | #[wasm_bindgen(method, getter, js_name=containerEl)] 114 | pub fn container_el(this: &RustSettingsTab) -> Element; 115 | 116 | #[wasm_bindgen(method)] 117 | pub fn empty(this: &Element); 118 | 119 | #[wasm_bindgen(method, js_name=createEl)] 120 | pub fn create_el(this: &Element, tag: &str, options: JsValue) -> Element; 121 | 122 | #[wasm_bindgen(constructor)] 123 | pub fn new(container: &Element) -> Setting; 124 | 125 | #[wasm_bindgen(method, js_name=setName)] 126 | pub fn set_name(this: &Setting, name: &str) -> Setting; 127 | 128 | #[wasm_bindgen(method, js_name=setDesc)] 129 | pub fn set_desc(this: &Setting, desc: &str) -> Setting; 130 | 131 | #[wasm_bindgen(method, js_name=addText)] 132 | pub fn add_text(this: &Setting, cb: &dyn Fn(TextComponent)) -> Setting; 133 | 134 | #[wasm_bindgen(method, js_name=setPlaceholder)] 135 | pub fn set_placeholder(this: &TextComponent, placeholder: &str) -> TextComponent; 136 | 137 | #[wasm_bindgen(method, js_name=setValue)] 138 | pub fn set_value(this: &TextComponent, value: &str) -> TextComponent; 139 | 140 | #[wasm_bindgen(method, js_name=onChange)] 141 | pub fn on_change(this: &TextComponent, cb: JsValue) -> TextComponent; 142 | } 143 | 144 | #[wasm_bindgen( 145 | inline_js = "export function plugin() { return app.plugins.plugins['extract-url']; }" 146 | )] 147 | extern "C" { 148 | pub fn plugin() -> Plugin; 149 | } 150 | -------------------------------------------------------------------------------- /src/request.rs: -------------------------------------------------------------------------------- 1 | use js_sys::Promise; 2 | use wasm_bindgen::prelude::*; 3 | 4 | #[wasm_bindgen] 5 | extern "C" { 6 | #[wasm_bindgen(catch)] 7 | pub fn request(params: JsValue) -> Result; 8 | } 9 | 10 | pub fn request_params(url: &str) -> JsValue { 11 | let obj = js_sys::Object::new(); 12 | js_sys::Reflect::set(&obj, &"url".into(), &JsValue::from_str(url)).unwrap(); 13 | obj.into() 14 | } 15 | -------------------------------------------------------------------------------- /src/settings.rs: -------------------------------------------------------------------------------- 1 | use crate::obsidian::Plugin; 2 | use js_sys::Promise; 3 | use serde::{Deserialize, Serialize}; 4 | use thiserror::Error; 5 | use wasm_bindgen::prelude::*; 6 | use wasm_bindgen_futures::JsFuture; 7 | 8 | #[wasm_bindgen] 9 | extern "C" { 10 | #[wasm_bindgen(method, js_name=loadData)] 11 | fn load_data(this: &Plugin) -> Promise; 12 | 13 | #[wasm_bindgen(method, js_name=saveData)] 14 | fn save_data(this: &Plugin, data: JsValue) -> Promise; 15 | } 16 | 17 | #[derive(Serialize, Deserialize, Clone)] 18 | pub struct Settings { 19 | pub archive_path: Option, 20 | } 21 | 22 | impl Settings { 23 | pub fn archive_path(self: &Self) -> &str { 24 | match &self.archive_path { 25 | Some(s) => s, 26 | None => "archive", 27 | } 28 | } 29 | } 30 | 31 | #[derive(Error, Debug)] 32 | pub enum SettingsError { 33 | #[error("Error deserializing setting data. {0}")] 34 | Serde(#[from] serde_json::error::Error), 35 | #[error("unexpected error `{0}`")] 36 | JsError(String), 37 | 38 | #[error("unknown error from js")] 39 | UnknownJsError, 40 | } 41 | 42 | impl std::convert::From for SettingsError { 43 | fn from(err: JsValue) -> Self { 44 | if let Some(err_val) = err.as_string() { 45 | SettingsError::JsError(err_val) 46 | } else { 47 | SettingsError::UnknownJsError 48 | } 49 | } 50 | } 51 | 52 | pub async fn load_settings(plugin: &Plugin) -> Result { 53 | let value: Option = JsFuture::from(plugin.load_data()).await?.into_serde()?; 54 | Ok(match value { 55 | Some(settings) => settings, 56 | None => Settings { archive_path: None }, 57 | }) 58 | } 59 | 60 | pub async fn save_settings(plugin: &Plugin, settings: Settings) -> Result<(), SettingsError> { 61 | let value = JsValue::from_serde(&settings)?; 62 | JsFuture::from(plugin.save_data(value)).await?; 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /src/shim.rs: -------------------------------------------------------------------------------- 1 | use js_sys::Error; 2 | use wasm_bindgen::prelude::*; 3 | 4 | #[wasm_bindgen(module = "/shim.js")] 5 | extern "C" { 6 | #[wasm_bindgen(js_name=hasBin)] 7 | pub fn has_bin(app: &str) -> bool; 8 | 9 | #[wasm_bindgen(js_name=nodeExec)] 10 | pub fn node_exec(cmd: &str, f: &Closure, String, String)>) -> JsValue; 11 | 12 | #[wasm_bindgen(js_name = clipboardReadText)] 13 | pub fn clipboard_read_text() -> String; 14 | } 15 | -------------------------------------------------------------------------------- /src/transform/github.rs: -------------------------------------------------------------------------------- 1 | use crate::request; 2 | use crate::transform::Markdown; 3 | use fancy_regex::Captures; 4 | use fancy_regex::Regex; 5 | use lazy_static::lazy_static; 6 | use scraper::Html; 7 | use scraper::Selector; 8 | use std::fmt; 9 | use thiserror::Error; 10 | use url::Url; 11 | use wasm_bindgen::JsValue; 12 | use wasm_bindgen_futures::JsFuture; 13 | 14 | lazy_static! { 15 | static ref RELATIVE_URL_REGEX: Regex = 16 | Regex::new(r"!\[(?P.*)\]\((?P\w.*)\)").unwrap(); 17 | } 18 | 19 | pub fn is_repo(url: &Url) -> bool { 20 | if let Some(domain) = url.domain() { 21 | if domain.eq("github.com") { 22 | if url.path().matches("/").count() == 2 { 23 | return true; 24 | } 25 | } 26 | } 27 | false 28 | } 29 | 30 | fn github_content_url(user: &str, repo: &str, branch: &Branch, path: &str) -> String { 31 | format!( 32 | "https://raw.githubusercontent.com/{}/{}/{}/{}", 33 | user, repo, branch, path 34 | ) 35 | } 36 | 37 | #[derive(Debug, PartialEq)] 38 | struct Repo { 39 | user: String, 40 | repo: String, 41 | } 42 | 43 | fn github_repo_to_readme_urls(url: &Url) -> (Repo, Vec<(Branch, Url)>) { 44 | let mut segments = url.path_segments().unwrap(); 45 | let user = segments.next().unwrap(); 46 | let repo = segments.next().unwrap(); 47 | ( 48 | Repo { 49 | user: user.to_string(), 50 | repo: repo.to_string(), 51 | }, 52 | vec![ 53 | ( 54 | Branch::Master, 55 | Url::parse(&github_content_url( 56 | &user, 57 | &repo, 58 | &Branch::Master, 59 | &"README.md", 60 | )) 61 | .unwrap(), 62 | ), 63 | ( 64 | Branch::Main, 65 | Url::parse(&github_content_url( 66 | &user, 67 | &repo, 68 | &Branch::Main, 69 | &"README.md", 70 | )) 71 | .unwrap(), 72 | ), 73 | ], 74 | ) 75 | } 76 | 77 | #[derive(Error, Debug)] 78 | pub enum GithubError { 79 | #[error("readme not found")] 80 | NoReadme(), 81 | 82 | #[error("fetch error `{0}`")] 83 | Fetch(String), 84 | } 85 | 86 | impl std::convert::From for GithubError { 87 | fn from(err: JsValue) -> Self { 88 | if let Some(err_val) = err.as_string() { 89 | GithubError::Fetch(format!("fetch error {}", err_val)) 90 | } else { 91 | GithubError::Fetch(String::from("fetch error")) 92 | } 93 | } 94 | } 95 | 96 | pub async fn transform_url( 97 | url: &Url, 98 | title_only: bool, 99 | body: String, 100 | ) -> Result { 101 | let title = body_to_title(&body).unwrap(); 102 | if title_only { 103 | return Ok(Markdown { 104 | title: title.clone(), 105 | content: format!("[{}]({})", title, url), 106 | }); 107 | } 108 | 109 | let (repo, urls) = github_repo_to_readme_urls(url); 110 | let (branch, readme) = first_working_url(&urls).await?; 111 | let fixed_readme = fix_relative_urls(&repo, &branch, &readme); 112 | Ok(Markdown { 113 | title: title.clone(), 114 | content: fixed_readme, 115 | }) 116 | } 117 | 118 | #[derive(Clone, Debug, PartialEq)] 119 | enum Branch { 120 | Master, 121 | Main, 122 | } 123 | 124 | impl fmt::Display for Branch { 125 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 126 | match self { 127 | Branch::Master => write!(f, "master"), 128 | Branch::Main => write!(f, "main"), 129 | } 130 | } 131 | } 132 | 133 | async fn first_working_url(urls: &[(Branch, Url)]) -> Result<(Branch, String), GithubError> { 134 | for url in urls { 135 | let params = request::request_params(url.1.as_str()); 136 | if let Ok(body) = JsFuture::from(request::request(params)?).await { 137 | return Ok((url.0.clone(), body.as_string().unwrap())); 138 | } 139 | } 140 | Err(GithubError::NoReadme()) 141 | } 142 | 143 | fn body_to_title(body: &str) -> Option { 144 | let doc = Html::parse_document(body); 145 | let selector = Selector::parse("meta[name=\"twitter:title\"]").unwrap(); 146 | doc.select(&selector) 147 | .nth(0) 148 | .map(|node| node.value().attr(&"content")) 149 | .flatten() 150 | .map(|attr| attr.to_string()) 151 | } 152 | 153 | fn fix_relative_urls(repo: &Repo, branch: &Branch, body: &str) -> String { 154 | RELATIVE_URL_REGEX 155 | .replace_all(body, |caps: &Captures| { 156 | let url = caps.name("url").unwrap().as_str(); 157 | let text = caps.name("text").map(|o| o.as_str()).unwrap_or(""); 158 | let fixed_url = github_content_url(&repo.user, &repo.repo, branch, url); 159 | format!("![{}]({})", text, fixed_url) 160 | }) 161 | .to_string() 162 | } 163 | 164 | #[cfg(test)] 165 | mod tests { 166 | use super::*; 167 | 168 | #[test] 169 | fn test_is_repo() { 170 | assert!(is_repo( 171 | &Url::parse(&"https://github.com/Restioson/xtra").unwrap() 172 | )); 173 | assert!(!is_repo( 174 | &Url::parse(&"https://github.com/Restioson/xtra/commits/master").unwrap() 175 | )); 176 | } 177 | 178 | #[test] 179 | fn test_github_repo_to_readme_urls() { 180 | assert_eq!( 181 | github_repo_to_readme_urls(&Url::parse(&"https://github.com/Restioson/xtra").unwrap()), 182 | ( 183 | Repo { 184 | user: String::from("Restioson"), 185 | repo: String::from("xtra") 186 | }, 187 | vec!( 188 | ( 189 | Branch::Master, 190 | Url::parse( 191 | "https://raw.githubusercontent.com/Restioson/xtra/master/README.md" 192 | ) 193 | .unwrap() 194 | ), 195 | ( 196 | Branch::Main, 197 | Url::parse( 198 | "https://raw.githubusercontent.com/Restioson/xtra/main/README.md" 199 | ) 200 | .unwrap() 201 | ) 202 | ) 203 | ) 204 | ); 205 | } 206 | 207 | #[test] 208 | fn test_body_to_title() { 209 | assert_eq!( 210 | body_to_title(""), 211 | Some(String::from("Restioson/xtra: 🎭 A tiny actor framework")) 212 | ); 213 | 214 | assert_eq!(body_to_title(""), None); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/transform/mod.rs: -------------------------------------------------------------------------------- 1 | mod github; 2 | mod oembed; 3 | use crate::extract::Markdown; 4 | use html2md::parse_html; 5 | use readability::extractor::extract; 6 | use thiserror::Error; 7 | use url::Url; 8 | use web_sys::console; 9 | 10 | #[derive(Error, Debug)] 11 | pub enum TransformError { 12 | #[error("url not readable. {0}")] 13 | Read(#[from] readability::error::Error), 14 | 15 | #[error("error converting oembed data. {0}")] 16 | Oembed(#[from] oembed::OembedError), 17 | 18 | #[error("error geting github readme. {0}")] 19 | Github(#[from] github::GithubError), 20 | } 21 | 22 | pub async fn readable_content(body: String, url: &Url) -> Result { 23 | let ref mut b = body.as_bytes(); 24 | let readable = extract(b, url)?; 25 | 26 | Ok(Markdown { 27 | title: readable.title.clone(), 28 | content: format!( 29 | "# [{}]({})\n{}", 30 | readable.title, 31 | url, 32 | parse_html(&readable.content) 33 | ), 34 | }) 35 | } 36 | 37 | pub async fn readable_title(body: String, url: &Url) -> Result { 38 | let ref mut b = body.as_bytes(); 39 | let readable = extract(b, url)?; 40 | 41 | Ok(Markdown { 42 | title: readable.title.clone(), 43 | content: format!("[{}]({})", readable.title, url), 44 | }) 45 | } 46 | 47 | pub async fn transform_url( 48 | url: &Url, 49 | title_only: bool, 50 | body: String, 51 | ) -> Result { 52 | if github::is_repo(url) { 53 | return Ok(github::transform_url(url, title_only, body).await?); 54 | } 55 | 56 | if title_only { 57 | match oembed::oembed_title(body.clone(), url).await { 58 | Ok(o) => Ok(o), 59 | Err(e) => { 60 | console::log_2(&"oembed error".into(), &format!("{:?}", e).into()); 61 | readable_title(body.clone(), url).await 62 | } 63 | } 64 | } else { 65 | match oembed::oembed_content(body.clone(), url).await { 66 | Ok(o) => Ok(o), 67 | Err(e) => { 68 | console::log_2(&"oembed error".into(), &format!("{:?}", e).into()); 69 | readable_content(body.clone(), url).await 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/transform/oembed.rs: -------------------------------------------------------------------------------- 1 | mod metadata; 2 | use crate::extract::Markdown; 3 | use crate::request; 4 | use html2md::parse_html; 5 | use serde::Deserialize; 6 | use serde_json; 7 | use thiserror::Error; 8 | use url::Url; 9 | use wasm_bindgen::JsValue; 10 | use wasm_bindgen_futures::JsFuture; 11 | use web_sys::console; 12 | 13 | #[derive(Error, Debug)] 14 | pub enum OembedError { 15 | #[error("Url missing oembed info")] 16 | NoHtml, 17 | 18 | #[error("Error fetching url for oembed")] 19 | Fetch(String), 20 | 21 | #[error("Error fetching url for oembed. {0}")] 22 | Url(#[from] url::ParseError), 23 | 24 | #[error("Error serializing oembed data. {0}")] 25 | Serde(#[from] serde_json::error::Error), 26 | 27 | #[error("Error getting youtube metadata")] 28 | Metadata(#[from] metadata::MetadataError), 29 | } 30 | 31 | impl std::convert::From for OembedError { 32 | fn from(err: JsValue) -> Self { 33 | if let Some(err_val) = err.as_string() { 34 | OembedError::Fetch(format!("fetch error {}", err_val)) 35 | } else { 36 | OembedError::Fetch(String::from("fetch error")) 37 | } 38 | } 39 | } 40 | 41 | #[derive(Debug, Deserialize)] 42 | pub struct OembedData { 43 | pub html: Option, 44 | pub title: String, 45 | } 46 | 47 | pub async fn oembed_content(_body: String, url: &Url) -> Result { 48 | let m = match metadata::metadata(url).await { 49 | Err(e) => { 50 | console::log_2(&"metadata error".into(), &format!("{:?}", e).into()); 51 | None 52 | } 53 | Ok(o) => o, 54 | }; 55 | 56 | let mut href = Url::parse("https://noembed.com/embed")?; 57 | href.query_pairs_mut().append_pair("url", &url.to_string()); 58 | let params = request::request_params(href.as_str()); 59 | let body = JsFuture::from(request::request(params)?) 60 | .await? 61 | .as_string() 62 | .ok_or_else(|| OembedError::Fetch("Value not a string".into()))?; 63 | 64 | if cfg!(debug_assertions) { 65 | let dbody = body.clone(); 66 | console::log_2(&"oembed".into(), &dbody.into()); 67 | } 68 | 69 | let data: OembedData = serde_json::from_str(&body)?; 70 | match data.html { 71 | None => Err(OembedError::NoHtml), 72 | Some(html) => { 73 | if html.contains("iframe") { 74 | match m { 75 | None => Ok(Markdown{ 76 | title: data.title.clone(), 77 | content: format!("# [{}]({})\n{}", data.title, url, html) 78 | }), 79 | Some(video) => Ok(Markdown{ 80 | title: data.title.clone(), 81 | content: format!( 82 | "# [{}]({})\n{}\n\n[{}]({})\n{}", 83 | data.title, url, html, video.channel, video.uploader_url, video.description 84 | )}), 85 | } 86 | } else { 87 | Ok(Markdown { 88 | title: data.title.clone(), 89 | content: format!( 90 | "# [{}]({})\n{}", 91 | data.title, 92 | url, 93 | parse_html(&html) 94 | )}) 95 | } 96 | } 97 | } 98 | } 99 | 100 | pub async fn oembed_title(_body: String, url: &Url) -> Result { 101 | let mut href = Url::parse("https://noembed.com/embed")?; 102 | href.query_pairs_mut().append_pair("url", &url.to_string()); 103 | let params = request::request_params(url.as_str()); 104 | let body = JsFuture::from(request::request(params)?).await?; 105 | let data: OembedData = body.into_serde()?; 106 | Ok(Markdown{ 107 | title: data.title.clone(), 108 | content: format!("[{}]({})", data.title, url) 109 | }) 110 | } 111 | -------------------------------------------------------------------------------- /src/transform/oembed/metadata.rs: -------------------------------------------------------------------------------- 1 | use crate::shim::{has_bin, node_exec}; 2 | use futures::channel::oneshot; 3 | use js_sys::Error; 4 | use serde::Deserialize; 5 | use serde_json; 6 | use thiserror::Error; 7 | use url::{Host, Url}; 8 | use wasm_bindgen::prelude::*; 9 | use web_sys::console; 10 | 11 | #[derive(Error, Debug)] 12 | pub enum ExecError { 13 | #[error("error executing command. {0} {1}")] 14 | Error(String, String), 15 | 16 | #[error("oneshot caneled")] 17 | Canceled(#[from] oneshot::Canceled), 18 | } 19 | 20 | async fn exec(cmd: &str) -> Result, ExecError> { 21 | let (sender, receiver) = oneshot::channel::>(); 22 | let cb = Closure::once(|err_val: Option, out: String, out_err: String| { 23 | let res = match err_val { 24 | None => sender.send(Ok(out)), 25 | Some(err) => { 26 | let msg: String = err.message().into(); 27 | sender.send(Err(ExecError::Error(msg, out_err))) 28 | } 29 | }; 30 | res.unwrap(); 31 | }); 32 | node_exec(cmd, &cb); 33 | let body = receiver.await??; 34 | Ok(Some(body)) 35 | } 36 | 37 | #[derive(Debug, Deserialize)] 38 | pub struct VideoMetadata { 39 | pub channel: String, 40 | pub uploader_url: String, 41 | pub description: String, 42 | } 43 | 44 | #[derive(Error, Debug)] 45 | pub enum MetadataError { 46 | #[error("error running youtube-dl {0}")] 47 | Exec(#[from] ExecError), 48 | 49 | #[error("Error serializing youtub data. {0}")] 50 | Serde(#[from] serde_json::error::Error), 51 | } 52 | 53 | pub async fn metadata(url: &Url) -> Result, MetadataError> { 54 | if !is_youtube(url) { 55 | return Ok(None); 56 | } else if !has_bin("youtube-dl") { 57 | return Ok(None); 58 | } 59 | console::log_1(&"detected youtube-dl".into()); 60 | match exec(&format!("youtube-dl -j {}", url)).await? { 61 | None => Ok(None), 62 | Some(s) => { 63 | let m: VideoMetadata = serde_json::from_str(&s)?; 64 | Ok(Some(m)) 65 | } 66 | } 67 | } 68 | 69 | fn is_youtube(url: &Url) -> bool { 70 | match url.host() { 71 | Some(Host::Domain("youtu.be")) 72 | | Some(Host::Domain("youtube.com")) 73 | | Some(Host::Domain("www.youtube.com")) => true, 74 | _ => false, 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | async@~1.5: 6 | version "1.5.2" 7 | resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" 8 | integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= 9 | 10 | esbuild@^0.12.25: 11 | version "0.12.25" 12 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.25.tgz#c2131cef022cf9fe94aaa5e00110b27fc976221a" 13 | integrity sha512-woie0PosbRSoN8gQytrdCzUbS2ByKgO8nD1xCZkEup3D9q92miCze4PqEI9TZDYAuwn6CruEnQpJxgTRWdooAg== 14 | 15 | hasbin@^1.2.3: 16 | version "1.2.3" 17 | resolved "https://registry.yarnpkg.com/hasbin/-/hasbin-1.2.3.tgz#78c5926893c80215c2b568ae1fd3fcab7a2696b0" 18 | integrity sha1-eMWSaJPIAhXCtWiuH9P8q3omlrA= 19 | dependencies: 20 | async "~1.5" 21 | --------------------------------------------------------------------------------