Compare commits

..

1 Commits

  1. 6
      .github/FUNDING.yml
  2. 189
      CHANGELOG.md
  3. 427
      Cargo.lock
  4. 2
      Cargo.toml
  5. 187
      DEV.md
  6. 2
      LICENSE-MIT
  7. 190
      README.md
  8. 40
      RELEASE-NOTE.md
  9. 1
      nextgraph/.gitignore
  10. 20
      nextgraph/Cargo.toml
  11. 2
      nextgraph/README.md
  12. 4
      nextgraph/examples/in_memory.rs
  13. 2
      nextgraph/examples/open.rs
  14. 4
      nextgraph/examples/persistent.rs
  15. 38
      nextgraph/src/lib.rs
  16. 872
      nextgraph/src/local_broker.rs
  17. 1
      nextgraph/src/local_broker_dev_env.rs
  18. 40
      ng-app/index-native.html
  19. 88
      ng-app/index-web.html
  20. 76
      ng-app/package.json
  21. 7
      ng-app/src-tauri/Cargo.toml
  22. 1
      ng-app/src-tauri/gen/android/app/src/main/AndroidManifest.xml
  23. 531
      ng-app/src-tauri/src/lib.rs
  24. 2
      ng-app/src-tauri/src/main.rs
  25. 2
      ng-app/src-tauri/src/mobile.rs
  26. 2
      ng-app/src-tauri/tauri.conf.json
  27. 26
      ng-app/src/App.svelte
  28. 147
      ng-app/src/api.ts
  29. 179
      ng-app/src/apps/AutomergeEditor.svelte
  30. 74
      ng-app/src/apps/AutomergeJsonSource.svelte
  31. 19
      ng-app/src/apps/AutomergeViewer.svelte
  32. 101
      ng-app/src/apps/CodeMirrorEditor.svelte
  33. 88
      ng-app/src/apps/ContainerView.svelte
  34. 0
      ng-app/src/apps/JsonLdEditor.svelte
  35. 159
      ng-app/src/apps/MdSource.svelte
  36. 200
      ng-app/src/apps/MilkDownEditor.svelte
  37. 197
      ng-app/src/apps/PostMdViewer.svelte
  38. 105
      ng-app/src/apps/ProseMirrorEditor.svelte
  39. 120
      ng-app/src/apps/ProseMirrorViewer.svelte
  40. 126
      ng-app/src/apps/SparqlQueryEditor.svelte
  41. 78
      ng-app/src/apps/SparqlUpdateEditor.svelte
  42. 129
      ng-app/src/apps/TextViewer.svelte
  43. 84
      ng-app/src/apps/TurtleViewer.svelte
  44. 131
      ng-app/src/apps/XmlSource.svelte
  45. 19
      ng-app/src/apps/YArrayEditor.svelte
  46. 19
      ng-app/src/apps/YArraySource.svelte
  47. 19
      ng-app/src/apps/YArrayViewer.svelte
  48. 291
      ng-app/src/apps/YMapEditor.svelte
  49. 79
      ng-app/src/apps/YMapSource.svelte
  50. 189
      ng-app/src/apps/YMapViewer.svelte
  51. 36
      ng-app/src/apps/automerge/ABoolean.svelte
  52. 65
      ng-app/src/apps/automerge/ACounter.svelte
  53. 71
      ng-app/src/apps/automerge/ADate.svelte
  54. 118
      ng-app/src/apps/automerge/AList.svelte
  55. 131
      ng-app/src/apps/automerge/AMap.svelte
  56. 42
      ng-app/src/apps/automerge/ANumber.svelte
  57. 43
      ng-app/src/apps/automerge/AString.svelte
  58. 87
      ng-app/src/apps/automerge/AValue.svelte
  59. 81
      ng-app/src/apps/automerge/utils.ts
  60. 102
      ng-app/src/apps/milkdown-placeholder.ts
  61. 59
      ng-app/src/base64url.js
  62. 521
      ng-app/src/classes.ts
  63. 2
      ng-app/src/history/LICENSE.md
  64. 0
      ng-app/src/history/gitgraph-core/branch.ts
  65. 0
      ng-app/src/history/gitgraph-core/branches-paths.ts
  66. 0
      ng-app/src/history/gitgraph-core/commit.ts
  67. 57
      ng-app/src/history/gitgraph-core/gitgraph-user-api.ts
  68. 0
      ng-app/src/history/gitgraph-core/gitgraph.ts
  69. 0
      ng-app/src/history/gitgraph-core/graph-rows.ts
  70. 0
      ng-app/src/history/gitgraph-core/index.ts
  71. 0
      ng-app/src/history/gitgraph-core/regular-graph-rows.ts
  72. 0
      ng-app/src/history/gitgraph-core/template.ts
  73. 0
      ng-app/src/history/gitgraph-core/utils.ts
  74. 21
      ng-app/src/history/gitgraph-js/gitgraph.ts
  75. 7
      ng-app/src/history/gitgraph-js/svg-elements.ts
  76. 2
      ng-app/src/history/gitgraph-js/tooltip.ts
  77. 10
      ng-app/src/lib/CenteredLayout.svelte
  78. 187
      ng-app/src/lib/DataClassIcon.svelte
  79. 224
      ng-app/src/lib/Document.svelte
  80. 1306
      ng-app/src/lib/FullLayout.svelte
  81. 69
      ng-app/src/lib/Home.svelte
  82. 20
      ng-app/src/lib/Install.svelte
  83. 77
      ng-app/src/lib/Login.svelte
  84. 4
      ng-app/src/lib/MobileBottomBar.svelte
  85. 13
      ng-app/src/lib/MobileBottomBarItem.svelte
  86. 6
      ng-app/src/lib/NoWallet.svelte
  87. 41
      ng-app/src/lib/Pane.svelte
  88. 616
      ng-app/src/lib/Test.svelte
  89. 24
      ng-app/src/lib/ZeraIcon.svelte
  90. 10
      ng-app/src/lib/components/CopyToClipboard.svelte
  91. 2
      ng-app/src/lib/components/DeviceIcon.svelte
  92. 4
      ng-app/src/lib/components/Logo.svelte
  93. 57
      ng-app/src/lib/components/MenuItem.svelte
  94. 64
      ng-app/src/lib/components/Message.svelte
  95. 94
      ng-app/src/lib/components/NavBar.svelte
  96. 49
      ng-app/src/lib/components/PaneHeader.svelte
  97. 4
      ng-app/src/lib/components/PasswordInput.svelte
  98. 2
      ng-app/src/lib/components/Spinner.svelte
  99. 3
      ng-app/src/lib/icons/BrailleIcon.svelte
  100. 13
      ng-app/src/lib/icons/BranchIcon.svelte
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,7 +1,3 @@
liberapay: nextgraph
ko_fi: nextgraph
custom:
[
"https://donate.stripe.com/8wMcOE3NI2B69NKeUU",
"https://nextgraph.org/donate",
]
custom: ["https://donate.stripe.com/8wMcOE3NI2B69NKeUU", "https://pay.vivawallet.com/par-le-peuple", "https://nextgraph.org/donate"]

@ -1,189 +0,0 @@
# Changelog
Access the sub-sections directly :
[App](#app) - [SDK](#sdk) - [Broker](#broker) - [CLI](#cli)
## App
### App [0.1.1-alpha] - 2024-09-02
#### Added
- edit title and intro
#### Fixed
- bug doc not saved when back navigation
### App [0.1.0-preview.8] - 2024-08-21
#### Added
- signature tool: signs HEADS or a snapshot
#### Fixed
- bug in synchronization of stores content (container) on tauri native apps
- removed dark theme (that wasn't implemented properly)
- on web-app, detects jshelter and ask user to deactivate it
### App [0.1.0-preview.7] - 2024-08-15
#### Added
- Wallet Creation : Download Recovery PDF
- Wallet Creation : Download wallet file
- Wallet Login : with pazzle
- Wallet Login : correct errors while entering pazzle
- Wallet Login : with mnemonic
- Wallet Login : in-memory session (save nothing locally)
- Wallet Import : from file
- Wallet Import : from QR code
- Wallet Import : from TextCode
- User Panel : Online / Offline status
- User Panel : Toggle Personal Connection
- User Panel : Logout
- User Panel / Wallet : Export by scanning QRCode
- User Panel / Wallet : Export by generating QRCode
- User Panel / Wallet : Export by generating TextCode
- User Panel / Wallet : Download file
- User Panel / Accounts Info : basic info (not accurate)
- Document Menu : switch Viewer / Editor
- Document Menu : switch Graph / Document
- Document Menu : Live editing
- Document Menu : Upload binary file + Attachements and Files pane
- Document Menu : History pane
- Add Document : Save in current Store
- Document class: Source Code: Rust, JS, TS, Svelte, React
- Document class: Data : Graph, Container, JSON, Array, Object
- Document class: Post (rich text)
- Document class: Markdown (rich text)
- Document class: Plain Text
- A11Y : limited ARIA and tabulation navigation on all pages. not tested with screen-reader.
- I18N : english
- I18N : german (partial)
- Native app: macOS
- Native app: android
- Native app: linux and Ubuntu
- Native app: Windows
## SDK
### SDK [unreleased]
#### Added
- js & nodejs : fetch_header
- js & nodejs : update_header
- js & nodejs : signature_status
- js & nodejs : signed_snapshot_request
- js & nodejs : signature_request
- rust : app_request: Fetch : SignatureStatus , SignatureRequest SignedSnapshotRequest
### SDK [0.1.0-preview.6] - 2024-08-15
#### Added
- js : session_start
- js : session_start_remote
- js : session_stop
- js : user_connect
- js : user_disconnect
- js : discrete_update
- js : sparql_update
- js : sparql_query (returns SPARQL Query Results JSON Format, a list of turtle triples, or a boolean )
- js : branch_history
- js : app_request_stream (fetch and subscribe)
- js : app_request
- js : doc_create
- js : file_get
- js : upload_start
- js : upload_done
- js : upload_chunk
- nodejs : init_headless
- nodejs : session_headless_start
- nodejs : session_headless_stop
- nodejs : sparql_query (returns SPARQL Query Results JSON Format, RDF-JS data model, or a boolean)
- nodejs : discrete_update
- nodejs : sparql_update
- nodejs : rdf_dump
- nodejs : admin_create_user
- nodejs : doc_create
- nodejs : file_get
- nodejs : file_put
- rust : session_start
- rust : session_stop
- rust : app_request_stream, gives access to:
- fetch and subscribe
- file_get
- rust : app_request, gives access to:
- create_doc
- sparql_query
- sparql_update
- discrete_update
- rdf_dump
- history
- file_put
## Broker
### Broker [0.1.1-alpha] - 2024-09-02
### Broker [0.1.0-preview.8] - 2024-08-21
#### Added
- ExtProtocol : ObjectGet
### Broker [0.1.0-preview.7] - 2024-08-15
#### Added
- listen on localhost
- listen on domain
- listen on private LAN
- listen on public IP
- invite-admin
- broker service provider : add invitation for user
- serve web app
- ExtProtocol : WalletGetExport
- ClientProtocol : BlocksExist
- ClientProtocol : BlocksGet
- ClientProtocol : BlocksPut
- ClientProtocol : CommitGet
- ClientProtocol : Event
- ClientProtocol : PinRepo
- ClientProtocol : RepoPinStatus
- ClientProtocol : TopicSub
- ClientProtocol : TopicSyncReq
- ClientProtocol : WalletPutExport
- AppProtocol : AppRequest
- AppProtocol : AppSessionStart
- AppProtocol : AppSessionStop
- AdminProtocol : AddInvitation
- AdminProtocol : AddUser
- AdminProtocol : CreateUser
- AdminProtocol : DelUser
- AdminProtocol : ListInvitations
- AdminProtocol : ListUsers
## CLI
### CLI [0.1.1-alpha] - 2024-09-02
### CLI [0.1.0-preview.8] - 2024-08-21
#### Added
- get : download binary files, snapshots, and head commits, and verify signature
### CLI [0.1.0-preview.7] - 2024-08-15
#### Added
- gen-key
- admin : add/remove admin user
- admin : add invitation
- admin : list users
- admin : list invitations

427
Cargo.lock generated

@ -12,6 +12,15 @@ dependencies = [
"psl-types",
]
[[package]]
name = "addr2line"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
@ -464,9 +473,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "automerge"
version = "0.5.11"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0b670b68c38e4042ea4826415f0f8101428810bce821d215e271966b24abac4"
checksum = "93b5e6ed2097a1e55cce3128d64c909cdb42c800d4880411c7382f3dfa2c808d"
dependencies = [
"flate2",
"fxhash",
@ -484,6 +493,21 @@ dependencies = [
"uuid",
]
[[package]]
name = "backtrace"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "base64"
version = "0.13.1"
@ -496,12 +520,6 @@ version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64-url"
version = "2.0.0"
@ -538,6 +556,15 @@ dependencies = [
"syn 2.0.58",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
dependencies = [
"serde",
]
[[package]]
name = "bit_field"
version = "0.10.2"
@ -637,6 +664,17 @@ dependencies = [
"log",
]
[[package]]
name = "bloomfilter"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b64d54e47a7f4fd723f082e8f11429f3df6ba8adaeca355a76556f9f0602bbcf"
dependencies = [
"bit-vec",
"getrandom 0.2.10",
"siphasher 1.0.1",
]
[[package]]
name = "brotli"
version = "3.3.4"
@ -1383,12 +1421,6 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "data-url"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
[[package]]
name = "debug_print"
version = "1.0.0"
@ -1659,6 +1691,28 @@ dependencies = [
"zune-inflate",
]
[[package]]
name = "failure"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
dependencies = [
"backtrace",
"failure_derive",
]
[[package]]
name = "failure_derive"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"synstructure",
]
[[package]]
name = "fastrand"
version = "1.9.0"
@ -1677,12 +1731,6 @@ dependencies = [
"getrandom 0.2.10",
]
[[package]]
name = "fastrange-rs"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e90a1392cd6ec5ebe42ccaf251f2b7ba6be654c377f05c913f3898bfb2172512"
[[package]]
name = "fdeflate"
version = "0.3.0"
@ -1743,12 +1791,6 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "float-cmp"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
[[package]]
name = "flume"
version = "0.10.14"
@ -2097,6 +2139,12 @@ dependencies = [
"weezl",
]
[[package]]
name = "gimli"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "gio"
version = "0.16.7"
@ -2572,12 +2620,6 @@ dependencies = [
"tiff",
]
[[package]]
name = "imagesize"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
[[package]]
name = "indexmap"
version = "1.9.3"
@ -2755,13 +2797,14 @@ checksum = "f850fafca79ebacd70eab9d80cb75a33aeda38bde8f3dd784c1837cdf0bde631"
[[package]]
name = "json-patch"
version = "1.4.0"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec9ad60d674508f3ca8f380a928cfe7b096bc729c4e2dbfe3852bc45da3ab30b"
checksum = "1f54898088ccb91df1b492cc80029a6fdf1c48ca0db7c6822a8babad69c94658"
dependencies = [
"serde",
"serde_json",
"thiserror",
"treediff",
]
[[package]]
@ -2788,16 +2831,6 @@ dependencies = [
"selectors",
]
[[package]]
name = "kurbo"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c"
dependencies = [
"arrayvec",
"smallvec",
]
[[package]]
name = "kv-log-macro"
version = "1.0.7"
@ -2909,9 +2942,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.22"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
dependencies = [
"value-bag",
]
@ -3224,14 +3257,13 @@ checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "nextgraph"
version = "0.1.1-alpha"
version = "0.1.0-preview.6"
dependencies = [
"async-once-cell",
"async-std",
"async-trait",
"base64-url",
"futures",
"lazy_static",
"ng-client-ws",
"ng-net",
"ng-repo",
@ -3239,20 +3271,15 @@ dependencies = [
"ng-verifier",
"ng-wallet",
"once_cell",
"pdf-writer",
"qrcode",
"serde_bare",
"serde_bytes",
"serde_json",
"svg2pdf",
"web-time",
"whoami",
"zeroize",
]
[[package]]
name = "ng-app"
version = "0.1.1-alpha"
version = "0.1.0-preview.1"
dependencies = [
"async-std",
"nextgraph",
@ -3260,18 +3287,13 @@ dependencies = [
"ng-net",
"ng-repo",
"ng-wallet",
"oxrdf",
"serde",
"serde_bare",
"serde_bytes",
"serde_json",
"sys-locale",
"tauri",
"tauri-build",
"tauri-plugin-barcode-scanner",
"tauri-plugin-window",
"tauri-utils",
"zeroize",
]
[[package]]
@ -3290,7 +3312,7 @@ dependencies = [
[[package]]
name = "ng-broker"
version = "0.1.1-alpha"
version = "0.1.0-preview.6"
dependencies = [
"async-std",
"async-trait",
@ -3315,7 +3337,7 @@ dependencies = [
[[package]]
name = "ng-client-ws"
version = "0.1.1-alpha"
version = "0.1.0-preview.1"
dependencies = [
"async-std",
"async-trait",
@ -3334,7 +3356,7 @@ dependencies = [
[[package]]
name = "ng-net"
version = "0.1.1-alpha"
version = "0.1.0-preview.1"
dependencies = [
"async-recursion",
"async-std",
@ -3364,7 +3386,7 @@ dependencies = [
[[package]]
name = "ng-oxigraph"
version = "0.4.0-alpha.7-ngalpha"
version = "0.4.0-alpha.7-ngpreview6"
dependencies = [
"base64-url",
"codspeed-criterion-compat",
@ -3377,7 +3399,6 @@ dependencies = [
"libc",
"md-5",
"memchr",
"ng-repo",
"ng-rocksdb",
"oxilangtag",
"oxiri",
@ -3388,17 +3409,18 @@ dependencies = [
"serde",
"sha1",
"sha2 0.10.8",
"siphasher 0.3.10",
"siphasher 1.0.1",
"thiserror",
"zstd",
]
[[package]]
name = "ng-repo"
version = "0.1.1-alpha"
version = "0.1.0-preview.1"
dependencies = [
"base64-url",
"blake3",
"bloomfilter",
"chacha20",
"crypto_box",
"current_platform",
@ -3408,13 +3430,11 @@ dependencies = [
"futures",
"getrandom 0.2.10",
"gloo-timers",
"lazy_static",
"log",
"num_enum",
"once_cell",
"os_info",
"rand 0.7.3",
"sbbf-rs-safe",
"serde",
"serde_bare",
"serde_bytes",
@ -3429,8 +3449,8 @@ dependencies = [
[[package]]
name = "ng-rocksdb"
version = "0.21.0-ngpreview.6"
source = "git+https://git.nextgraph.org/NextGraph/rust-rocksdb.git?branch=master#75ee95166954e6ec67a229d5697b2319dd02efc6"
version = "0.21.0-ngpreview.4"
source = "git+https://git.nextgraph.org/NextGraph/rust-rocksdb.git?branch=master#f1ce2e345f3d729350a02bcffb47c526674edd85"
dependencies = [
"bindgen",
"bzip2-sys",
@ -3444,10 +3464,9 @@ dependencies = [
[[package]]
name = "ng-sdk-js"
version = "0.1.1-alpha"
version = "0.1.0-preview.1"
dependencies = [
"async-std",
"futures",
"getrandom 0.1.16",
"gloo-timers",
"js-sys",
@ -3463,6 +3482,7 @@ dependencies = [
"serde-wasm-bindgen",
"serde_bare",
"serde_bytes",
"serde_json",
"sys-locale",
"wasm-bindgen",
"wasm-bindgen-futures",
@ -3471,7 +3491,7 @@ dependencies = [
[[package]]
name = "ng-storage-rocksdb"
version = "0.1.1-alpha"
version = "0.1.0-preview.6"
dependencies = [
"ng-repo",
"ng-rocksdb",
@ -3499,37 +3519,34 @@ dependencies = [
[[package]]
name = "ng-verifier"
version = "0.1.1-alpha"
version = "0.1.0-preview.6"
dependencies = [
"async-std",
"async-trait",
"automerge",
"bloomfilter",
"either",
"futures",
"getrandom 0.2.10",
"lazy_static",
"ng-net",
"ng-oxigraph",
"ng-repo",
"ng-storage-rocksdb",
"rand 0.7.3",
"sbbf-rs-safe",
"serde",
"serde_bare",
"serde_bytes",
"serde_json",
"web-time",
"yrs",
]
[[package]]
name = "ng-wallet"
version = "0.1.1-alpha"
version = "0.1.0-preview.6"
dependencies = [
"aes-gcm-siv",
"argon2",
"async-std",
"base64-url",
"blake3",
"chacha20poly1305",
"crypto_box",
@ -3551,7 +3568,7 @@ dependencies = [
[[package]]
name = "ngaccount"
version = "0.1.1-alpha"
version = "0.1.0-preview.1"
dependencies = [
"anyhow",
"duration-str",
@ -3569,7 +3586,7 @@ dependencies = [
[[package]]
name = "ngcli"
version = "0.1.1-alpha"
version = "0.1.0-preview.1"
dependencies = [
"async-std",
"blake3",
@ -3578,7 +3595,6 @@ dependencies = [
"env_logger",
"getrandom 0.2.10",
"log",
"ng-async-tungstenite",
"ng-client-ws",
"ng-net",
"ng-repo",
@ -3589,7 +3605,7 @@ dependencies = [
[[package]]
name = "ngd"
version = "0.1.1-alpha"
version = "0.1.0-preview.1"
dependencies = [
"addr",
"async-std",
@ -3608,7 +3624,7 @@ dependencies = [
[[package]]
name = "ngone"
version = "0.1.1-alpha"
version = "0.1.0-preview.1"
dependencies = [
"base64-url",
"bytes",
@ -3767,15 +3783,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]]
name = "objc"
version = "0.2.7"
@ -3804,6 +3811,15 @@ dependencies = [
"objc",
]
[[package]]
name = "object"
version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.18.0"
@ -4024,18 +4040,6 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
[[package]]
name = "pdf-writer"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af6a7882fda7808481d43c51cadfc3ec934c6af72612a1fe6985ce329a2f0469"
dependencies = [
"bitflags 2.5.0",
"itoa 1.0.6",
"memchr",
"ryu",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
@ -4193,12 +4197,6 @@ dependencies = [
"siphasher 0.3.10",
]
[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]]
name = "pin-project"
version = "1.1.0"
@ -4438,12 +4436,6 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "qrcode"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec"
[[package]]
name = "quick-xml"
version = "0.28.2"
@ -4616,15 +4608,6 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_users"
version = "0.4.3"
@ -4707,12 +4690,6 @@ dependencies = [
"winreg 0.10.1",
]
[[package]]
name = "roxmltree"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]]
name = "rust-embed"
version = "6.7.0"
@ -4758,6 +4735,12 @@ dependencies = [
"num-traits",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustc-hash"
version = "1.1.0"
@ -4838,25 +4821,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "sbbf-rs"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5525db49c7719816ac719ea8ffd0d0b4586db1a3f5d3e7751593230dacc642fd"
dependencies = [
"cpufeatures",
"fastrange-rs",
]
[[package]]
name = "sbbf-rs-safe"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9902ffeb2cff3f8c072c60c7d526ac9560fc9a66fe1dfc3c240eba5e2151ba3c"
dependencies = [
"sbbf-rs",
]
[[package]]
name = "schannel"
version = "0.1.22"
@ -5175,15 +5139,6 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f"
[[package]]
name = "simplecss"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d"
dependencies = [
"log",
]
[[package]]
name = "siphasher"
version = "0.3.10"
@ -5195,6 +5150,9 @@ name = "siphasher"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
dependencies = [
"serde",
]
[[package]]
name = "sized-chunks"
@ -5313,15 +5271,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strict-num"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
dependencies = [
"float-cmp",
]
[[package]]
name = "string_cache"
version = "0.8.7"
@ -5360,29 +5309,6 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "svg2pdf"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e31565956eb1dc398c0d9776ee1d1bac4e34759af63dcbe0520df32313a5b53b"
dependencies = [
"log",
"miniz_oxide",
"once_cell",
"pdf-writer",
"usvg",
]
[[package]]
name = "svgtypes"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fae3064df9b89391c9a76a0425a69d124aee9c5c28455204709e72c39868a43c"
dependencies = [
"kurbo",
"siphasher 1.0.1",
]
[[package]]
name = "swift-rs"
version = "1.0.6"
@ -5416,6 +5342,18 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"unicode-xid",
]
[[package]]
name = "sys-locale"
version = "0.3.1"
@ -5637,20 +5575,6 @@ dependencies = [
"tauri-utils",
]
[[package]]
name = "tauri-plugin-barcode-scanner"
version = "2.0.0-alpha.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "058922dd9cafc89a865593c99507177c4cdbdad9d22a911ac41872ed7dbf0348"
dependencies = [
"log",
"serde",
"serde_json",
"tauri",
"tauri-build",
"thiserror",
]
[[package]]
name = "tauri-plugin-window"
version = "2.0.0-alpha.1"
@ -5816,9 +5740,11 @@ dependencies = [
[[package]]
name = "threshold_crypto"
version = "0.4.0"
source = "git+https://github.com/nextgraph-org/threshold_crypto.git?branch=master#b60552e4d42f67058455779eed476a76986b5478"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f708705bce37e765c37a95a8e0221a327c880d5a5a148d522552e8daa85787a"
dependencies = [
"byteorder",
"failure",
"ff",
"group",
"hex_fmt",
@ -5827,7 +5753,6 @@ dependencies = [
"rand 0.7.3",
"rand_chacha 0.2.2",
"serde",
"thiserror",
"tiny-keccak",
"zeroize",
]
@ -5862,10 +5787,7 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
dependencies = [
"deranged",
"itoa 1.0.6",
"js-sys",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
@ -5897,17 +5819,6 @@ dependencies = [
"crunchy",
]
[[package]]
name = "tiny-skia-path"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
dependencies = [
"arrayref",
"bytemuck",
"strict-num",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
@ -6132,6 +6043,15 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "treediff"
version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303"
dependencies = [
"serde_json",
]
[[package]]
name = "try-lock"
version = "0.2.4"
@ -6209,6 +6129,12 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-xid"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "unique_id"
version = "0.1.5"
@ -6242,28 +6168,6 @@ dependencies = [
"serde",
]
[[package]]
name = "usvg"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032"
dependencies = [
"base64 0.22.1",
"data-url",
"flate2",
"imagesize",
"kurbo",
"log",
"pico-args",
"roxmltree",
"simplecss",
"siphasher 1.0.1",
"strict-num",
"svgtypes",
"tiny-skia-path",
"xmlwriter",
]
[[package]]
name = "utf-8"
version = "0.7.6"
@ -6294,9 +6198,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "value-bag"
version = "1.9.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101"
checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e"
[[package]]
name = "vcpkg"
@ -6421,12 +6325,6 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
version = "0.2.87"
@ -6638,20 +6536,9 @@ dependencies = [
[[package]]
name = "weezl"
version = "0.1.8"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
[[package]]
name = "whoami"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9"
dependencies = [
"redox_syscall 0.4.1",
"wasite",
"web-sys",
]
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
[[package]]
name = "winapi"
@ -7112,12 +6999,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "xmlwriter"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]]
name = "xsalsa20poly1305"
version = "0.9.1"
@ -7133,9 +7014,9 @@ dependencies = [
[[package]]
name = "yrs"
version = "0.19.2"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8ca5126331b9a5ef5bb10f3f1c3d01b05f298d348c66f8fb15497d83ee73176"
checksum = "a4058d69bbbc97181d53d9d093a4b892001b84601f2fc4e27f48c8862bc8b369"
dependencies = [
"arc-swap",
"atomic_refcell",

@ -19,7 +19,7 @@ members = [
default-members = [ "nextgraph", "ngcli", "ngd" ]
[workspace.package]
version = "0.1.1-alpha"
version = "0.1.0-preview.1"
edition = "2021"
rust-version = "1.74.0"
license = "MIT/Apache-2.0"

187
DEV.md

@ -1,187 +0,0 @@
# Contributors or compilation guide
- [Install Rust](https://www.rust-lang.org/tools/install) minimum required MSRV 1.74.0
- [Install Nodejs](https://nodejs.org/en/download/)
- [Install LLVM](https://rust-lang.github.io/rust-bindgen/requirements.html)
On openbsd, for LLVM you need to choose llvm-17.
until this [PR](https://github.com/rustwasm/wasm-pack/pull/1271) is accepted, will have to install wasm-pack this way:
```
cargo install wasm-pack --git https://github.com/rustwasm/wasm-pack.git --rev c2b663f25abe50631a236d57a8c6d7fd806413b2
```
then :
create a file called `nextgraph/src/local_broker_dev_env.rs` with the content :
```
pub const PEER_ID: &str = "FtdzuDYGewfXWdoPuXIPb0wnd0SAg1WoA2B14S7jW3MA";
```
once your ngd server will run in your dev env, replace the above string with the actual PEER ID of your ngd server.
```
cargo install cargo-watch
// optionally, if you want a Rust REPL: cargo install evcxr_repl
git clone git@git.nextgraph.org:NextGraph/nextgraph-rs.git
// or if you don't have a git account: git clone https://git.nextgraph.org/NextGraph/nextgraph-rs.git
cd nextgraph-rs
cargo build
```
### Packages
The crates are organized as follow :
- [nextgraph](nextgraph/README.md) : Client library. Use this crate to embed NextGraph client in your Rust application
- [ngcli](ngcli/README.md) : CLI tool to manipulate the local documents and repos and administrate the server
- [ngd](ngd/README.md) : binary executable of the daemon (that can run a broker, verifier and/or Rust services)
- [ng-app](ng-app/README.md) : all the native apps, based on Tauri, and the official web app.
- [ng-sdk-js](ng-sdk-js/DEV.md) : contains the JS SDK, with example for: web app, react app, or node service.
- ng-repo : Repositories common library
- ng-net : Network common library
- ng-oxigraph : Fork of OxiGraph. contains our CRDT of RDF
- ng-verifier : Verifier library, that exposes the document API to the app
- ng-wallet : keeps the secret keys of all identities of the user in a safe wallet
- ng-broker : Core and Server Broker library
- ng-client-ws : Websocket client library
- ng-storage-rocksdb : RocksDB backed stores. see also dependency [repo here](https://git.nextgraph.org/NextGraph/rust-rocksdb)
- ngone : server for nextgraph.one. helps user bootstrap into the right app. Not useful to you. Published here for transparency
- ngaccount : server for nextgraph's Broker Service Provider account manager. Not useful to you. Published here for transparency
### Run
Build & run debug executables:
```
// runs the daemon
cargo run --bin ngd
// runs the client
cargo run --bin ngcli
```
For the apps, see the [README](ng-app/README.md)
### Test
Please test by following this order (as we need to generate some files locally)
```
cargo test --package nextgraph -r --lib -- local_broker::test::gen_wallet_for_test --show-output --nocapture
cargo test -r
cargo test --package nextgraph -r --lib -- local_broker::test::import_session_for_test_to_disk --show-output --nocapture --ignored
```
Test a single crate:
```
cargo test --package ng-repo --lib -- --show-output --nocapture
cargo test --package ng-wallet --lib -- --show-output --nocapture
cargo test --package ng-verifier --lib -- --show-output --nocapture
cargo test --package ng-sdk-js --lib -- --show-output --nocapture
cargo test --package ng-broker --lib -- --show-output --nocapture
cargo test --package ng-client-ws --lib -- --show-output --nocapture
```
Test WASM websocket
First you need to install the `chromedriver` that matches your version of Chrome
https://googlechromelabs.github.io/chrome-for-testing/
then:
```
cd ng-sdk-js
wasm-pack test --chrome --headless
```
Test Rust websocket
```
cargo test --package ng-client-ws --lib -- remote_ws::test::test_ws --show-output --nocapture
```
### Build release binaries
First you will need to have the production build of the frontend.
If you do not want to setup a whole development environment for the frontend, you can use the precompiled release of the frontend available in `dist-file.tar.gz` that you can download from the release page.
```
cd ng-app
tar -xzf dist-file.tar.gz
cd ..
```
Otherwise, build from source the single-file release of ng-app
```
npm install -g pnpm
cd ng-sdk-js
wasm-pack build --target bundler
cd ../ng-app
pnpm install
pnpm webfilebuild
cd ..
```
then build the ngd daemon
```
cargo build -r -p ngd
```
you can then find the binary `ngd` in `target/release`
The CLI tool can be obtained with :
```
cargo build -r -p ngcli
```
you can then use the binary `target/release/ngcli`
For usage, see the documentation [here](ngd/README.md).
For building the apps, see this [documentation](ng-app/README.md).
#### OpenBSD
On OpenBSD, a conflict between the installed LibreSSL library and the reqwest crate, needs a bit of attention.
Before compiling the daemon for OpenBSD, please comment out lines 41-42 of `ng-net/Cargo.toml`. This will be solved soon by using `resolver = "2"`.
```
#[target.'cfg(target_arch = "wasm32")'.dependencies]
#reqwest = { version = "0.11.18", features = ["json","native-tls-vendored"] }
```
to use the app on OpenBSD, you need to run the daemon locally.
```
ngd -l 14400 --save-key
```
then open chrome (previously installed with `doas pkg_add chrome`)
```
env ENABLE_WASM=1 chrome --enable-wasm --process-per-site --new-window --app=http://localhost:14400
```
### Generate documentation
Generate documentation for all packages without their dependencies:
```
cargo doc --no-deps
```
The generated documentation can be found in `target/doc/nextgraph`.
### Contributions license
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you shall be dual licensed as below, without any
additional terms or conditions.

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy

@ -30,23 +30,195 @@ Documentation can be found here [https://docs.nextgraph.org](https://docs.nextgr
And our community forum where you can ask questions is here [https://forum.nextgraph.org](https://forum.nextgraph.org)
[![Mastodon](https://img.shields.io/badge/-MASTODON-%232B90D9?style=for-the-badge&logo=mastodon&logoColor=white)](https://fosstodon.org/@nextgraph)
[![Mastodon](https://img.shields.io/badge/-MASTODON-%232B90D9?style=for-the-badge&logo=mastodon&logoColor=white)](https://mastodon.lescommuns.org/@nextgraph)
[![Twitter URL](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Ftwitter.com%2Fnextgraph)](https://twitter.com/nextgraph)
## How to use NextGraph App & Platform
## How to use NextGraph
NextGraph is in alpha release!
NextGraph is not ready yet. You can subscribe to [our newsletter](https://list.nextgraph.org/subscription/form) to get updates, and support us with a [donation](https://nextgraph.org/donate/).
You can try it online or by installing the apps. Please follow our [Getting started](https://docs.nextgraph.org/en/getting-started/) guide .
## For developers
You can also subscribe to [our newsletter](https://list.nextgraph.org/subscription/form) to get updates, and support us with a [donation](https://nextgraph.org/donate/).
Read our [getting started guide](https://docs.nextgraph.org/en/getting-started/).
## NextGraph is also a Framework for App developers
## For contributors
Read our [getting started guide for developers](https://docs.nextgraph.org/en/framework/getting-started/).
- [Install Rust](https://www.rust-lang.org/tools/install) minimum required MSRV 1.74.0
- [Install Nodejs](https://nodejs.org/en/download/)
- [Install LLVM](https://rust-lang.github.io/rust-bindgen/requirements.html)
## For contributors or self compilation
On openbsd, for LLVM you need to choose llvm-17.
See our [contributor's guide](DEV.md)
until this [PR](https://github.com/rustwasm/wasm-pack/pull/1271) is accepted, will have to install wasm-pack this way:
```
cargo install wasm-pack --git https://github.com/rustwasm/wasm-pack.git --rev c2b663f25abe50631a236d57a8c6d7fd806413b2
```
then :
```
cargo install cargo-watch
// optionally, if you want a Rust REPL: cargo install evcxr_repl
git clone git@git.nextgraph.org:NextGraph/nextgraph-rs.git
// or if you don't have a git account: git clone https://git.nextgraph.org/NextGraph/nextgraph-rs.git
cd nextgraph-rs
cargo build
```
### Packages
The crates are organized as follow :
- [nextgraph](nextgraph/README.md) : Client library. Use this crate to embed NextGraph client in your Rust application
- [ngcli](ngcli/README.md) : CLI tool to manipulate the local documents and repos and administrate the server
- [ngd](ngd/README.md) : binary executable of the daemon (that can run a broker, verifier and/or Rust services)
- [ng-app](ng-app/README.md) : all the native apps, based on Tauri, and the official web app.
- [ng-sdk-js](ng-sdk-js/DEV.md) : contains the JS SDK, with example for: web app, react app, or node service.
- ng-repo : Repositories common library
- ng-net : Network common library
- ng-verifier : Verifier library, that exposes the document API to the app
- ng-wallet : keeps the secret keys of all identities of the user in a safe wallet
- ng-broker : Core and Server Broker library
- ng-client-ws : Websocket client library
- ng-storage-rocksdb : RocksDB backed stores. see also dependency [repo here](https://git.nextgraph.org/NextGraph/rust-rocksdb)
- ngone : server for nextgraph.one. helps user bootstrap into the right app. Not useful to you. Published here for transparency
- ngaccount : server for nextgraph's Broker Service Provider account manager. Not useful to you. Published here for transparency
### Run
Build & run debug executables:
```
// runs the daemon
cargo run --bin ngd
// runs the client
cargo run --bin ngcli
```
For the apps, see the [README](ng-app/README.md)
### Test
Please test by following this order (as we need to generate some files locally)
```
cargo test --package nextgraph -r --lib -- local_broker::test::gen_wallet_for_test --show-output --nocapture
cargo test -r
cargo test --package nextgraph -r --lib -- local_broker::test::import_session_for_test_to_disk --show-output --nocapture --ignored
```
Test a single crate:
```
cargo test --package ng-repo --lib -- --show-output --nocapture
cargo test --package ng-wallet --lib -- --show-output --nocapture
cargo test --package ng-verifier --lib -- --show-output --nocapture
cargo test --package ng-sdk-js --lib -- --show-output --nocapture
cargo test --package ng-broker --lib -- --show-output --nocapture
cargo test --package ng-client-ws --lib -- --show-output --nocapture
```
Test WASM websocket
First you need to install the `chromedriver` that matches your version of Chrome
https://googlechromelabs.github.io/chrome-for-testing/
then:
```
cd ng-sdk-js
wasm-pack test --chrome --headless
```
Test Rust websocket
```
cargo test --package ng-client-ws --lib -- remote_ws::test::test_ws --show-output --nocapture
```
### Build release binaries
First you will need to have the production build of the frontend.
If you do not want to setup a whole development environment for the frontend, you can use the precompiled release of the frontend available in `dist-file.tar.gz` that you can download from the release page.
```
cd ng-app
tar -xzf dist-file.tar.gz
cd ..
```
Otherwise, build from source the single-file release of ng-app
```
npm install -g pnpm
cd ng-sdk-js
wasm-pack build --target bundler
cd ../ng-app
pnpm install
pnpm webfilebuild
cd ..
```
then build the ngd daemon
```
cargo build -r -p ngd
```
you can then find the binary `ngd` in `target/release`
The CLI tool can be obtained with :
```
cargo build -r -p ngcli
```
you can then use the binary `target/release/ngcli`
For usage, see the documentation [here](ngd/README.md).
For building the apps, see this [documentation](ng-app/README.md).
#### OpenBSD
On OpenBSD, a conflict between the installed LibreSSL library and the reqwest crate, needs a bit of attention.
Before compiling the daemon for OpenBSD, please comment out lines 41-42 of `ng-net/Cargo.toml`. This will be solved soon by using `resolver = "2"`.
```
#[target.'cfg(target_arch = "wasm32")'.dependencies]
#reqwest = { version = "0.11.18", features = ["json","native-tls-vendored"] }
```
to use the app on OpenBSD, you need to run the daemon locally.
```
ngd -l 14400 --save-key
```
then open chrome (previously installed with `doas pkg_add chrome`)
```
env ENABLE_WASM=1 chrome --enable-wasm --process-per-site --new-window --app=http://localhost:14400
```
### Generate documentation
Generate documentation for all packages without their dependencies:
```
cargo doc --no-deps
```
The generated documentation can be found in `target/doc/nextgraph`.
### Contributions license
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you shall be dual licensed as below, without any
additional terms or conditions.
## License

@ -1,40 +0,0 @@
# Release 0.1.1-alpha
_02 September 2024_
This release is not stable and should not be used for any productive work or to store personal documents. This release is meant as a **preview** of what NextGraph can do as of today and hints at its future potential.
**Please note: The binary format of the Documents or Wallet might change, that might result in a complete loss of data. We will not provide migration scripts as the APIs and formats aren't stable yet.**
If you previously installed any NextGraph app on your device, please uninstall it first, by following the normal uninstall procedure specific to your OS. If you have previously created a Wallet, it will not work with this new release. Please create a new one now.
## App
Please read the [Getting started](https://docs.nextgraph.org/en/getting-started) guide.
[changelog](CHANGELOG.md#app-0-1-1-alpha-2024-09-02)
## SDK
The SDK for is not documented yet.
[changelog](CHANGELOG.md#sdk-0-1-0-preview-6-2024-08-15)
## Broker
The `ngd` daemon is release with the basic features listed in `ngd --help`. More documentation will come soon
[changelog](CHANGELOG.md#broker-0-1-1-alpha-2024-09-02)
## CLI
The `ngcli` daemon is release with the basic features listed in `ngcli --help`. More documentation will come soon.
[changelog](CHANGELOG.md#cli-0-1-1-alpha-2024-09-02)
## Limitations of this release
- you cannot share documents with other users. Everything is ready for this internally, but there is still some wiring to do that will take some more time.
- the Rich text editors (both for normal Post/Article and in Markdown) do not let you insert images nor links to other documents.
- The webapp has some limitation for now when it is offline, because it doesn't have a UserStorage. it works differently than the native apps, as it has to replay all the commits at every load. This will stay like that for now, as the feature "Web UserStorage" based on IndexedDB will take some time to be coded.
- JSON-LD isn't ready yet as we need the "Context branch" feature in order to enter the list of ontologies each document is based on.

@ -1,2 +1 @@
tests
local_broker_dev_env_peer_id.rs

@ -2,7 +2,7 @@
name = "nextgraph"
description = "NextGraph client library. Nextgraph is a decentralized, secure and local-first web 3.0 ecosystem based on Semantic Web and CRDTs"
categories = ["asynchronous","text-editors","web-programming","development-tools","database-implementations"]
version = "0.1.1-alpha"
version = "0.1.0-preview.6"
edition.workspace = true
license.workspace = true
authors.workspace = true
@ -18,7 +18,6 @@ maintenance = { status = "actively-developed" }
[dependencies]
serde_bare = "0.5.0"
serde_json = "1.0"
serde_bytes = "0.11.7"
base64-url = "2.0.0"
once_cell = "1.17.1"
zeroize = { version = "1.7.0", features = ["zeroize_derive"] }
@ -26,20 +25,15 @@ futures = "0.3.24"
async-std = { version = "1.12.0", features = [ "attributes", "unstable" ] }
async-trait = "0.1.64"
async-once-cell = "0.5.3"
lazy_static = "1.4.0"
web-time = "0.2.0"
whoami = "1.5.1"
qrcode = { version = "0.14.1", default-features = false, features = ["svg"] }
svg2pdf = { version = "0.11.0", default-features = false }
pdf-writer = "0.10.0"
ng-repo = { path = "../ng-repo", version = "0.1.1-alpha" }
ng-net = { path = "../ng-net", version = "0.1.1-alpha" }
ng-wallet = { path = "../ng-wallet", version = "0.1.1-alpha" }
ng-client-ws = { path = "../ng-client-ws", version = "0.1.1-alpha" }
ng-verifier = { path = "../ng-verifier", version = "0.1.1-alpha" }
ng-repo = { path = "../ng-repo", version = "0.1.0-preview.1" }
ng-net = { path = "../ng-net", version = "0.1.0-preview.1" }
ng-wallet = { path = "../ng-wallet", version = "0.1.0-preview.5" }
ng-client-ws = { path = "../ng-client-ws", version = "0.1.0-preview.1" }
ng-verifier = { path = "../ng-verifier", version = "0.1.0-preview.5" }
[target.'cfg(all(not(target_family = "wasm"),not(docsrs)))'.dependencies]
ng-storage-rocksdb = { path = "../ng-storage-rocksdb", version = "0.1.1-alpha" }
ng-storage-rocksdb = { path = "../ng-storage-rocksdb", version = "0.1.0-preview.6" }
[[example]]
name = "in_memory"

@ -43,7 +43,7 @@ A tokio-based version (as a feature) might be available in the future.
```toml
[dependencies]
nextgraph = "0.1.1-alpha"
nextgraph = "0.1.0-preview.6"
async-std = "1.12.0"
```

@ -1,4 +1,4 @@
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -55,8 +55,6 @@ async fn main() -> std::io::Result<()> {
core_bootstrap: BootstrapContentV0::new_localhost(peer_id_of_server_broker),
core_registration: None,
additional_bootstrap: None,
pdf: false,
device_name: "test".to_string(),
})
.await?;

@ -1,4 +1,4 @@
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>

@ -1,4 +1,4 @@
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -63,8 +63,6 @@ async fn main() -> std::io::Result<()> {
core_bootstrap: BootstrapContentV0::new_localhost(peer_id_of_server_broker),
core_registration: None,
additional_bootstrap: None,
pdf: false,
device_name: "test".to_string(),
})
.await?;

@ -1,4 +1,4 @@
#![doc(html_logo_url = "https://nextgraph.org/nextgraph-logo-192.png")]
#![doc(html_logo_url = "https://file.nextgraph.org/download/1fd175bb6d7d832156bd5ad4abcdee7e")]
#![doc(issue_tracker_base_url = "https://git.nextgraph.org/NextGraph/nextgraph-rs/issues")]
#![doc(html_favicon_url = "https://nextgraph.org/favicon.svg")]
//! # NextGraph framework client library
@ -93,44 +93,8 @@ pub mod verifier {
pub mod protocol {
pub use ng_net::app_protocol::*;
}
pub use ng_verifier::prepare_app_response_for_js;
pub use ng_verifier::triples_ser_to_json_string;
}
pub mod wallet {
pub use ng_wallet::*;
}
pub fn get_device_name() -> String {
let mut list: Vec<String> = Vec::with_capacity(3);
#[cfg(not(target_arch = "wasm32"))]
if let Ok(realname) = whoami::fallible::realname() {
list.push(realname);
} else {
#[cfg(not(target_arch = "wasm32"))]
if let Ok(username) = whoami::fallible::username() {
list.push(username);
}
}
if let Ok(devicename) = whoami::fallible::devicename() {
list.push(devicename);
} else {
#[cfg(not(target_arch = "wasm32"))]
if let Ok(hostname) = whoami::fallible::hostname() {
list.push(hostname);
} else {
if let Ok(distro) = whoami::fallible::distro() {
list.push(distro);
}
}
}
#[cfg(target_arch = "wasm32")]
if let Ok(distro) = whoami::fallible::distro() {
list.push(distro);
}
list.join(" ")
}
#[cfg(debug_assertions)]
mod local_broker_dev_env;

File diff suppressed because it is too large Load Diff

@ -1 +0,0 @@
pub const PEER_ID: &str = "FtdzuDYGewfXWdoPuXIPb0wnd0SAg1WoA2B14S7jW3MA";

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -56,46 +56,10 @@
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NextGraph</title>
<style>
.splashing {
height: 95vh;
width:100%;
display: flex;
justify-content: center;
align-items: center;
}
.noshow {
display: none !important;
}
</style>
</head>
<body>
<div id="splash" class="splashing">
<div style="flex-direction: column;justify-content: center;color:#4972a5;width:100%;text-align:center;font-family: Inter, Avenir, Helvetica, Arial, sans-serif;">
<svg
style="width:100px;height:100px;margin: 0 auto 20px ;"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 225 225"
>
<g>
<circle
r="106.98013"
cy="112.90476"
cx="109.88096"
style="fill:#ffffff;stroke:none;stroke-width:0.268375" />
<path
d="M 98.343352,190.26108 C 80.403778,187.53354 65.011938,179.57839 52.608228,166.62327 38.602093,151.99448 31.178059,133.41381 31.178059,112.98841 c 0,-10.21889 1.700058,-19.44396 5.221234,-28.332119 4.28678,-10.820699 10.037295,-19.39063 18.535095,-27.62263 4.72982,-4.58187 6.60687,-6.10643 11.28099,-9.16256 11.89869,-7.779841 24.173884,-11.879991 38.095802,-12.724761 19.80437,-1.2017 39.11165,5.11306 54.60284,17.858751 1.50718,1.24006 2.72951,2.35934 2.71628,2.48729 -0.0132,0.12795 -3.85821,3.63443 -8.54442,7.79217 -4.6862,4.157729 -10.04724,8.96276 -11.91342,10.677819 -1.86617,1.715071 -3.54094,3.11831 -3.7217,3.11831 -0.18075,0 -1.39985,-0.745188 -2.70911,-1.655969 -7.53011,-5.23834 -15.99428,-7.82188 -25.62597,-7.82188 -12.731628,0 -23.249192,4.3379 -32.143882,13.257541 -6.39594,6.413868 -10.70387,14.555268 -12.50018,23.623578 -0.69099,3.48832 -0.68968,13.53072 0.002,17.00893 3.70508,18.62577 18.31886,33.10194 36.642322,36.29729 4.16439,0.72621 11.98099,0.71223 15.98975,-0.0286 14.03187,-2.59311 25.86047,-11.36806 32.26533,-23.93578 0.77379,-1.51834 1.26018,-2.88461 1.08086,-3.03616 -0.17934,-0.15156 -6.87448,-1.1779 -14.87813,-2.28078 -9.7795,-1.34758 -14.92353,-2.21379 -15.68471,-2.64117 -1.52067,-0.85379 -2.83611,-2.88806 -2.83611,-4.3859 0,-1.1732 2.02687,-15.86876 2.49085,-18.05962 0.29676,-1.40127 2.42559,-3.4934 3.84317,-3.77691 0.62227,-0.12445 8.82712,0.85555 18.28065,2.18348 9.43343,1.32511 17.26269,2.29453 17.39833,2.15427 0.13566,-0.14026 1.11808,-6.54833 2.18313,-14.24014 1.10778,-8.000208 2.20407,-14.60184 2.56177,-15.426229 0.34392,-0.792599 1.11019,-1.849131 1.70287,-2.34782 2.06321,-1.736079 3.1433,-1.785011 12.20439,-0.55291 9.63637,1.310309 10.70873,1.56224 12.28077,2.88503 1.64359,1.382979 2.2732,2.810909 2.25906,5.123309 -0.007,1.10173 -0.92172,8.29645 -2.03332,15.98826 -1.11158,7.69182 -1.97159,14.04091 -1.91113,14.1091 0.0605,0.0682 7.16644,1.11143 15.79109,2.31832 11.10566,1.55407 16.00827,2.38757 16.80223,2.85657 1.53015,0.90389 2.48023,2.64785 2.45017,4.49756 -0.0462,2.84349 -2.41252,18.12279 -2.97521,19.21089 -0.66164,1.27949 -2.60244,2.54696 -3.92109,2.56074 -0.51973,0.005 -7.87449,-0.95937 -16.34391,-2.144 -8.46944,-1.18464 -15.47588,-2.077 -15.56986,-1.98301 -0.094,0.094 -1.18792,7.34163 -2.43097,16.10589 -1.44004,10.15311 -2.49792,16.43621 -2.91556,17.31631 -0.72531,1.52848 -2.76261,3.06291 -4.53817,3.41802 -0.95688,0.19138 -10.90014,-0.92798 -13.59859,-1.53084 -0.5471,-0.12223 -1.89146,0.67252 -4.50941,2.66588 -11.2627,8.57562 -24.34195,13.90917 -38.35741,15.64164 -4.40038,0.54395 -15.72658,0.43298 -19.853658,-0.19451 z"
style="fill:#4972a5;fill-opacity:1;stroke:#4972a5;stroke-width:0.377976;stroke-opacity:1" />
</g>
</svg>
<div>&nbsp;&nbsp;&nbsp;Loading ...</div>
</div>
</div>
<div id="app" class="noshow"></div>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script>
window.supported = true;
</script>
</body>
</html>

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -56,94 +56,10 @@
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NextGraph</title>
<meta property="og:title" content="NextGraph App - The New Internet Platform" />
<meta property="og:image" content="https://nextgraph.org/card.png"/>
<meta name="twitter:image" content="https://nextgraph.org/card-twitter.png"/>
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:description" content="Decentralized, encrypted and local-first platform and framework, towards the better internet we all deserve! Features a social network, shared documents, productivity tools, an app store, and more! You can use NextGraph for free, and it works online and offline. With NextGraph you own your data and software, having your privacy respected, while enjoying high-quality apps for your daily use. Try it now! Developers can build new web3.0 local-first apps with our open source framework, based on open standards, with CRDTs, E2EE, Semantic Web, RDF, SPARQL, JSON, Markdown, Svelte, React, JavaScript, Rust, etc... ActivityPub and Solid compatible." />
<meta name="description" content="Decentralized, encrypted and local-first platform and framework, towards the better internet we all deserve! Features a social network, shared documents, productivity tools, an app store, and more! You can use NextGraph for free, and it works online and offline. With NextGraph you own your data and software, having your privacy respected, while enjoying high-quality apps for your daily use. Try it now! Developers can build new web3.0 local-first apps with our open source framework, based on open standards, with CRDTs, E2EE, Semantic Web, RDF, SPARQL, JSON, Markdown, Svelte, React, JavaScript, Rust, etc... ActivityPub and Solid compatible.">
</head>
<style>
.splashing {
height: 95vh;
width:100%;
display: flex;
justify-content: center;
align-items: center;
}
.noshow {
display: none !important;
}
.error-no-wasm-hidden {
display:none;
}
</style>
<body>
<div id="splash" class="splashing">
<div style="flex-direction: column;justify-content: center;color:#4972a5;width:100%;text-align:center;font-family: Inter, Avenir, Helvetica, Arial, sans-serif;">
<svg
style="width:100px;height:100px;margin: 0 auto 20px ;display:flex;"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 225 225"
>
<g>
<circle
r="106.98013"
cy="112.90476"
cx="109.88096"
style="fill:#ffffff;stroke:none;stroke-width:0.268375" />
<path
d="M 98.343352,190.26108 C 80.403778,187.53354 65.011938,179.57839 52.608228,166.62327 38.602093,151.99448 31.178059,133.41381 31.178059,112.98841 c 0,-10.21889 1.700058,-19.44396 5.221234,-28.332119 4.28678,-10.820699 10.037295,-19.39063 18.535095,-27.62263 4.72982,-4.58187 6.60687,-6.10643 11.28099,-9.16256 11.89869,-7.779841 24.173884,-11.879991 38.095802,-12.724761 19.80437,-1.2017 39.11165,5.11306 54.60284,17.858751 1.50718,1.24006 2.72951,2.35934 2.71628,2.48729 -0.0132,0.12795 -3.85821,3.63443 -8.54442,7.79217 -4.6862,4.157729 -10.04724,8.96276 -11.91342,10.677819 -1.86617,1.715071 -3.54094,3.11831 -3.7217,3.11831 -0.18075,0 -1.39985,-0.745188 -2.70911,-1.655969 -7.53011,-5.23834 -15.99428,-7.82188 -25.62597,-7.82188 -12.731628,0 -23.249192,4.3379 -32.143882,13.257541 -6.39594,6.413868 -10.70387,14.555268 -12.50018,23.623578 -0.69099,3.48832 -0.68968,13.53072 0.002,17.00893 3.70508,18.62577 18.31886,33.10194 36.642322,36.29729 4.16439,0.72621 11.98099,0.71223 15.98975,-0.0286 14.03187,-2.59311 25.86047,-11.36806 32.26533,-23.93578 0.77379,-1.51834 1.26018,-2.88461 1.08086,-3.03616 -0.17934,-0.15156 -6.87448,-1.1779 -14.87813,-2.28078 -9.7795,-1.34758 -14.92353,-2.21379 -15.68471,-2.64117 -1.52067,-0.85379 -2.83611,-2.88806 -2.83611,-4.3859 0,-1.1732 2.02687,-15.86876 2.49085,-18.05962 0.29676,-1.40127 2.42559,-3.4934 3.84317,-3.77691 0.62227,-0.12445 8.82712,0.85555 18.28065,2.18348 9.43343,1.32511 17.26269,2.29453 17.39833,2.15427 0.13566,-0.14026 1.11808,-6.54833 2.18313,-14.24014 1.10778,-8.000208 2.20407,-14.60184 2.56177,-15.426229 0.34392,-0.792599 1.11019,-1.849131 1.70287,-2.34782 2.06321,-1.736079 3.1433,-1.785011 12.20439,-0.55291 9.63637,1.310309 10.70873,1.56224 12.28077,2.88503 1.64359,1.382979 2.2732,2.810909 2.25906,5.123309 -0.007,1.10173 -0.92172,8.29645 -2.03332,15.98826 -1.11158,7.69182 -1.97159,14.04091 -1.91113,14.1091 0.0605,0.0682 7.16644,1.11143 15.79109,2.31832 11.10566,1.55407 16.00827,2.38757 16.80223,2.85657 1.53015,0.90389 2.48023,2.64785 2.45017,4.49756 -0.0462,2.84349 -2.41252,18.12279 -2.97521,19.21089 -0.66164,1.27949 -2.60244,2.54696 -3.92109,2.56074 -0.51973,0.005 -7.87449,-0.95937 -16.34391,-2.144 -8.46944,-1.18464 -15.47588,-2.077 -15.56986,-1.98301 -0.094,0.094 -1.18792,7.34163 -2.43097,16.10589 -1.44004,10.15311 -2.49792,16.43621 -2.91556,17.31631 -0.72531,1.52848 -2.76261,3.06291 -4.53817,3.41802 -0.95688,0.19138 -10.90014,-0.92798 -13.59859,-1.53084 -0.5471,-0.12223 -1.89146,0.67252 -4.50941,2.66588 -11.2627,8.57562 -24.34195,13.90917 -38.35741,15.64164 -4.40038,0.54395 -15.72658,0.43298 -19.853658,-0.19451 z"
style="fill:#4972a5;fill-opacity:1;stroke:#4972a5;stroke-width:0.377976;stroke-opacity:1" />
</g>
</svg>
<div class="noshow" id="app-loading">&nbsp;&nbsp;&nbsp;Loading ...</div>
<div id="error-no-wasm" class="error-no-wasm-hidden">
Your browser is too old and does not support NextGraph. <br/>Please upgrade to a newer version of this browser,<br/> try with another browser,<br/> <br/>or <a href="https://nextgraph.org/download">install our native apps on <br/>
Linux, macOS, Windows desktops and laptops,<br/> and iOS, Android mobiles.</a><br/><br/>If you are using jshelter or another javascript protection mechanism, please deactivate it as we need access to the WebWorker facility of your browser.
</div>
<noscript style="display:grid;">
NextGraph cannot load as Javascript is deactivated.<br/>
You can use the CLI ngcli to access the Documents in the terminal<br/>
Or use the native apps for Linux, macOS, Windows, Android, iOS<br/>
Or setup an SSR static generator like Astro for read-only access.
</noscript>
</div>
</div>
<div id="app" class="noshow">
</div>
<script>
const supported = (() => {
if (RegExp().hasIndices === undefined) {console.log("no RegExp().hasIndices");return false;}
try {
if (Worker === undefined) {console.log("no Worker");return false;}
new Worker(URL.createObjectURL(new Blob([';'], {type: 'application/javascript'})));
if (typeof WebAssembly === "object"
&& typeof WebAssembly.instantiate === "function") {
const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
if (module instanceof WebAssembly.Module)
return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
else {
console.log("no WebAssembly module");
}
}
} catch (e) {
{console.log(e);return false;}
}
console.log("no WebAssembly");
return false;
})();
if (!supported ) {
window.document.getElementById("error-no-wasm").className="";
}
window.supported = supported;
if (supported) window.document.getElementById("app-loading").className="";
</script>
<!-- # INSERT SCRIPT HERE -->
<div id="app"></div>
<script type="module" src="/src/main-web.ts"></script>
</body>
</html>

@ -1,92 +1,32 @@
{
"name": "ng-app",
"private": true,
"version": "0.1.1-alpha",
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "shx cp index-native.html index.html && vite",
"webdev": "shx cp index-web.html index.html && cross-env NG_APP_WEB=1 TAURI_DEBUG=1 vite",
"webbuild": "shx cp index-web.html index.html && cross-env NG_APP_WEB=1 vite build && shx rm -f ./dist-web/assets/*.svg && tar --exclude .DS_Store -zcvf dist-web.tar.gz dist-web",
"webfilebuild": "shx cp index-web.html index.html && cross-env NG_APP_WEB=1 NG_APP_FILE=1 vite build && node prepare-app-file.cjs && shx rm -rf ./dist-file/assets && tar --exclude .DS_Store -zcvf dist-file.tar.gz dist-file",
"webbuild": "shx cp index-web.html index.html && cross-env NG_APP_WEB=1 vite build && rm -f ./dist-web/assets/*.svg && tar --exclude .DS_Store -zcvf dist-web.tar.gz dist-web",
"webfilebuild": "shx cp index-web.html index.html && cross-env NG_APP_WEB=1 NG_APP_FILE=1 vite build && node prepare-app-file.cjs && rm -rf ./dist-file/assets && tar --exclude .DS_Store -zcvf dist-file.tar.gz dist-file",
"webfilebuilddebug": "shx cp index-web.html index.html && cross-env NG_APP_WEB=1 NG_APP_FILE=1 TAURI_DEBUG=1 vite build -m debug",
"build": "shx cp index-native.html index.html && vite build && shx rm -f ./dist/assets/*.svg",
"buildfile": "shx cp index-native.html index.html && cross-env NG_APP_FILE=1 vite build && shx rm -rf ./dist/assets && tar --exclude .DS_Store -zcvf dist.tar.gz dist",
"buildfile": "shx cp index-native.html index.html && cross-env NG_APP_FILE=1 vite build && rm -rf ./dist/assets && tar --exclude .DS_Store -zcvf dist.tar.gz dist",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json",
"tauri": "tauri"
},
"dependencies": {
"@automerge/automerge": "^2.2.8",
"@codemirror/autocomplete": "^6.17.0",
"@codemirror/commands": "^6.6.0",
"@codemirror/lang-css": "^6.0.1",
"@codemirror/lang-html": "^6.2.0",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-rust": "^6.0.1",
"@codemirror/language": "^6.10.2",
"@codemirror/legacy-modes": "^6.4.0",
"@codemirror/lint": "^6.8.1",
"@codemirror/search": "^6.5.6",
"@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.29.1",
"@lezer/common": "^1.0.0",
"@lezer/highlight": "^1.0.0",
"@lezer/javascript": "^1.2.0",
"@lezer/lr": "^1.0.0",
"@milkdown-lab/plugin-split-editing": "^1.3.1",
"@milkdown/core": "^7.4.0",
"@milkdown/ctx": "^7.2.0",
"@milkdown/plugin-collab": "^7.4.0",
"@milkdown/plugin-emoji": "^7.4.0",
"@milkdown/plugin-indent": "^7.4.0",
"@milkdown/plugin-math": "^7.4.0",
"@milkdown/plugin-prism": "^7.4.0",
"@milkdown/plugin-slash": "^7.4.0",
"@milkdown/preset-commonmark": "^7.4.0",
"@milkdown/preset-gfm": "^7.4.0",
"@milkdown/prose": "^7.2.0",
"@milkdown/theme-nord": "^7.4.0",
"@milkdown/transformer": "^7.2.0",
"@milkdown/utils": "^7.4.0",
"@popperjs/core": "^2.11.8",
"@replit/codemirror-lang-svelte": "^6.0.0",
"@sindresorhus/is": "4.6.0",
"@tailwindcss/typography": "^0.5.13",
"@tauri-apps/api": "2.0.0-alpha.8",
"@tauri-apps/plugin-barcode-scanner": "2.0.0-alpha.0",
"@tauri-apps/plugin-window": "2.0.0-alpha.1",
"async-proxy": "^0.4.1",
"char-regex": "1.0.2",
"classnames": "^2.3.2",
"codemirror": "^6.0.1",
"debug": "^4.3.6",
"emojilib": "2.4.0",
"extend": "3.0.2",
"flowbite": "^1.6.5",
"flowbite-svelte": "^0.43.3",
"html5-qrcode": "^2.3.8",
"immutable-json-patch": "^6.0.1",
"katex": "^0.16.11",
"lodash.debounce": "4.0.8",
"ng-sdk-js": "workspace:^0.1.1-alpha",
"prism-themes": "^1.9.0",
"prosemirror-model": "^1.7.1",
"prosemirror-state": "^1.2.3",
"prosemirror-svelte": "^0.2.4",
"prosemirror-view": "^1.9.10",
"skin-tone": "2.0.0",
"style-mod": "^4.1.2",
"svelte-codemirror-editor": "^1.4.0",
"ng-sdk-js": "workspace:^0.1.0-preview.1",
"svelte-i18n": "^4.0.0",
"svelte-inview": "^4.0.2",
"svelte-jsoneditor": "^0.23.8",
"svelte-spa-router": "^3.3.0",
"vite-plugin-top-level-await": "^1.3.1",
"xml-beautifier": "^0.5.0",
"y-codemirror.next": "^0.3.5",
"y-prosemirror": "^1.2.10",
"y-protocols": "^1.0.1",
"yjs": "^13.6.18"
"vite-plugin-top-level-await": "^1.3.1"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.0.0",
@ -96,18 +36,14 @@
"autoprefixer": "^10.4.14",
"cross-env": "^7.0.3",
"dayjs": "^1.11.10",
"highlight.js": "^11.10.0",
"internal-ip": "^7.0.0",
"node-gzip": "^1.1.2",
"postcss": "^8.4.23",
"postcss-load-config": "^4.0.1",
"prettier": "^3.3.2",
"prettier-plugin-svelte": "^3.2.5",
"shx": "^0.3.4",
"svelte": "^3.54.0",
"svelte-check": "^3.0.0",
"svelte-heros-v2": "^0.10.12",
"svelte-highlight": "^7.7.0",
"svelte-preprocess": "^5.0.3",
"svelte-time": "^0.8.0",
"tailwindcss": "^3.3.1",

@ -21,21 +21,17 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2.0.0-alpha.8", features = [] }
# tauri-macros = { version = "=2.0.0-alpha.6" }
# tauri-codegen = { version = "=2.0.0-alpha.6" }
tauri-utils = { version = "=2.0.0-alpha.7" }
# tauri-utils = { version = "=2.0.0-alpha.6" }
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_bare = "0.5.0"
serde_json = "1.0"
serde_bytes = "0.11.7"
async-std = { version = "1.12.0", features = ["attributes", "unstable"] }
sys-locale = { version = "0.3.1" }
zeroize = { version = "1.7.0", features = ["zeroize_derive"] }
ng-async-tungstenite = { git = "https://git.nextgraph.org/NextGraph/async-tungstenite.git", branch = "nextgraph", features = ["async-std-runtime", "async-native-tls"] }
tauri = { version = "2.0.0-alpha.14", features = [] }
# add the "devtools" feature if devtools in the production build should be activated
tauri-plugin-window = "2.0.0-alpha.1"
tauri-plugin-barcode-scanner = "=2.0.0-alpha.0"
# tauri-plugin-window = { git = "https://git.nextgraph.org/NextGraph/plugins-workspace.git", branch="window-alpha.1-nextgraph" }
# tauri = { git = "https://git.nextgraph.org/NextGraph/tauri.git", branch="alpha.11-nextgraph", features = ["no-ipc-custom-protocol"] }
# tauri = { git = "https://github.com/simonhyll/tauri.git", branch="fix/ipc-mixup", features = [] }
@ -43,7 +39,6 @@ ng-repo = { path = "../../ng-repo" }
ng-net = { path = "../../ng-net" }
ng-wallet = { path = "../../ng-wallet" }
nextgraph = { path = "../../nextgraph" }
oxrdf = { git = "https://git.nextgraph.org/NextGraph/oxigraph.git", branch="main", features = ["rdf-star", "oxsdatatypes"] }
[features]
# this feature is used for production builds or when `devPath` points to the filesystem

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<application
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"

@ -1,4 +1,4 @@
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -11,14 +11,12 @@ use std::collections::HashMap;
use std::fs::write;
use async_std::stream::StreamExt;
use oxrdf::Triple;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use sys_locale::get_locales;
use tauri::scope::ipc::RemoteDomainAccessScope;
use tauri::utils::config::WindowConfig;
use tauri::{path::BaseDirectory, App, Manager};
use zeroize::Zeroize;
use ng_repo::errors::NgError;
use ng_repo::log::*;
@ -113,12 +111,12 @@ async fn wallet_open_with_pazzle(
#[tauri::command(rename_all = "snake_case")]
async fn wallet_open_with_mnemonic(
wallet: Wallet,
mnemonic: [u16; 12],
mnemonic: Vec<u16>,
pin: [u8; 4],
_app: tauri::AppHandle,
) -> Result<SensitiveWallet, String> {
let wallet =
ng_wallet::open_wallet_with_mnemonic(&wallet, mnemonic, pin).map_err(|e| e.to_string())?;
let wallet = nextgraph::local_broker::wallet_open_with_mnemonic(&wallet, mnemonic, pin)
.map_err(|e| e.to_string())?;
Ok(wallet)
}
@ -129,8 +127,7 @@ async fn wallet_open_with_mnemonic_words(
pin: [u8; 4],
_app: tauri::AppHandle,
) -> Result<SensitiveWallet, String> {
let wallet =
nextgraph::local_broker::wallet_open_with_mnemonic_words(&wallet, &mnemonic_words, pin)
let wallet = nextgraph::local_broker::wallet_open_with_mnemonic_words(&wallet, &mnemonic_words, pin)
.map_err(|e| e.to_string())?;
Ok(wallet)
}
@ -161,7 +158,6 @@ async fn wallet_create(
//log_debug!("wallet_create from rust {:?}", params);
params.result_with_wallet_file = !params.local_save;
let local_save = params.local_save;
let pdf = params.pdf;
let mut cwr = nextgraph::local_broker::wallet_create_v0(params)
.await
.map_err(|e| e.to_string())?;
@ -175,22 +171,8 @@ async fn wallet_create(
)
.unwrap();
let _r = write(path, &cwr.wallet_file);
cwr.wallet_file.zeroize();
cwr.wallet_file = vec![];
}
if pdf {
// save pdf file to Downloads folder
let path = app
.path()
.resolve(
format!("wallet-{}.pdf", cwr.wallet_name),
BaseDirectory::Download,
)
.unwrap();
let _r = write(path, &cwr.pdf_file);
cwr.pdf_file.zeroize();
cwr.pdf_file = vec![];
}
Ok(cwr)
}
@ -223,55 +205,6 @@ async fn wallet_import(
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_export_rendezvous(
session_id: u64,
code: String,
_app: tauri::AppHandle,
) -> Result<(), String> {
nextgraph::local_broker::wallet_export_rendezvous(session_id, code)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_export_get_qrcode(
session_id: u64,
size: u32,
_app: tauri::AppHandle,
) -> Result<String, String> {
nextgraph::local_broker::wallet_export_get_qrcode(session_id, size)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_export_get_textcode(
session_id: u64,
_app: tauri::AppHandle,
) -> Result<String, String> {
nextgraph::local_broker::wallet_export_get_textcode(session_id)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_import_rendezvous(
size: u32,
_app: tauri::AppHandle,
) -> Result<(String, String), String> {
nextgraph::local_broker::wallet_import_rendezvous(size)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_import_from_code(code: String, _app: tauri::AppHandle) -> Result<Wallet, String> {
nextgraph::local_broker::wallet_import_from_code(code)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn get_wallets(
app: tauri::AppHandle,
@ -354,25 +287,6 @@ async fn decode_invitation(invite: String) -> Option<Invitation> {
decode_invitation_string(invite)
}
#[tauri::command(rename_all = "snake_case")]
async fn file_get(
session_id: u64,
stream_id: &str,
reference: BlockRef,
branch_nuri: String,
app: tauri::AppHandle,
) -> Result<(), String> {
let branch_nuri =
NuriV0::new_from(&branch_nuri).map_err(|e| format!("branch_nuri: {}", e.to_string()))?;
let mut nuri = NuriV0::new_from_obj_ref(&reference);
nuri.copy_target_from(&branch_nuri);
let mut request = AppRequest::new(AppRequestCommandV0::FileGet, nuri, None);
request.set_session_id(session_id);
app_request_stream(request, stream_id, app).await
}
#[tauri::command(rename_all = "snake_case")]
async fn app_request_stream(
request: AppRequest,
@ -400,7 +314,6 @@ async fn app_request_stream(
main_window: tauri::Window,
) -> ResultSend<()> {
while let Some(app_response) = reader.next().await {
let app_response = nextgraph::verifier::prepare_app_response_for_js(app_response)?;
main_window.emit(&stream_id, app_response).unwrap();
}
@ -417,99 +330,6 @@ async fn app_request_stream(
Ok(())
}
#[tauri::command(rename_all = "snake_case")]
async fn discrete_update(
session_id: u64,
update: serde_bytes::ByteBuf,
heads: Vec<String>,
crdt: String,
nuri: String,
) -> Result<(), String> {
let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?;
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_update(),
nuri,
payload: Some(
AppRequestPayload::new_discrete_update(heads, crdt, update.into_vec())
.map_err(|e| format!("Deserialization error of heads: {e}"))?,
),
session_id,
});
let res = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
if let AppResponse::V0(AppResponseV0::Error(e)) = res {
Err(e)
} else {
Ok(())
}
}
#[tauri::command(rename_all = "snake_case")]
async fn file_save_to_downloads(
session_id: u64,
reference: ObjectRef,
filename: String,
branch_nuri: String,
app: tauri::AppHandle,
) -> Result<(), String> {
let branch_nuri =
NuriV0::new_from(&branch_nuri).map_err(|e| format!("branch_nuri: {}", e.to_string()))?;
let mut nuri = NuriV0::new_from_obj_ref(&reference);
nuri.copy_target_from(&branch_nuri);
let mut request = AppRequest::new(AppRequestCommandV0::FileGet, nuri, None);
request.set_session_id(session_id);
let (mut reader, _cancel) = nextgraph::local_broker::app_request_stream(request)
.await
.map_err(|e| e.to_string())?;
let mut file_vec: Vec<u8> = vec![];
while let Some(app_response) = reader.next().await {
match app_response {
AppResponse::V0(AppResponseV0::FileMeta(filemeta)) => {
file_vec = Vec::with_capacity(filemeta.size as usize);
}
AppResponse::V0(AppResponseV0::FileBinary(mut bin)) => {
if !bin.is_empty() {
file_vec.append(&mut bin);
}
}
AppResponse::V0(AppResponseV0::EndOfStream) => break,
_ => return Err("invalid response".to_string()),
}
}
let mut i: usize = 0;
loop {
let dest_filename = if i == 0 {
filename.clone()
} else {
filename
.rsplit_once(".")
.map(|(l, r)| format!("{l} ({}).{r}", i.to_string()))
.or_else(|| Some(format!("{filename} ({})", i.to_string())))
.unwrap()
};
let path = app
.path()
.resolve(dest_filename, BaseDirectory::Download)
.unwrap();
if path.exists() {
i = i + 1;
} else {
write(path, &file_vec).map_err(|e| e.to_string())?;
break;
}
}
Ok(())
}
#[tauri::command(rename_all = "snake_case")]
async fn doc_fetch_private_subscribe() -> Result<AppRequest, String> {
let request = AppRequest::new(
@ -521,166 +341,17 @@ async fn doc_fetch_private_subscribe() -> Result<AppRequest, String> {
}
#[tauri::command(rename_all = "snake_case")]
async fn doc_fetch_repo_subscribe(repo_o: String) -> Result<AppRequest, String> {
async fn doc_fetch_repo_subscribe(repo_id: String) -> Result<AppRequest, String> {
let request = AppRequest::new(
AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)),
NuriV0::new_from(&repo_o).map_err(|e| e.to_string())?,
NuriV0::new_repo_target_from_string(repo_id).map_err(|e| e.to_string())?,
None,
);
Ok(request)
}
#[tauri::command(rename_all = "snake_case")]
async fn branch_history(session_id: u64, nuri: String) -> Result<AppHistoryJs, String> {
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_history(),
nuri: NuriV0::new_from(&nuri).map_err(|e| e.to_string())?,
payload: None,
session_id,
});
let res = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
let AppResponse::V0(res) = res;
//log_debug!("{:?}", res);
match res {
AppResponseV0::History(s) => Ok(s.to_js()),
_ => Err("invalid response".to_string()),
}
}
#[tauri::command(rename_all = "snake_case")]
async fn update_header(
session_id: u64,
nuri: String,
title: Option<String>,
about: Option<String>,
) -> Result<(), String> {
let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?;
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_header(),
nuri,
payload: Some(AppRequestPayload::new_header(title, about)),
session_id,
});
let res = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
if let AppResponse::V0(AppResponseV0::Error(e)) = res {
Err(e)
} else {
Ok(())
}
}
#[tauri::command(rename_all = "snake_case")]
async fn fetch_header(session_id: u64, nuri: String) -> Result<AppHeader, String> {
let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?;
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_fetch_header(),
nuri,
payload: None,
session_id,
});
let res = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
match res {
AppResponse::V0(AppResponseV0::Error(e)) => Err(e),
AppResponse::V0(AppResponseV0::Header(h)) => Ok(h),
_ => Err("invalid response".to_string()),
}
}
#[tauri::command(rename_all = "snake_case")]
async fn sparql_update(
session_id: u64,
sparql: String,
nuri: Option<String>,
) -> Result<(), String> {
let (nuri, base) = if let Some(n) = nuri {
let nuri = NuriV0::new_from(&n).map_err(|e| e.to_string())?;
let b = nuri.repo();
(nuri, Some(b))
} else {
(NuriV0::new_private_store_target(), None)
};
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_write_query(),
nuri,
payload: Some(AppRequestPayload::new_sparql_query(sparql, base)),
session_id,
});
let res = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
if let AppResponse::V0(AppResponseV0::Error(e)) = res {
Err(e)
} else {
Ok(())
}
}
#[tauri::command(rename_all = "snake_case")]
async fn sparql_query(
session_id: u64,
sparql: String,
base: Option<String>,
nuri: Option<String>,
) -> Result<Value, String> {
let nuri = if nuri.is_some() {
NuriV0::new_from(&nuri.unwrap()).map_err(|e| e.to_string())?
} else {
NuriV0::new_entire_user_site()
};
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_read_query(),
nuri,
payload: Some(AppRequestPayload::new_sparql_query(sparql, base)),
session_id,
});
let response = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
let AppResponse::V0(res) = response;
match res {
AppResponseV0::False => return Ok(Value::Bool(false)),
AppResponseV0::True => return Ok(Value::Bool(true)),
AppResponseV0::Graph(graph) => {
let triples: Vec<Triple> = serde_bare::from_slice(&graph)
.map_err(|_| "Deserialization error of graph".to_string())?;
Ok(Value::Array(
triples
.into_iter()
.map(|t| Value::String(t.to_string()))
.collect(),
))
}
AppResponseV0::QueryResult(buf) => {
let string = String::from_utf8(buf)
.map_err(|_| "Deserialization error of JSON QueryResult String".to_string())?;
Ok(serde_json::from_str(&string)
.map_err(|_| "Parsing error of JSON QueryResult String".to_string())?)
}
AppResponseV0::Error(e) => Err(e.to_string().into()),
_ => Err("invalid AppResponse".to_string().into()),
}
}
#[tauri::command(rename_all = "snake_case")]
async fn app_request(request: AppRequest) -> Result<AppResponse, String> {
async fn app_request(request: AppRequest, _app: tauri::AppHandle) -> Result<AppResponse, String> {
//log_debug!("app request {:?}", request);
nextgraph::local_broker::app_request(request)
@ -688,164 +359,19 @@ async fn app_request(request: AppRequest) -> Result<AppResponse, String> {
.map_err(|e| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn signature_status(
session_id: u64,
nuri: Option<String>,
) -> Result<Vec<(String, Option<String>, bool)>, String> {
let nuri = if nuri.is_some() {
NuriV0::new_from(&nuri.unwrap()).map_err(|e| e.to_string())?
} else {
NuriV0::new_private_store_target()
};
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_signature_status(),
nuri,
payload: None,
session_id,
});
let res = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
let AppResponse::V0(res) = res;
//log_debug!("{:?}", res);
match res {
AppResponseV0::SignatureStatus(s) => Ok(s),
_ => Err("invalid response".to_string()),
}
}
#[tauri::command(rename_all = "snake_case")]
async fn signed_snapshot_request(session_id: u64, nuri: Option<String>) -> Result<bool, String> {
let nuri = if nuri.is_some() {
NuriV0::new_from(&nuri.unwrap()).map_err(|e| e.to_string())?
} else {
NuriV0::new_private_store_target()
};
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_signed_snapshot_request(),
nuri,
payload: None,
session_id,
});
let res = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
let AppResponse::V0(res) = res;
//log_debug!("{:?}", res);
match res {
AppResponseV0::True => Ok(true),
AppResponseV0::False => Ok(false),
AppResponseV0::Error(e) => Err(e),
_ => Err("invalid response".to_string()),
}
}
#[tauri::command(rename_all = "snake_case")]
async fn signature_request(session_id: u64, nuri: Option<String>) -> Result<bool, String> {
let nuri = if nuri.is_some() {
NuriV0::new_from(&nuri.unwrap()).map_err(|e| e.to_string())?
} else {
NuriV0::new_private_store_target()
};
let request = AppRequest::V0(AppRequestV0 {
command: AppRequestCommandV0::new_signature_request(),
nuri,
payload: None,
session_id,
});
let res = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
let AppResponse::V0(res) = res;
//log_debug!("{:?}", res);
match res {
AppResponseV0::True => Ok(true),
AppResponseV0::False => Ok(false),
AppResponseV0::Error(e) => Err(e),
_ => Err("invalid response".to_string()),
}
}
#[tauri::command(rename_all = "snake_case")]
async fn doc_create(
session_id: u64,
crdt: String,
class_name: String,
store_repo: StoreRepo,
destination: String,
) -> Result<String, String> {
let class = BranchCrdt::from(crdt, class_name).map_err(|e| e.to_string())?;
let destination = DocCreateDestination::from(destination).map_err(|e| e.to_string())?;
let request = AppRequest::V0(AppRequestV0 {
session_id,
command: AppRequestCommandV0::new_create(),
nuri: NuriV0::new_empty(),
payload: Some(AppRequestPayload::V0(AppRequestPayloadV0::Create(
DocCreate {
store: store_repo,
class,
destination,
},
))),
});
let response = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
if let AppResponse::V0(AppResponseV0::Nuri(nuri)) = response {
Ok(nuri)
} else {
Err("invalid response".to_string())
}
}
#[tauri::command(rename_all = "snake_case")]
async fn app_request_with_nuri_command(
nuri: String,
command: AppRequestCommandV0,
session_id: u64,
payload: Option<AppRequestPayloadV0>,
) -> Result<AppResponse, String> {
let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?;
let payload = payload.map(|p| AppRequestPayload::V0(p));
let request = AppRequest::V0(AppRequestV0 {
session_id,
command,
nuri,
payload,
});
app_request(request).await
}
#[tauri::command(rename_all = "snake_case")]
async fn upload_chunk(
session_id: u64,
upload_id: u32,
chunk: serde_bytes::ByteBuf,
nuri: String,
nuri: NuriV0,
_app: tauri::AppHandle,
) -> Result<AppResponse, String> {
//log_debug!("upload_chunk {:?}", chunk);
let mut request = AppRequest::new(
AppRequestCommandV0::FilePut,
NuriV0::new_from(&nuri).map_err(|e| e.to_string())?,
nuri,
Some(AppRequestPayload::V0(
AppRequestPayloadV0::RandomAccessFilePutChunk((upload_id, chunk)),
)),
@ -965,11 +491,6 @@ fn client_info_rust() -> Result<Value, String> {
Ok(ng_repo::os_info::get_os_info())
}
#[tauri::command(rename_all = "snake_case")]
fn get_device_name() -> Result<String, String> {
Ok(nextgraph::get_device_name())
}
#[derive(Default)]
pub struct AppBuilder {
setup: Option<SetupHook>,
@ -996,9 +517,7 @@ impl AppBuilder {
pub fn run(self) {
let setup = self.setup;
#[allow(unused_mut)]
let mut builder = tauri::Builder::default()
tauri::Builder::default()
.setup(move |app| {
if let Some(setup) = setup {
(setup)(app)?;
@ -1014,14 +533,7 @@ impl AppBuilder {
}
Ok(())
})
.plugin(tauri_plugin_window::init());
#[cfg(mobile)]
{
builder = builder.plugin(tauri_plugin_barcode_scanner::init());
}
builder
.plugin(tauri_plugin_window::init())
.invoke_handler(tauri::generate_handler![
test,
locales,
@ -1035,11 +547,6 @@ impl AppBuilder {
wallet_read_file,
wallet_get_file,
wallet_import,
wallet_export_rendezvous,
wallet_export_get_qrcode,
wallet_export_get_textcode,
wallet_import_rendezvous,
wallet_import_from_code,
wallet_close,
encode_create_account,
session_start,
@ -1054,24 +561,10 @@ impl AppBuilder {
client_info_rust,
doc_fetch_private_subscribe,
doc_fetch_repo_subscribe,
doc_create,
cancel_stream,
discrete_update,
app_request_stream,
file_get,
file_save_to_downloads,
app_request,
app_request_with_nuri_command,
upload_chunk,
get_device_name,
sparql_query,
sparql_update,
branch_history,
signature_status,
signature_request,
signed_snapshot_request,
update_header,
fetch_header,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

@ -1,4 +1,4 @@
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>

@ -1,4 +1,4 @@
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>

@ -8,7 +8,7 @@
},
"package": {
"productName": "Nextgraph",
"version": "0.1.1-alpha"
"version": "0.1.0"
},
"tauri": {
"bundle": {

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -35,32 +35,22 @@
import User from "./routes/User.svelte";
import UserRegistered from "./routes/UserRegistered.svelte";
import Install from "./routes/Install.svelte";
import ScanQR from "./routes/ScanQR.svelte";
import Shared from "./routes/Shared.svelte";
import Site from "./routes/Site.svelte";
import ng from "./api";
import AccountInfo from "./routes/AccountInfo.svelte";
import WalletLoginQr from "./routes/WalletLoginQr.svelte";
import WalletLoginTextCode from "./routes/WalletLoginTextCode.svelte";
const routes = new Map();
routes.set("/", Home);
routes.set("/test", Test);
routes.set("/wallet/login", WalletLogin);
routes.set("/wallet/login-qr", WalletLoginQr);
routes.set("/wallet/login-text-code", WalletLoginTextCode);
routes.set("/wallet/create", WalletCreate);
routes.set("/i/:invitation", Invitation);
routes.set("/user", User);
routes.set("/user/registered", UserRegistered);
routes.set("/wallet", WalletInfo);
routes.set("/user/accounts", AccountInfo);
routes.set("/wallet/scanqr", ScanQR);
if (import.meta.env.NG_APP_WEB) routes.set("/install", Install);
routes.set("/shared", Shared);
routes.set("/site", Site);
routes.set(/^\/did:ng:(.*)/i, NURI);
routes.set(/^\/did:ng(.*)/i, NURI);
routes.set("*", NotFound);
let unsubscribe = () => {};
@ -74,18 +64,11 @@
// };
onMount(async () => {
//console.log("hide splash", window.supported);
if (window.supported) {
window.document.getElementById("splash").className="noshow";
window.document.getElementById("app").className="";
}
//window.document.getElementById("splash").className="splash-loaded";
try {
await disconnections_subscribe();
await select_default_lang();
} catch (e) {
console.warn(e);
console.error(e);
//console.log("called disconnections_subscribe twice");
}
let tauri_platform = import.meta.env.TAURI_PLATFORM;
@ -279,7 +262,6 @@
}
});
}
});
onDestroy(() => {
@ -304,3 +286,5 @@
{:else}
<Router {routes} />
{/if}

@ -1,4 +1,4 @@
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -17,17 +17,11 @@ const mapping = {
"wallet_gen_shuffle_for_pin": [],
"wallet_open_with_pazzle": ["wallet","pazzle","pin"],
"wallet_open_with_mnemonic_words": ["wallet","mnemonic_words","pin"],
"wallet_open_with_mnemonic": ["wallet","mnemonic","pin"],
"wallet_was_opened": ["opened_wallet"],
"wallet_create": ["params"],
"wallet_read_file": ["file"],
"wallet_get_file": ["wallet_name"],
"wallet_import": ["encrypted_wallet","opened_wallet","in_memory"],
"wallet_export_rendezvous": ["session_id", "code"],
"wallet_export_get_qrcode": ["session_id", "size"],
"wallet_export_get_textcode": ["session_id"],
"wallet_import_rendezvous": ["size"],
"wallet_import_from_code": ["code"],
"wallet_close": ["wallet_name"],
"encode_create_account": ["payload"],
"session_start": ["wallet_name","user"],
@ -38,23 +32,10 @@ const mapping = {
"decode_invitation": ["invite"],
"user_connect": ["info","user_id","location"],
"user_disconnect": ["user_id"],
"discrete_update": ["session_id", "update", "heads", "crdt", "nuri"],
"app_request": ["request"],
"app_request_with_nuri_command": ["nuri", "command", "session_id", "payload"],
"sparql_query": ["session_id","sparql","base","nuri"],
"sparql_update": ["session_id","sparql","nuri"],
"test": [ ],
"get_device_name": [],
"doc_create": [ "session_id", "crdt", "class_name", "store_repo", "destination" ],
"doc_fetch_private_subscribe": [],
"doc_fetch_repo_subscribe": ["repo_o"],
"branch_history": ["session_id", "nuri"],
"file_save_to_downloads": ["session_id", "reference", "filename", "branch_nuri"],
"signature_status": ["session_id", "nuri"],
"signed_snapshot_request": ["session_id", "nuri"],
"signature_request": ["session_id", "nuri"],
"update_header": ["session_id","nuri","title","about"],
"fetch_header": ["session_id", "nuri"]
"doc_fetch_repo_subscribe": ["repo_id"],
}
@ -79,28 +60,11 @@ const handler = {
// } else if (path[0] === "wallet_create") {
// let res = await Reflect.apply(sdk[path], caller, args);
// return res;
} else if (path[0] === "app_request_stream") {
let callback = args[1];
let new_callback = (event) => {
if (event.V0.State?.graph?.triples) {
let json_str = new TextDecoder().decode(event.V0.State.graph.triples);
event.V0.State.graph.triples = JSON.parse(json_str);
} else if (event.V0.Patch?.graph) {
let inserts_json_str = new TextDecoder().decode(event.V0.Patch.graph.inserts);
event.V0.Patch.graph.inserts = JSON.parse(inserts_json_str);
let removes_json_str = new TextDecoder().decode(event.V0.Patch.graph.removes);
event.V0.Patch.graph.removes = JSON.parse(removes_json_str);
}
callback(event).then(()=> {})
};
args[1] = new_callback;
return Reflect.apply(sdk[path], caller, args)
} else {
return Reflect.apply(sdk[path], caller, args)
}
} else {
let tauri = await import("@tauri-apps/api/tauri");
try {
if (path[0] === "client_info") {
let from_rust = await tauri.invoke("client_info_rust",{});
@ -134,11 +98,6 @@ const handler = {
};
//console.log(info,res);
return res;
} else if (path[0] === "get_device_name") {
let tauri_platform = import.meta.env.TAURI_PLATFORM;
if (tauri_platform == 'android') return "Android Phone";
else if (tauri_platform == 'ios') return "iPhone";
else return await tauri.invoke(path[0],{});
} else if (path[0] === "locales") {
let from_rust = await tauri.invoke("locales",{});
let from_js = window.navigator.languages;
@ -171,47 +130,7 @@ const handler = {
}
return ret;
}
else if (path[0] === "file_get") {
let stream_id = (lastStreamId += 1).toString();
//console.log("stream_id",stream_id);
let { getCurrent } = await import("@tauri-apps/plugin-window");
//let session_id = args[0];
let callback = args[3];
let unlisten = await getCurrent().listen(stream_id, async (event) => {
//console.log(event.payload);
if (event.payload.V0.FileBinary) {
event.payload.V0.FileBinary = Uint8Array.from(event.payload.V0.FileBinary);
}
let ret = callback(event.payload);
if (ret === true) {
await tauri.invoke("cancel_stream", {stream_id});
} else if (ret.then) {
ret.then(async (val)=> {
if (val === true) {
await tauri.invoke("cancel_stream", {stream_id});
}
});
}
})
try {
await tauri.invoke("file_get",{stream_id, session_id:args[0], reference: args[1], branch_nuri:args[2]});
} catch (e) {
unlisten();
await tauri.invoke("cancel_stream", {stream_id});
throw e;
}
return () => {
unlisten();
tauri.invoke("cancel_stream", {stream_id});
}
} else if (path[0] === "discrete_update") {
let arg = {};
args.map((el,ix) => arg[mapping[path[0]][ix]]=el)
arg.update = Array.from(new Uint8Array(arg.update));
return await tauri.invoke(path[0],arg)
} else if (path[0] === "app_request_stream") {
else if (path[0] === "app_request_stream") {
let stream_id = (lastStreamId += 1).toString();
//console.log("stream_id",stream_id);
let { getCurrent } = await import("@tauri-apps/plugin-window");
@ -219,45 +138,15 @@ const handler = {
let request = args[0];
let callback = args[1];
let unlisten = await getCurrent().listen(stream_id, async (event) => {
let unlisten = await getCurrent().listen(stream_id, (event) => {
//console.log(event.payload);
if (event.payload.V0.FileBinary) {
event.payload.V0.FileBinary = Uint8Array.from(event.payload.V0.FileBinary);
}
if (event.payload.V0.State?.graph?.triples) {
let json_str = new TextDecoder().decode(Uint8Array.from(event.payload.V0.State.graph.triples));
event.payload.V0.State.graph.triples = JSON.parse(json_str);
} else if (event.payload.V0.Patch?.graph) {
let inserts_json_str = new TextDecoder().decode(Uint8Array.from(event.payload.V0.Patch.graph.inserts));
event.payload.V0.Patch.graph.inserts = JSON.parse(inserts_json_str);
let removes_json_str = new TextDecoder().decode(Uint8Array.from(event.payload.V0.Patch.graph.removes));
event.payload.V0.Patch.graph.removes = JSON.parse(removes_json_str);
}
if (event.payload.V0.State?.discrete) {
let crdt = Object.getOwnPropertyNames(event.payload.V0.State.discrete)[0];
event.payload.V0.State.discrete[crdt] = Uint8Array.from(event.payload.V0.State.discrete[crdt]);
} else if (event.payload.V0.Patch?.discrete) {
let crdt = Object.getOwnPropertyNames(event.payload.V0.Patch.discrete)[0];
event.payload.V0.Patch.discrete[crdt] = Uint8Array.from(event.payload.V0.Patch.discrete[crdt]);
}
let ret = callback(event.payload);
if (ret === true) {
await tauri.invoke("cancel_stream", {stream_id});
} else if (ret.then) {
ret.then(async (val)=> {
if (val === true) {
await tauri.invoke("cancel_stream", {stream_id});
}
});
}
callback(event.payload).then(()=> {})
})
try {
await tauri.invoke("app_request_stream",{stream_id, request});
} catch (e) {
unlisten();
await tauri.invoke("cancel_stream", {stream_id});
throw e;
}
return () => {
unlisten();
tauri.invoke("cancel_stream", {stream_id});
@ -270,15 +159,6 @@ const handler = {
}
return res || {};
} else if (path[0] === "wallet_import_from_code") {
let arg = {};
args.map((el,ix) => arg[mapping[path[0]][ix]]=el);
let res = await tauri.invoke(path[0],arg);
if (res) {
res.V0.content.security_img = Uint8Array.from(res.V0.content.security_img);
}
return res || {};
} else if (path[0] === "upload_chunk") {
let session_id = args[0];
let upload_id = args[1];
@ -303,7 +183,7 @@ const handler = {
return false;
} else if (path[0] === "get_local_url") {
return false;
} else if (path[0] === "wallet_open_with_pazzle" || path[0] === "wallet_open_with_mnemonic_words" || path[0] === "wallet_open_with_mnemonic") {
} else if (path[0] === "wallet_open_with_pazzle" || path[0] === "wallet_open_with_mnemonic_words") {
let arg:any = {};
args.map((el,ix) => arg[mapping[path[0]][ix]]=el)
let img = Array.from(new Uint8Array(arg.wallet.V0.content.security_img));
@ -311,20 +191,11 @@ const handler = {
arg.wallet = {V0:{id:arg.wallet.V0.id, sig:arg.wallet.V0.sig, content:{}}};
Object.assign(arg.wallet.V0.content,old_content);
arg.wallet.V0.content.security_img = img;
return await tauri.invoke(path[0],arg);
return tauri.invoke(path[0],arg);
} else {
let arg = {};
args.map((el,ix) => arg[mapping[path[0]][ix]]=el)
return await tauri.invoke(path[0],arg)
}
} catch (e) {
let error;
try {
error = JSON.parse(e);
} catch (f) {
error = e;
}
throw error;
return tauri.invoke(path[0],arg)
}
}
},

@ -1,179 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import {
toast_error,
toast_success,
reset_toasts,
display_error,
live_discrete_update,
discrete_update
} from "../store";
import {
cur_tab_register_on_save,
cur_tab_deregister_on_save,
cur_tab_doc_can_edit,
set_view_or_edit
} from "../tab";
import { t } from "svelte-i18n";
import wasmUrl from "@automerge/automerge/automerge.wasm?url";
import { next as A } from "@automerge/automerge/slim";
import{ PencilSquare } from "svelte-heros-v2";
import AMap from "./automerge/AMap.svelte";
export let commits = {};
export let readonly = false;
let doc = {};
let loading = true;
let safari_error = false;
function concatenate(uint8arrays) {
const totalLength = uint8arrays.reduce(
(total, uint8array) => total + uint8array.byteLength,
0
);
const result = new Uint8Array(totalLength);
let offset = 0;
uint8arrays.forEach((uint8array) => {
result.set(uint8array, offset);
offset += uint8array.byteLength;
});
return result;
}
let root_proxy;
onMount(async ()=>{
try {
await A.initializeWasm(wasmUrl);
} catch (e) {
safari_error = true;
return;
}
doc = A.init();
if (!readonly) {
cur_tab_register_on_save(async (updates)=>{
let update = concatenate(updates);
await live_discrete_update(update, "Automerge", commits.heads);
});
}
let history = commits.discrete?.registerOnUpdate((update) => {
doc = A.loadIncremental(doc, update.Automerge);
});
for (const h of history) {
doc = A.loadIncremental(doc, h.Automerge);
}
A.change(doc, (d) => {
root_proxy = d;
});
loading = false;
});
async function update(event) {
//console.log("got update", event)
doc = event.detail.d;
try {
await discrete_update(event.detail.u, "Automerge", commits.heads);
} catch (e){
toast_error(display_error(e));
}
}
onDestroy(async ()=>{
commits.discrete?.deregisterOnUpdate();
if (!readonly) {
await cur_tab_deregister_on_save();
}
});
async function updateText(event) {
doc = A.change(doc, (d) => {
A.updateText(d, event.detail.p, event.detail.s)
});
let update = A.getLastLocalChange(doc);
try {
await discrete_update(update, "Automerge", commits.heads);
} catch (e){
toast_error(display_error(e));
}
}
const edit = () => {
set_view_or_edit(false);
}
</script>
{#if safari_error}
<Alert class="m-2" color="red">{$t("errors.no_wasm_on_old_safari")}</Alert>
{:else}
{#if loading}
<div class="mb-4 flex flex-col justify-center text-primary-700">
<svg
class="animate-spin mt-4 h-10 w-10 mb-4 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<p class="text-center">{$t("connectivity.loading")}...</p>
</div>
{/if}
{#if Object.keys(doc).length !== 0 || !readonly}
<div class="grow mb-20" style="min-height:300px;">
<AMap {readonly} value={doc} {doc} on:update={update} on:updateText={updateText} proxy={root_proxy}/>
</div>
{:else if $cur_tab_doc_can_edit}
<div class="flex-row">
<button
on:click={edit}
on:keypress={edit}
class="shrink select-none ml-4 mt-2 mb-10 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<PencilSquare class="mr-2 focus:outline-none" tabindex="-1" />
{$t("doc.start_editing")}
</button>
</div>
{:else}
<p class="ml-5">{$t("doc.empty")}</p>
{/if}
{/if}
<style>
</style>

@ -1,74 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import {
toast_error,
toast_success,
reset_toasts,
display_error,
live_discrete_update,
discrete_update
} from "../store";
import {
cur_tab_register_on_save,
cur_tab_deregister_on_save,
cur_tab_branch_class
} from "../tab";
import { t } from "svelte-i18n";
import wasmUrl from "@automerge/automerge/automerge.wasm?url";
import { next as A } from "@automerge/automerge/slim";
import Highlight, { LineNumbers } from "svelte-highlight";
import json from "svelte-highlight/languages/json";
import "svelte-highlight/styles/github.css";
export let commits = {};
let doc = {};
let source = "";
let safari_error = false;
onMount(async ()=>{
try {
await A.initializeWasm(wasmUrl);
} catch (e) {
toast_error($t("errors.no_wasm_on_old_safari"));
safari_error = true;
return;
}
doc = A.init();
let history = commits.discrete?.registerOnUpdate((update) => {
doc = A.loadIncremental(doc, update.Automerge);
source = JSON.stringify(doc,null , 4 );
});
for (const h of history) {
doc = A.loadIncremental(doc, h.Automerge);
}
source = JSON.stringify(doc,null , 4 );
});
onDestroy(async ()=>{
commits.discrete?.deregisterOnUpdate();
});
</script>
{#if safari_error}
<Alert class="m-2" color="red">{$t("errors.no_wasm_on_old_safari")}</Alert>
{:else if source}
<Highlight language={json} code={source} class="mb-10" let:highlighted >
<LineNumbers {highlighted} wrapLines hideBorder />
</Highlight>
{/if}

@ -1,19 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import AutomergeEditor from "./AutomergeEditor.svelte";
export let commits = {};
</script>
<AutomergeEditor {commits} readonly={true}/>

@ -1,101 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
toast_error,
toast_success,
reset_toasts,
display_error,
live_discrete_update,
discrete_update
} from "../store";
import {
cur_tab_register_on_save,
cur_tab_deregister_on_save,
cur_tab_branch_class
} from "../tab";
import * as Y from 'yjs'
// @ts-ignore
import { yCollab } from 'y-codemirror.next'
import CodeMirror from "svelte-codemirror-editor";
import { javascript } from '@codemirror/lang-javascript'
import { rust } from '@codemirror/lang-rust'
import { svelte } from "@replit/codemirror-lang-svelte";
import {basicSetup} from "codemirror"
export let commits = {};
const class_to_lang = {
"code:js" : javascript(),
"code:ts" : javascript({"typescript":true}),
"code:rust" : rust(),
"code:svelte" : svelte(),
"code:react" : javascript({"jsx":true, "typescript":true}),
}
let lang;
$: lang = $cur_tab_branch_class && class_to_lang[$cur_tab_branch_class]
const ydoc = new Y.Doc()
const ytext = ydoc.getText('ng')
ydoc.on('update', async (update, origin) => {
if (!origin.local) {
try {
await discrete_update(update, "YText", commits.heads);
} catch (e){
toast_error(display_error(e));
}
}
})
ydoc.on('destroy', async () => {
commits.discrete?.deregisterOnUpdate();
await cur_tab_deregister_on_save();
})
let view;
onMount(()=>{
cur_tab_register_on_save(async (updates)=>{
let update = Y.mergeUpdates(updates);
await live_discrete_update(update, "YText", commits.heads);
});
let history = commits.discrete?.registerOnUpdate((update) => {
Y.applyUpdate(ydoc, update.YText, {local:true})
});
for (const h of history) {
Y.applyUpdate(ydoc, h.YText, {local:true})
}
});
onDestroy(()=>{
ydoc.destroy();
});
</script>
<div class="flex-col">
<CodeMirror {lang} on:ready={(e) => { view = e.detail; view.focus(); }} lineWrapping extensions={[basicSetup, yCollab(ytext, false, { undoManager: false })]} styles={{
"&": {
maxWidth: "100%",
},
}}/>
</div>

@ -1,88 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import ng from "../api";
import { link } from "svelte-spa-router";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import{ PlusCircle } from "svelte-heros-v2";
import { t } from "svelte-i18n";
import {
in_memory_discrete, open_viewer, set_viewer, set_editor, set_view_or_edit, cur_tab_branch_class, cur_tab_doc_can_edit, cur_tab
} from "../tab";
import DataClassIcon from "../lib/icons/DataClassIcon.svelte";
import {
openModalCreate,
sparql_query,
active_session
} from "../store";
import {
Clipboard
} from "svelte-heros-v2";
export let commits;
function contained(graph) {
let ret = [];
for (const g of graph) {
if (g.substring(57,90) === "http://www.w3.org/ns/ldp#contains") {
let nuri = g.substring(93,146);
let repo = nuri;
nuri = nuri + ":" + $cur_tab.store.overlay;
let hash = nuri.substring(9,16);
ret.push({nuri,hash,repo});
}
}
ret.sort((a, b) => a.hash.localeCompare(b.hash));
return ret;
}
async function fetch_header(repo) {
try {
let res = await ng.fetch_header($active_session.session_id, repo);
return res;
}catch(e){
console.error(e);
return {};
}
}
const create = () => {
openModalCreate();
}
const config = {
class: "mr-2 w-6 h-6 shrink-0 focus:outline-none"
}
</script>
<div class="flex-col p-5">
{#each contained(commits.graph) as doc}
{#await fetch_header(doc.repo)}
<div class="flex"> <Clipboard tabindex="-1" class="mr-2 w-6 h-6 shrink-0 focus:outline-none"/><div class="flex font-mono mb-3"> <a use:link href="/{doc.nuri}">{doc.hash}</a> </div> </div>
{:then header}
<div class="flex" title="{header.about || ''}"> {#if header.class}<DataClassIcon {config} dataClass={header.class}/>{:else}<Clipboard tabindex="-1" class="mr-2 w-6 h-6 shrink-0 focus:outline-none"/>{/if}<div class="flex font-mono mb-3"> <a use:link href="/{doc.nuri}">{header.title || doc.hash}</a> </div></div>
{/await}
{/each}
{#if commits.graph.length == 0 || contained(commits.graph).length == 0}
<p>{$t("doc.empty_container")}</p>
{#if $cur_tab_doc_can_edit}
<button
on:click={create}
on:keypress={create}
class="select-none ml-0 mt-2 mb-10 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<PlusCircle tabindex="-1" class="mr-2 focus:outline-none" />
{$t("doc.create")}
</button>
{/if}
{/if}
</div>

@ -1,159 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<!--
We could maybe also use https://ssssota.github.io/svelte-exmarkdown/ for rendering the MD (but to obtain the MD, we need to instantiate Milkdown anyway)
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
toast_error,
toast_success,
reset_toasts,
display_error,
live_discrete_update,
discrete_update
} from "../store";
import {
cur_tab_register_on_save,
cur_tab_deregister_on_save,
cur_tab_branch_class,
set_view_or_edit
} from "../tab";
import { t } from "svelte-i18n";
import{ PencilSquare } from "svelte-heros-v2";
import * as Y from 'yjs'
import { Editor, editorCtx, rootCtx } from '@milkdown/core';
import { collab, collabServiceCtx } from '@milkdown/plugin-collab';
import { commonmark } from '@milkdown/preset-commonmark';
import { gfm } from '@milkdown/preset-gfm';
import markdown from "svelte-highlight/languages/markdown";
import Highlight, { LineNumbers } from "svelte-highlight";
import "svelte-highlight/styles/github.css";
import { getMarkdown } from "@milkdown/utils";
export let commits = {};
const ydoc = new Y.Doc()
let has_content = true;
let loading = true;
let source = "";
let editor;
function process_doc() {
editor.action((ctx) => {
const editor = ctx.get(editorCtx);
source = editor.action(getMarkdown());
});
}
async function setup() {
try {
editor = await Editor.make().config((ctx) => {
ctx.set(rootCtx, '#mdhiddeneditor')
})
.use(commonmark)
.use(gfm)
.use(collab).create();
ydoc.on('destroy', async () => {
})
editor.action((ctx) => {
const collabService = ctx.get(collabServiceCtx);
collabService
// bind doc
.bindDoc(ydoc)
// connect yjs with milkdown
.connect();
});
has_content = false;
let history = commits.discrete?.registerOnUpdate((update) => {
Y.applyUpdate(ydoc, update.YXml, {local:true})
has_content = true;
process_doc();
});
for (const h of history) {
Y.applyUpdate(ydoc, h.YXml, {local:true})
has_content = true;
}
if (has_content) process_doc();
loading = false;
}
catch (e){
console.log(e)
}
}
onMount(async ()=>{
if (editor) await editor.destroy();
await setup();
});
onDestroy(async ()=>{
ydoc.destroy();
commits.discrete?.deregisterOnUpdate();
if (editor) await editor.destroy();
editor = undefined;
});
</script>
{#if !has_content}
<p class="ml-5">{$t("doc.empty")}</p>
{/if}
{#if loading}
<div class="grow flex flex-col justify-center text-primary-700">
<svg
class="animate-spin mt-4 h-10 w-10 mb-4 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<p class="text-center">{$t("connectivity.loading")}...</p>
</div>
{/if}
{#if source}
<Highlight language={markdown} code={source} class="mb-10" let:highlighted>
<LineNumbers {highlighted} wrapLines hideBorder />
</Highlight>
{/if}
<div id="mdhiddeneditor" style="display:none;"></div>

@ -1,200 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<!--
TODO:
https://github.com/Milkdown/milkdown/tree/main/packages/components/src
https://milkdown-storybook.vercel.app/?path=/story/components-image-block--empty
https://github.com/Milkdown/milkdown/tree/main/packages/crepe
https://milkdown-storybook.vercel.app/?path=/story/crepe-crepe--empty
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
toast_error,
toast_success,
reset_toasts,
display_error,
live_discrete_update,
discrete_update
} from "../store";
import {
cur_tab_register_on_save,
cur_tab_deregister_on_save,
cur_tab_branch_class
} from "../tab";
import { t } from "svelte-i18n";
import * as Y from 'yjs'
import { Editor, rootCtx, editorViewCtx } from '@milkdown/core';
import { commonmark } from '@milkdown/preset-commonmark';
import { gfm } from '@milkdown/preset-gfm'
import { nord } from '@milkdown/theme-nord';
import '@milkdown/theme-nord/style.css';
import { collab, collabServiceCtx } from '@milkdown/plugin-collab';
import { placeholder, placeholderCtx } from './milkdown-placeholder'
import { splitEditing, toggleSplitEditing } from '@milkdown-lab/plugin-split-editing'
//import { SlashProvider, slashFactory } from '@milkdown/plugin-slash'
import { callCommand } from '@milkdown/utils';
import { emoji } from '@milkdown/plugin-emoji';
import { math } from '@milkdown/plugin-math';
import 'katex/dist/katex.min.css';
import { indent } from '@milkdown/plugin-indent';
import 'prism-themes/themes/prism-nord.css'
export let commits = {};
const ydoc = new Y.Doc()
let editor;
let width;
let split = true;
function width_changed() {
if (!editor) return;
if (width < 768 && split) {
split = false;
editor.action(callCommand(toggleSplitEditing.key, true));
} else if (width >= 768 && !split) {
split = true;
editor.action(callCommand(toggleSplitEditing.key, false));
}
}
$: width, width_changed();
// function slashPluginView(view) {
// const content = document.createElement('div');
// const provider = new SlashProvider({
// content,
// });
// return {
// update: (updatedView, prevState) => {
// provider.update(updatedView, prevState);
// },
// destroy: () => {
// provider.destroy();
// content.remove();
// }
// }
// }
// const slash = slashFactory('my-slash');
async function setup() {
if (!Array.prototype.at) {
Array.prototype.at = function at(n) {
let i = Math.trunc(n) || 0
i = i < 0 ? this.length + i : i
if (i < 0 || i >= this.length) return undefined
return this[i]
}
}
if (!Element.prototype.replaceChildren) {
Element.prototype.replaceChildren = function replaceChildren(...new_children) {
const { childNodes } = this;
while (childNodes.length) {
childNodes[0].remove();
}
this.append(...new_children);
}
}
let prism = await import("@milkdown/plugin-prism");
editor = await Editor.make().config((ctx) => {
ctx.set(rootCtx, '#mdeditor')
ctx.set(placeholderCtx, $t("doc.type_your_text_here"))
// ctx.set(slash.key, {
// view: slashPluginView
// })
})//.use(slash)
.config(nord)
.use(commonmark)
.use(gfm)
.use(prism.prism)
.use(indent)
.use(math)
.use(emoji)
.use(placeholder)
.use(splitEditing)
.use(collab).create();
ydoc.on('update', async (update, origin) => {
//console.log(update,origin);
if (!origin.local) {
try {
await discrete_update(update, "YXml", commits.heads);
} catch (e){
toast_error(display_error(e));
}
}
})
ydoc.on('destroy', async () => {
commits.discrete?.deregisterOnUpdate();
await cur_tab_deregister_on_save();
})
editor.action((ctx) => {
const collabService = ctx.get(collabServiceCtx);
collabService
// bind doc
.bindDoc(ydoc)
// connect yjs with milkdown
.connect();
});
cur_tab_register_on_save(async (updates)=>{
let update = Y.mergeUpdates(updates);
await live_discrete_update(update, "YXml", commits.heads);
});
let history = commits.discrete?.registerOnUpdate((update) => {
Y.applyUpdate(ydoc, update.YXml, {local:true})
});
for (const h of history) {
Y.applyUpdate(ydoc, h.YXml, {local:true})
}
await tick();
editor.action((ctx) => {
const editorView = ctx.get(editorViewCtx)
editorView.focus();
});
width_changed();
}
onMount(async ()=>{
if (editor) await editor.destroy();
await setup();
});
onDestroy(async ()=>{
ydoc.destroy();
if (editor) await editor.destroy();
editor = undefined;
});
</script>
<div class="grow p-5 post-rich-text" style="min-height:300px;" bind:clientWidth={width}>
<div id="mdeditor" class="prosemirror-editor"></div>
</div>

@ -1,197 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<!--
We could maybe also use https://ssssota.github.io/svelte-exmarkdown/ for rendering the MD (but to obtain the MD, we need to instantiate Milkdown anyway)
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
toast_error,
toast_success,
reset_toasts,
display_error,
live_discrete_update,
discrete_update
} from "../store";
import {
cur_tab_register_on_save,
cur_tab_deregister_on_save,
cur_tab_branch_class,
set_view_or_edit
} from "../tab";
import { t } from "svelte-i18n";
import{ PencilSquare } from "svelte-heros-v2";
import * as Y from 'yjs'
import { Editor, rootCtx, editorViewOptionsCtx } from '@milkdown/core';
import { commonmark } from '@milkdown/preset-commonmark';
import { gfm } from '@milkdown/preset-gfm'
import { nord } from '@milkdown/theme-nord';
import '@milkdown/theme-nord/style.css';
import { collab, collabServiceCtx } from '@milkdown/plugin-collab';
import "svelte-highlight/styles/github.css";
import { emoji } from '@milkdown/plugin-emoji';
import { math } from '@milkdown/plugin-math';
import 'katex/dist/katex.min.css';
import { indent } from '@milkdown/plugin-indent';
import "prism-themes/themes/prism-nord.css";
export let commits = {};
const ydoc = new Y.Doc()
let editor;
let has_content = true;
let loading = true;
async function setup() {
try {
editor = Editor.make().config((ctx) => {
ctx.set(rootCtx, '#mdeditor')
ctx.update(editorViewOptionsCtx, (prev) => ({
...prev,
editable:() => false,
}))
}).config(nord)
.use(commonmark)
.use(gfm);
// polyfill if Safari < 15.4
if (!Array.prototype.at) {
Array.prototype.at = function at(n) {
let i = Math.trunc(n) || 0
i = i < 0 ? this.length + i : i
if (i < 0 || i >= this.length) return undefined
return this[i]
}
}
if (!Element.prototype.replaceChildren) {
Element.prototype.replaceChildren = function replaceChildren(...new_children) {
const { childNodes } = this;
while (childNodes.length) {
childNodes[0].remove();
}
this.append(...new_children);
}
}
if ([].at) {
let prism = await import("@milkdown/plugin-prism");
editor = editor.use(prism.prism);
}
editor = await editor
.use(indent)
.use(math)
.use(emoji)
.use(collab)
.create();
ydoc.on('destroy', async () => {
commits.discrete?.deregisterOnUpdate();
})
editor.action((ctx) => {
const collabService = ctx.get(collabServiceCtx);
collabService
// bind doc and awareness
.bindDoc(ydoc)
// connect yjs with milkdown
.connect();
});
has_content = false;
let history = commits.discrete?.registerOnUpdate((update) => {
Y.applyUpdate(ydoc, update.YXml, {local:true})
has_content = true;
});
for (const h of history) {
Y.applyUpdate(ydoc, h.YXml, {local:true})
has_content = true;
}
loading = false;
}
catch (e){
console.log(e)
}
}
onMount(async ()=>{
if (editor) await editor.destroy();
await setup();
});
onDestroy(async ()=>{
ydoc.destroy();
try {
if (editor) await editor.destroy();
editor = undefined;
} catch(e) {
console.log(e);
}
});
const edit = () => {
set_view_or_edit(false);
}
</script>
{#if !has_content}
<div class="flex-row">
<button
on:click={edit}
on:keypress={edit}
class="select-none ml-5 mb-10 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<PencilSquare tabindex="-1" class="mr-2 focus:outline-none" />
{$t("doc.start_editing")}
</button>
</div>
{/if}
{#if loading}
<div class="grow flex flex-col justify-center text-primary-700">
<svg
class="animate-spin mt-4 h-10 w-10 mb-4 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<p class="text-center">{$t("connectivity.loading")}...</p>
</div>
{/if}
<div class="grow p-5 post-rich-text prose">
<div id="mdeditor" class="prosemirror-editor"></div>
</div>

@ -1,105 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
toast_error,
toast_success,
reset_toasts,
display_error,
live_discrete_update,
discrete_update
} from "../store";
import {
cur_tab_register_on_save,
cur_tab_deregister_on_save,
cur_tab_branch_class
} from "../tab";
import { t } from "svelte-i18n";
import * as Y from 'yjs'
// @ts-ignore
import { ySyncPlugin, initProseMirrorDoc } from 'y-prosemirror';
import ProsemirrorEditor from 'prosemirror-svelte';
import { richTextSchema } from 'prosemirror-svelte/state';
import { richTextPlugins, corePlugins } from 'prosemirror-svelte/helpers';
import { EditorState } from "prosemirror-state";
export let commits = {};
const ydoc = new Y.Doc()
const yxml = ydoc.getXmlFragment('prosemirror')
let view;
ydoc.on('update', async (update, origin) => {
if (!origin.local) {
try {
await discrete_update(update, "YXml", commits.heads);
} catch (e){
toast_error(display_error(e));
}
}
})
ydoc.on('destroy', async () => {
commits.discrete?.deregisterOnUpdate();
await cur_tab_deregister_on_save();
})
const { doc, mapping } = initProseMirrorDoc(yxml, richTextSchema)
let selection;
let editorState = EditorState.create({
schema: richTextSchema,
doc,
selection,
plugins: [
...corePlugins,
...richTextPlugins,
ySyncPlugin(yxml, { mapping })
]
});
onMount(()=>{
cur_tab_register_on_save(async (updates)=>{
let update = Y.mergeUpdates(updates);
await live_discrete_update(update, "YXml", commits.heads);
});
let history = commits.discrete?.registerOnUpdate((update) => {
Y.applyUpdate(ydoc, update.YXml, {local:true})
});
for (const h of history) {
Y.applyUpdate(ydoc, h.YXml, {local:true})
}
view.focus()
});
onDestroy(()=>{
ydoc.destroy();
});
</script>
<div class="grow p-5 post-rich-text prose" style="min-height:300px;">
<ProsemirrorEditor
className="prosemirror-editor"
{editorState}
debounceChangeEventsInterval=2000
placeholder={$t("doc.type_your_text_here")}
bind:view={view}
/>
</div>

@ -1,120 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
sparql_update,
toast_error,
toast_success
} from "../store";
import {
set_view_or_edit, cur_tab_doc_can_edit
} from "../tab";
import{ PencilSquare, RocketLaunch } from "svelte-heros-v2";
import { t } from "svelte-i18n";
import * as Y from 'yjs'
import { yXmlFragmentToProseMirrorRootNode } from 'y-prosemirror';
import { richTextSchema } from 'prosemirror-svelte/state';
import { DOMSerializer } from "prosemirror-model";
export let commits = {};
let source = "";
const ydoc = new Y.Doc()
const yxml = ydoc.getXmlFragment('prosemirror')
let loading = true;
ydoc.on('destroy', async () => {
commits.discrete?.deregisterOnUpdate();
})
const toHTML = () => {
const serializer = DOMSerializer.fromSchema(richTextSchema);
const fragment = serializer.serializeFragment(yXmlFragmentToProseMirrorRootNode(yxml, richTextSchema));
const node = document.createElement('div');
node.append(fragment);
return node.innerHTML;
}
onMount(()=>{
let history = commits.discrete?.registerOnUpdate((update) => {
Y.applyUpdate(ydoc, update.YXml, {local:true})
source = toHTML()
});
for (const h of history) {
Y.applyUpdate(ydoc, h.YXml, {local:true})
}
source = toHTML();
loading = false;
});
onDestroy(()=>{
ydoc.destroy();
});
const edit = () => {
set_view_or_edit(false);
}
</script>
<div class="grow p-5">
{#if loading}
<div class="grow flex flex-col justify-center text-primary-700">
<svg
class="animate-spin mt-4 h-10 w-10 mb-4 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<p class="text-center">{$t("connectivity.loading")}...</p>
</div>
{/if}
{#if source}
<div class="post-rich-text prose" style="margin-top: 1.25em;">
{@html source}
</div>
{:else if $cur_tab_doc_can_edit}
<button
on:click={edit}
on:keypress={edit}
class="select-none mb-10 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<PencilSquare tabindex="-1" class="mr-2 focus:outline-none" />
{$t("doc.start_editing")}
</button>
{/if}
</div>

@ -1,126 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
sparql_query,
toast_error,
toast_success,
reset_toasts,
display_error,
} from "../store";
import {
in_memory_discrete, open_viewer, set_viewer, reset_in_memory
} from "../tab";
import{ Sun, RocketLaunch } from "svelte-heros-v2";
import { t } from "svelte-i18n";
import { Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell, Toggle } from 'flowbite-svelte';
import CodeMirror from "svelte-codemirror-editor";
import {StreamLanguage} from "@codemirror/language"
import { sparql } from "@codemirror/legacy-modes/mode/sparql";
import {basicSetup} from "codemirror"
import Highlight, { LineNumbers } from "svelte-highlight";
import hljs from "highlight.js";
import { definer } from "../turtle";
import "svelte-highlight/styles/github.css";
import { each } from "svelte/internal";
const language = {
name: "turtle",
register: (hljs) => {
return definer(hljs);
},
};
onMount(()=>{
reset_in_memory();
if (!$in_memory_discrete){
$in_memory_discrete = "SELECT ?subject ?predicate ?object WHERE {\n ?subject ?predicate ?object .\n} LIMIT 10";
}
});
let union = false;
const run = async () => {
try{
await reset_toasts();
results = await sparql_query($in_memory_discrete, union);
} catch(e) {
console.error(e)
toast_error(display_error(e));
}
}
const openTurtle = () => {
reset_toasts();
set_viewer("n:g:z:rdf_viewer:turtle");
}
let results = undefined;
</script>
<div class="flex-col">
<CodeMirror bind:value={$in_memory_discrete} lineWrapping useTab={false} extensions={[basicSetup,StreamLanguage.define(sparql)]} styles={{
"&": {
maxWidth: "100%",
},
}}/>
<Toggle class="mt-1 ml-2" bind:checked={union}>{$t("doc.query_all_docs")}</Toggle>
<button
on:click={run}
on:keypress={run}
class="select-none ml-2 mt-2 mb-10 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<RocketLaunch tabindex="-1" class="mr-2 focus:outline-none" />
{$t("doc.run_query")}
</button>
<button
on:click={openTurtle}
on:keypress={openTurtle}
class="select-none ml-2 mt-2 mb-10 text-gray-600 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<Sun class="mr-2 focus:outline-none" tabindex="-1" />
{$t("doc.view_as_turtle")}
</button>
{#if results!==undefined}
<div>
<span class="ml-2 font-bold">{$t("doc.results")}: <br/></span>
{#if Array.isArray(results)}
{#if results.length}
<Highlight {language} code={results.join(" .\r\n") + (results.length ? " .":"")} class="mb-10" let:highlighted >
<LineNumbers {highlighted} wrapLines hideBorder />
</Highlight>
{:else}
<span class="ml-2">{$t("doc.empty")}</span>
{/if}
{:else if results?.head}
<Table>
<TableHead>
{#each results.head.vars as variable}
<TableHeadCell>{variable}</TableHeadCell>
{/each}
</TableHead>
<TableBody tableBodyClass="divide-y">
{#each results.results.bindings as row}
<TableBodyRow>
{#each results.head.vars as variable}
<TableBodyCell class="px-6 py-4 whitespace-break-spaces font-medium">{#if row[variable]} {row[variable].value }{/if}</TableBodyCell>
{/each}
</TableBodyRow>
{/each}
</TableBody>
</Table>
{:else}
<span class="ml-2">{results}</span>
{/if}
</div>
{/if}
</div>

@ -1,78 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
sparql_update,
toast_error,
toast_success,
reset_toasts,
display_error,
} from "../store";
import {
in_memory_discrete, open_viewer, reset_in_memory
} from "../tab";
import{ Sun, RocketLaunch } from "svelte-heros-v2";
import { t } from "svelte-i18n";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import CodeMirror from "svelte-codemirror-editor";
import {StreamLanguage} from "@codemirror/language"
import { sparql } from "@codemirror/legacy-modes/mode/sparql";
import {basicSetup} from "codemirror"
onMount(()=>{
reset_in_memory();
if (!$in_memory_discrete){
$in_memory_discrete = "INSERT DATA { \n <> <example:predicate> \"An example value\".\r}";
}
});
const run = async () => {
try{
await reset_toasts();
await sparql_update($in_memory_discrete);
toast_success($t("app.sparql_update_editor.success"));
} catch(e) {
toast_error(display_error(e));
}
}
const openViewer = () => {
reset_toasts();
open_viewer();
}
</script>
<div class="flex-col">
<CodeMirror bind:value={$in_memory_discrete} lineWrapping useTab={false} extensions={[basicSetup,StreamLanguage.define(sparql)]} styles={{
"&": {
maxWidth: "100%",
},
}}/>
<button
on:click={run}
on:keypress={run}
class="select-none ml-2 mt-2 mb-10 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<RocketLaunch tabindex="-1" class="mr-2 focus:outline-none" />
{$t("doc.run_update")}
</button>
<button
on:click={openViewer}
on:keypress={openViewer}
class="select-none ml-2 mt-2 mb-10 text-gray-600 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<Sun class="mr-2 focus:outline-none" tabindex="-1" />
{$t("doc.view_graph")}
</button>
</div>

@ -1,129 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
sparql_update,
toast_error,
toast_success
} from "../store";
import {
in_memory_discrete, open_viewer, set_viewer, set_editor, set_view_or_edit, cur_tab_branch_class, cur_tab_doc_can_edit
} from "../tab";
import{ PencilSquare, RocketLaunch } from "svelte-heros-v2";
import { t } from "svelte-i18n";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import * as Y from 'yjs'
import Highlight, { LineNumbers, HighlightSvelte } from "svelte-highlight";
import typescript from "svelte-highlight/languages/typescript";
import javascript from "svelte-highlight/languages/javascript";
import rust from "svelte-highlight/languages/rust";
import "svelte-highlight/styles/github.css";
const class_to_lang = {
"code:js" : javascript,
"code:ts" : typescript,
"code:rust" : rust,
"code:react" : javascript,
}
let language;
$: language = $cur_tab_branch_class && class_to_lang[$cur_tab_branch_class]
export let commits = {};
let source = "";
const ydoc = new Y.Doc()
const ytext = ydoc.getText('ng');
let loading = true;
ydoc.on('destroy', async () => {
commits.discrete?.deregisterOnUpdate();
})
onMount(()=>{
let history = commits.discrete?.registerOnUpdate((update) => {
Y.applyUpdate(ydoc, update.YText, {local:true})
source = ytext.toString()
});
for (const h of history) {
Y.applyUpdate(ydoc, h.YText, {local:true})
}
source = ytext.toString()
loading = false;
});
onDestroy(()=>{
ydoc.destroy();
});
const edit = () => {
set_view_or_edit(false);
}
</script>
<div class="flex-col">
{#if loading}
<div class="grow flex flex-col justify-center text-primary-700">
<svg
class="animate-spin mt-4 h-10 w-10 mb-4 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<p class="text-center">{$t("connectivity.loading")}...</p>
</div>
{/if}
{#if source}
{#if $cur_tab_branch_class === "code:svelte"}
<HighlightSvelte code={source} class="mb-10" let:highlighted>
<LineNumbers {highlighted} wrapLines hideBorder />
</HighlightSvelte>
{:else if language}
<Highlight {language} code={source} class="mb-10" let:highlighted>
<LineNumbers {highlighted} wrapLines hideBorder />
</Highlight>
{:else}
<p class="font-mono whitespace-pre-wrap p-5">
{source}
</p>
{/if}
{:else if $cur_tab_doc_can_edit}
<button
on:click={edit}
on:keypress={edit}
class="select-none ml-5 mt-2 mb-10 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<PencilSquare tabindex="-1" class="mr-2 focus:outline-none" />
{$t("doc.start_editing")}
</button>
{/if}
</div>

@ -1,84 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
sparql_update,
toast_error,
toast_success
} from "../store";
import {
in_memory_discrete, open_viewer, set_viewer, set_editor, set_view_or_edit, cur_tab_doc_can_edit
} from "../tab";
import{ PencilSquare, RocketLaunch } from "svelte-heros-v2";
import { t } from "svelte-i18n";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import Highlight, { LineNumbers } from "svelte-highlight";
import hljs from "highlight.js";
import { definer } from "../turtle";
import "svelte-highlight/styles/github.css";
const language = {
name: "turtle",
register: (hljs) => {
return definer(hljs);
},
};
export let commits = {graph:[]};
let source = "";
$: source = commits.graph.join(" .\r\n") + (commits.graph.length ? " .":"");
const openQuery = () => {
set_viewer("n:g:z:sparql_query");
}
const openUpdate = () => {
set_editor("n:g:z:sparql_update");
set_view_or_edit(false);
}
onMount(()=>{
});
</script>
<div class="flex-col">
{#if !source}
<p class="p-3">{$t("doc.no_triples")}</p>
{/if}
<button
on:click={openQuery}
on:keypress={openQuery}
class="select-none ml-2 mt-2 mb-2 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<RocketLaunch tabindex="-1" class="mr-2 focus:outline-none" />
{$t("doc.sparql_query")}
</button>
{#if $cur_tab_doc_can_edit}
<button
on:click={openUpdate}
on:keypress={openUpdate}
class="select-none ml-2 mt-2 text-gray-600 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<PencilSquare class="mr-2 focus:outline-none" tabindex="-1" />
{$t("doc.sparql_update")}
</button>
{/if}
{#if source}
<Highlight {language} code={source} class="mb-10" let:highlighted >
<LineNumbers {highlighted} wrapLines hideBorder />
</Highlight>
{/if}
</div>

@ -1,131 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<!--
We could maybe also use https://ssssota.github.io/svelte-exmarkdown/ for rendering the MD (but to obtain the MD, we need to instantiate Milkdown anyway)
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
toast_error,
toast_success,
reset_toasts,
display_error,
live_discrete_update,
discrete_update
} from "../store";
import {
cur_tab_register_on_save,
cur_tab_deregister_on_save,
cur_tab_branch_class,
set_view_or_edit
} from "../tab";
import { t } from "svelte-i18n";
import{ PencilSquare } from "svelte-heros-v2";
import * as Y from 'yjs'
import xml from "svelte-highlight/languages/xml";
import Highlight, { LineNumbers } from "svelte-highlight";
import "svelte-highlight/styles/github.css";
import beautify from "xml-beautifier";
export let commits = {};
const ydoc = new Y.Doc();
const yxml = ydoc.getXmlFragment('prosemirror');
let has_content = true;
let loading = true;
let source = "";
function process_doc() {
source = beautify("<?xml version=\"1.0\" encoding=\"utf-8\"?>"+yxml.toString());
//console.log(source);
}
async function setup() {
try {
ydoc.on('destroy', async () => {
})
has_content = false;
let history = commits.discrete?.registerOnUpdate((update) => {
Y.applyUpdate(ydoc, update.YXml, {local:true})
has_content = true;
process_doc();
});
for (const h of history) {
Y.applyUpdate(ydoc, h.YXml, {local:true})
has_content = true;
}
if (has_content) process_doc();
loading = false;
}
catch (e){
console.log(e)
}
}
onMount(async ()=>{
await setup();
});
onDestroy(async ()=>{
ydoc.destroy();
commits.discrete?.deregisterOnUpdate();
});
</script>
{#if !has_content}
<p class="ml-5">{$t("doc.empty")}</p>
{/if}
{#if loading}
<div class="grow flex flex-col justify-center text-primary-700">
<svg
class="animate-spin mt-4 h-10 w-10 mb-4 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<p class="text-center">{$t("connectivity.loading")}...</p>
</div>
{/if}
{#if source}
<Highlight language={xml} code={source} class="mb-10" let:highlighted>
<LineNumbers {highlighted} wrapLines hideBorder />
</Highlight>
{/if}

@ -1,19 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import YMapEditor from "./YMapEditor.svelte";
export let commits = {};
</script>
<YMapEditor {commits} crdt="YArray"/>

@ -1,19 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import YMapSource from "./YMapSource.svelte";
export let commits = {};
</script>
<YMapSource {commits} crdt="YArray"/>

@ -1,19 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import YMapViewer from "./YMapViewer.svelte";
export let commits = {};
</script>
<YMapViewer {commits} crdt="YArray"/>

@ -1,291 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
toast_error,
toast_success,
reset_toasts,
display_error,
live_discrete_update,
discrete_update
} from "../store";
import {
cur_tab_register_on_save,
cur_tab_deregister_on_save,
cur_tab_branch_class
} from "../tab";
import { t } from "svelte-i18n";
import * as Y from 'yjs'
import { JSONEditor } from 'svelte-jsoneditor'
export let commits = {};
export let crdt = "YMap";
const ydoc = new Y.Doc()
const ymap = ydoc.get('ng', crdt == "YMap" ? Y.Map : Y.Array)
let editor;
let content = {
text: undefined,
json: crdt=="YMap"? {
} : []
}
ymap.observeDeep((events, transaction) => {
if (transaction.origin.local) {
let operations = [];
events.forEach((event) => {
let target = ymap;
let path = "";
event.path.forEach((p)=> { target = target.get(p); path += `/${p}`;});
event.changes.keys.forEach((change, key) => {
if (change.action === 'add') {
let newval = target.get(key);
if ( newval instanceof Y.Array) newval = newval.toJSON();
else if ( newval instanceof Y.Map) newval = newval.toJSON();
//console.log(`Property "${key}" was added in path "${path}". Initial value: "`,newval)
let p = path + `/${key}`;
operations.push({ op: 'add', path:p, value: newval });
} else if (change.action === 'update') {
let newval = target.get(key);
if ( newval instanceof Y.Array) newval = newval.toJSON();
else if ( newval instanceof Y.Map) newval = newval.toJSON();
//console.log(`Property "${key}" was updated in path "${path}". Previous value: "${change.oldValue}". New value: `, newval)
let p = path + `/${key}`;
operations.push({ op: 'replace', path:p, value: newval });
} else if (change.action === 'delete') {
//console.log(`Property "${key}" was deleted in path "${path}". Previous value: "${change.oldValue}".`)
let p = path + `/${key}`;
operations.push({ op: 'remove', path:p });
}
});
let pos = 0;
event.changes.delta.forEach((delta) => {
if (delta.retain) pos += delta.retain;
else if (delta.insert && Array.isArray(delta.insert)) {
delta.insert.forEach((newval) => {
let p = path + `/${pos}`;
if ( newval instanceof Y.Array) newval = newval.toJSON();
else if ( newval instanceof Y.Map) newval = newval.toJSON();
//console.log(`Adding array element to path "${p}". New value: `, newval)
operations.push({ op: 'add', path:p, value: newval });
pos += 1;
});
} else if (delta.delete) {
let p = path + `/${pos}`;
for (let i=0; i< delta.delete; i++) {
//console.log(`removing array element in path "${p}"`)
operations.push({ op: 'remove', path:p });
}
}
});
});
editor.patch(operations);
content.json = ymap.toJSON();
}
});
ydoc.on('update', async (update, origin) => {
//console.log(update, origin)
if (!origin.local) {
try {
await discrete_update(update, crdt, commits.heads);
} catch (e){
toast_error(display_error(e));
}
}
})
function process_value(val) {
let value;
if (Array.isArray(val)) {
const subArray = new Y.Array();
for (let i=0; i<val.length; i++) {
let r = process_value(val[i]);
subArray.insert(i, [r]);
}
value = subArray;
} else if (typeof val === 'object' && val !== null) {
const ymapNested = new Y.Map();
for (const [key, value] of Object.entries(val)) {
ymapNested.set(key, process_value(value));
}
value = ymapNested;
} else {
value = val;
}
return value;
}
function handleChange(updatedContent, previousContent, { contentErrors, patchResult }) {
// content is an object { json: unknown } | { text: string }
//console.log('onChange: ', patchResult?.redo, patchResult, updatedContent)
if (patchResult) {
ydoc.transact((transac) => {
patchResult.redo.forEach((op)=>{
let path = op.path.split("/");
path.shift();
let key = path.pop();
let target = ymap;
path.forEach((p)=> { target = target.get(p);});
if (op.op == "add") {
//console.log("adding", op.value,key, op.path)
let value = process_value(op.value);
if (target instanceof Y.Map) {
target.set(key, value);
} else {
target.insert(Number(key), [value]);
}
} else if (op.op == "remove") {
//console.log("removing", key, op.path)
if (target instanceof Y.Map) {
target.delete(key);
} else {
target.delete(Number(key), 1);
}
} else if (op.op == "replace") {
//console.log("replacing", op.value, key, op.path)
if (key === undefined) {
if (crdt === "YArray" && Array.isArray(op.value)) {
if (target.length) target.delete(0,target.length);
op.value.forEach((v)=> {
target.push([process_value(v)]);
});
} else if (crdt === "YMap" && (typeof op.value === 'object' && op.value !== null)) {
target.clear();
for (const [key, value] of Object.entries(op.value)) {
target.set(key, process_value(value));
}
}
return;
}
if (target instanceof Y.Map) {
target.set(key, process_value(op.value));
} else {
let idx = Number(key);
target.delete(idx, 1);
target.insert(idx, [process_value(op.value)]);
}
} else if (op.op == "move" || op.op == "copy") {
let move = op.op == "move";
if (op.from === op.path) return;
//console.log("moving or copying", op.from, op.path)
let from = op.from.split("/");
from.shift();
let from_key = from.pop();
let origin = ymap;
from.forEach((p)=> { origin = origin.get(p);});
from_key = (origin instanceof Y.Map) ? from_key : Number(from_key);
let value_to_move = origin.get(from_key);
if ( value_to_move instanceof Y.Array) value_to_move = value_to_move.clone();
else if ( value_to_move instanceof Y.Map) value_to_move = value_to_move.clone();
if (target instanceof Y.Map) {
target.set(key, value_to_move);
} else {
let idx = Number(key);
target.insert(idx, [value_to_move]);
}
if (move) {
if (typeof from_key === "number") {
origin.delete(from_key, 1);
} else {
origin.delete(from_key);
}
}
}
});
} , {local:false});
}
content = updatedContent
}
ydoc.on('destroy', async () => {
commits.discrete?.deregisterOnUpdate();
await cur_tab_deregister_on_save();
})
onMount(async ()=>{
cur_tab_register_on_save(async (updates)=>{
let update = Y.mergeUpdates(updates);
await live_discrete_update(update, crdt, commits.heads);
});
let history = commits.discrete?.registerOnUpdate((update) => {
Y.applyUpdate(ydoc, update[crdt], {local:true})
});
for (const h of history) {
Y.applyUpdate(ydoc, h[crdt], {local:true})
}
await editor.focus()
});
onDestroy(async ()=>{
ydoc.destroy();
await editor.destroy();
editor = undefined;
});
function onRenderMenu(items, context) {
items.shift();
items.pop();
items.pop();
items.pop();
return items;
}
function onRenderContextMenu(items, context) {
if (items[4].items[1].items[0].text == "Convert to:") items[4].items.pop();
if (Array.isArray(context.selection?.path) && context.selection.path.length == 0 && context.selection.type === "value") {
items[2].items.shift();
items[2].items.pop();
items[4].items[0].items.pop();
}
return items;
}
</script>
<div class="grow ng-json-editor" style="min-height:300px;">
<JSONEditor bind:this={editor} {content} onChange={handleChange} {onRenderMenu} {onRenderContextMenu}/>
</div>
<style>
.ng-json-editor {
/* define a custom theme color */
--jse-theme-color: rgb(73, 114, 165);
--jse-theme-color-highlight: rgb(30 136 229);
}
</style>

@ -1,79 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
sparql_update,
toast_error,
toast_success
} from "../store";
import {
set_view_or_edit, cur_tab_doc_can_edit
} from "../tab";
import{ PencilSquare, } from "svelte-heros-v2";
import { t } from "svelte-i18n";
import Highlight, { LineNumbers } from "svelte-highlight";
import json from "svelte-highlight/languages/json";
import "svelte-highlight/styles/github.css";
import * as Y from 'yjs'
let source = "";
const edit = () => {
set_view_or_edit(false);
}
export let commits = {};
export let crdt = "YMap";
const ydoc = new Y.Doc()
const ymap = ydoc.get('ng', crdt == "YMap" ? Y.Map : Y.Array)
ydoc.on('destroy', async () => {
commits.discrete?.deregisterOnUpdate();
})
onMount(()=>{
let history = commits.discrete?.registerOnUpdate((update) => {
Y.applyUpdate(ydoc, update[crdt], {local:true})
source = JSON.stringify(ymap.toJSON(),null , 4 );
});
for (const h of history) {
if (h[crdt]) Y.applyUpdate(ydoc, h[crdt], {local:true})
}
source = JSON.stringify(ymap.toJSON(), null , 4 );
});
</script>
<div class="flex-col">
{#if source}
<Highlight language={json} code={source} class="mb-10" let:highlighted >
<LineNumbers {highlighted} wrapLines hideBorder />
</Highlight>
{:else if $cur_tab_doc_can_edit}
<button
on:click={edit}
on:keypress={edit}
class="select-none ml-2 mt-2 mb-10 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<PencilSquare class="mr-2 focus:outline-none" tabindex="-1" />
{$t("doc.start_editing")}
</button>
{/if}
</div>

@ -1,189 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, tick, onDestroy } from "svelte";
import {
toast_error,
toast_success,
reset_toasts,
display_error,
live_discrete_update,
discrete_update
} from "../store";
import {
cur_tab_doc_can_edit,
set_view_or_edit
} from "../tab";
import { t } from "svelte-i18n";
import{ PencilSquare } from "svelte-heros-v2";
import * as Y from 'yjs'
import { JSONEditor } from 'svelte-jsoneditor'
export let commits = {};
export let crdt = "YMap";
const ydoc = new Y.Doc()
const ymap = ydoc.get('ng', crdt == "YMap" ? Y.Map : Y.Array)
let editor;
let loading = true;
let content = {
text: undefined,
json: crdt=="YMap"? {
} : []
}
ymap.observeDeep((events, transaction) => {
if (transaction.origin.local) {
let operations = [];
events.forEach((event) => {
let target = ymap;
let path = "";
event.path.forEach((p)=> { target = target.get(p); path += `/${p}`;});
event.changes.keys.forEach((change, key) => {
if (change.action === 'add') {
let newval = target.get(key);
if ( newval instanceof Y.Array) newval = newval.toJSON();
else if ( newval instanceof Y.Map) newval = newval.toJSON();
//console.log(`Property "${key}" was added in path "${path}". Initial value: "`,newval)
let p = path + `/${key}`;
operations.push({ op: 'add', path:p, value: newval });
} else if (change.action === 'update') {
let newval = target.get(key);
if ( newval instanceof Y.Array) newval = newval.toJSON();
else if ( newval instanceof Y.Map) newval = newval.toJSON();
//console.log(`Property "${key}" was updated in path "${path}". Previous value: "${change.oldValue}". New value: `, newval)
let p = path + `/${key}`;
operations.push({ op: 'replace', path:p, value: newval });
} else if (change.action === 'delete') {
//console.log(`Property "${key}" was deleted in path "${path}". Previous value: "${change.oldValue}".`)
let p = path + `/${key}`;
operations.push({ op: 'remove', path:p });
}
});
let pos = 0;
event.changes.delta.forEach((delta) => {
if (delta.retain) pos += delta.retain;
else if (delta.insert && Array.isArray(delta.insert)) {
delta.insert.forEach((newval) => {
let p = path + `/${pos}`;
if ( newval instanceof Y.Array) newval = newval.toJSON();
else if ( newval instanceof Y.Map) newval = newval.toJSON();
//console.log(`Adding array element to path "${p}". New value: `, newval)
operations.push({ op: 'add', path:p, value: newval });
pos += 1;
});
} else if (delta.delete) {
let p = path + `/${pos}`;
for (let i=0; i< delta.delete; i++) {
//console.log(`removing array element in path "${p}"`)
operations.push({ op: 'remove', path:p });
}
}
});
});
editor.patch(operations);
content.json = ymap.toJSON();
loading = false;
}
});
ydoc.on('destroy', async () => {
commits.discrete?.deregisterOnUpdate();
})
onMount(async ()=>{
let history = commits.discrete?.registerOnUpdate((update) => {
Y.applyUpdate(ydoc, update[crdt], {local:true})
});
for (const h of history) {
Y.applyUpdate(ydoc, h[crdt], {local:true})
}
loading = false;
});
onDestroy(async ()=>{
ydoc.destroy();
await editor.destroy();
editor = undefined;
});
const edit = () => {
set_view_or_edit(false);
}
</script>
{#if loading}
<div class="grow mb-4 flex flex-col justify-center text-primary-700">
<svg
class="animate-spin mt-4 h-10 w-10 mb-4 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<p class="text-center">{$t("connectivity.loading")}...</p>
</div>
{/if}
{#if $cur_tab_doc_can_edit && ( crdt=="YMap" && Object.keys(content.json).length == 0 || crdt=="YArray" && Array.isArray(content.json) && content.json.length == 0 ) }
<div class="flex-row">
<button
on:click={edit}
on:keypress={edit}
class="shrink select-none ml-4 mt-2 mb-4 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
<PencilSquare class="mr-2 focus:outline-none" tabindex="-1" />
{$t("doc.start_editing")}
</button>
</div>
{/if}
<div class="grow ng-json-editor" style="min-height:300px;">
<JSONEditor bind:this={editor} {content} readOnly={true} />
</div>
<style>
.ng-json-editor {
/* define a custom theme color */
--jse-theme-color: rgb(73, 114, 165);
--jse-theme-color-highlight: rgb(30 136 229);
}
</style>

@ -1,36 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { Toggle } from 'flowbite-svelte';
const dispatch = createEventDispatcher();
export let value;
function update() {
temp_val = value;
}
let temp_val;
$: value, update();
const change = (event) => {
dispatch('updateScalar', {
v: temp_val,
});
}
</script>
<Toggle bind:checked={temp_val} on:change={change} />

@ -1,65 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
import { next as A } from "@automerge/automerge/slim";
export let value;
export let proxy;
export let doc;
async function increment(val) {
doc = A.change(doc, (d) => {
proxy.increment(val);
});
let update = A.getLastLocalChange(doc);
dispatch('update', {
u: update,
d: doc,
});
}
function update() {
temp_val = value.value;
previous_val = value.value;
}
let temp_val;
let previous_val;
$: value, update();
const inc = async () => { temp_val+=1; await increment(1) }
const dec = async () => { temp_val-=1; await increment(-1) }
const change = async () => { let diff = Math.round(temp_val - previous_val); if (diff !==0) await increment(diff); else temp_val = previous_val; }
</script>
<div class="relative flex items-center max-w-[8rem]">
<button type="button" on:click={dec} class="bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 dark:border-gray-600 hover:bg-gray-200 border border-gray-300 rounded-s-lg p-2 h-7 focus:ring-gray-100 dark:focus:ring-gray-700 focus:ring-2 focus:outline-none">
<svg class="w-3 h-3 text-gray-900 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 2">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h16"/>
</svg>
</button>
<input bind:value={temp_val} on:change={change} type="text" id="quantity-input" data-input-counter aria-describedby="helper-text-explanation" style="max-width:70px;" class="bg-gray-50 border-x-0 border-gray-300 h-7 text-center text-gray-900 text-sm focus:ring-blue-500 focus:border-blue-500 block w-full py-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="999" required />
<button type="button" on:click={inc} class="bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 dark:border-gray-600 hover:bg-gray-200 border border-gray-300 rounded-e-lg p-2 h-7 focus:ring-gray-100 dark:focus:ring-gray-700 focus:ring-2 focus:outline-none">
<svg class="w-3 h-3 text-gray-900 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 18">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 1v16M1 9h16"/>
</svg>
</button>
</div>

@ -1,71 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
export let value;
function update() {
time = value.toLocaleTimeString([],{
hour: "2-digit",
minute: "2-digit"
});
date = value.toLocaleDateString([],{
year: "numeric",
month: "numeric",
day: "numeric",
});
}
let time;
let date;
$: value, update();
const change = (event) => {
let newval = new Date(date.split('/').reverse().join('/')+" "+time);
//console.log(time, date, newval)
dispatch('updateScalar', {
v: newval,
});
}
</script>
<div class="flex flex-wrap">
<div class="relative" style="max-width: 129px;">
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
<svg class="w-4 h-4 text-primary-700 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z"/>
</svg>
</div>
<input type="text" style="max-width: 129px;cursor:pointer;" on:change={change} bind:value={date} datepicker-format="dd/mm/yyyy"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 px-2 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Select date">
</div>
<div class="relative" style="width: 129px;">
<div class="absolute inset-y-0 end-0 top-0 flex items-center pe-3.5 pointer-events-none">
<svg class="w-4 h-4 text-primary-700 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm11-4a1 1 0 1 0-2 0v4a1 1 0 0 0 .293.707l3 3a1 1 0 0 0 1.414-1.414L13 11.586V8Z" clip-rule="evenodd"/>
</svg>
</div>
<input bind:value={time} on:change={change} type="time" class="bg-gray-50 border leading-none border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" />
</div>
</div>

@ -1,118 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { new_value, find_type, new_prop_types } from "./utils";
import AValue from "./AValue.svelte";
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
import { next as A } from "@automerge/automerge/slim";
export let value;
export let proxy;
export let doc;
export let path = undefined;
export let readonly = false;
let props = [];
$: props = value.map((v,i)=> {
let ar = [i];
ar.push(v);
let type = find_type(v);
ar.push(type);
const with_proxy = type == "counter" || type == "map" || type == "list" ;
if (with_proxy) {
ar.push(proxy[i]);
}
return ar;
});
function add_prop() {
doc = A.change(doc, (d) => {
proxy.push(new_value(new_prop_type_selected))
});
let update = A.getLastLocalChange(doc);
dispatch('update', {
u: update,
d: doc,
});
}
let new_prop_type_selected = 'text';
function updateText(event) {
if (path !== undefined) event.detail.p.unshift(path);
dispatch('updateText', {
s: event.detail.s,
p: event.detail.p,
});
}
function updateScalar(prop, event) {
doc = A.change(doc, (d) => {
proxy[prop] = event.detail.v;
});
let update = A.getLastLocalChange(doc);
dispatch('update', {
u: update,
d: doc,
});
}
</script>
<table class="border-collapse border border-slate-400">
<thead>
<tr class="bg-slate-100">
<th>List</th>
<th class="text-sm">
{#if !readonly}
<span class="ml-2">Push entry at the end of list:</span>
<select bind:value={new_prop_type_selected}>
{#each new_prop_types as value}<option value={value.value}>{value.name}</option>{/each}
</select>
<button on:click={add_prop}>Add</button>
{/if}
</th>
</tr>
</thead>
<tbody>
{#each props as prop}
<tr>
<td>{prop[0]}</td>
<!-- <td>{prop[2]}</td> -->
<td>
<AValue {readonly} type={prop[2]} value={prop[1]} {doc} on:updateText={updateText} on:update proxy={prop[3]} path={prop[0]} on:updateScalar={(event)=>updateScalar(prop[0],event)} />
</td>
<!-- <td>{prop[3]?.constructor.name || ""}</td> -->
</tr>
{/each}
</tbody>
</table>
<style>
td {
padding:5px;
}
tr {
border-bottom: 1px;
border-style: dashed;
border-top: none;
}
</style>

@ -1,131 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import AValue from "./AValue.svelte";
import { createEventDispatcher } from 'svelte';
import { new_value, find_type, new_prop_types } from "./utils";
const dispatch = createEventDispatcher();
import { next as A } from "@automerge/automerge/slim";
export let value;
export let proxy;
export let doc;
export let path = undefined;
export let readonly = false;
let props = [];
$: props = Object.entries(value).map((ar)=> {
let type = find_type(ar[1]);
ar.push(type);
const with_proxy = type == "counter" || type == "map" || type == "list" ;
if (with_proxy) {
ar.push(proxy[ar[0]]);
}
return ar;
});
let new_prop = "";
function add_prop() {
if (new_prop.trim().length > 0) {
doc = A.change(doc, (d) => {
proxy[new_prop] = new_value(new_prop_type_selected);
});
let update = A.getLastLocalChange(doc);
dispatch('update', {
u: update,
d: doc,
});
new_prop = "";
}
}
let new_prop_type_selected = 'text';
function updateText(event) {
if (path!== undefined) event.detail.p.unshift(path);
dispatch('updateText', {
s: event.detail.s,
p: event.detail.p,
});
}
function updateScalar(prop, event) {
doc = A.change(doc, (d) => {
proxy[prop] = event.detail.v;
});
let update = A.getLastLocalChange(doc);
dispatch('update', {
u: update,
d: doc,
});
}
</script>
<table class="border-collapse border border-slate-400">
<thead>
<tr class="bg-slate-100">
<th>Map</th>
<th class="text-sm">
{#if !readonly}
<span>Add property:</span>
<input placeholder="Enter the name of property" bind:value={new_prop} class="prop-input"/>
<select bind:value={new_prop_type_selected}>
{#each new_prop_types as value}<option value={value.value}>{value.name}</option>{/each}
</select>
<button on:click={add_prop}>Add</button>
{/if}
</th>
</tr>
</thead>
<tbody>
{#each props as prop}
<tr>
<td>{prop[0]}</td>
<!-- <td>{prop[2]}</td> -->
<td>
<AValue {readonly} type={prop[2]} value={prop[1]} {doc} on:updateText={updateText} on:update proxy={prop[3]} path={prop[0]} on:updateScalar={(event)=>updateScalar(prop[0],event)} />
</td>
<!-- <td>{prop[3]?.constructor.name || ""}</td> -->
</tr>
{/each}
</tbody>
</table>
<style>
td {
padding:5px;
min-width:80px;
}
tr {
border-bottom: 1px;
border-style: dashed;
border-top: none;
}
@screen xs {
.prop-input {
min-width: 250px;
}
}
</style>

@ -1,42 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { Input } from 'flowbite-svelte';
const dispatch = createEventDispatcher();
export let value;
function update() {
temp_val = value;
previous_val = value;
}
let temp_val;
let previous_val;
$: value, update();
const change = (event) => {
let newval = parseFloat(event.target.value.replace(",", "."));
//console.log(previous_val, temp_val, newval)
if (isNaN(newval) || previous_val === newval) return;
dispatch('updateScalar', {
v: newval,
});
}
</script>
<Input style="max-width: 129px;" bind:value={temp_val} on:change={change} on:keyup={change} type="number" />

@ -1,43 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { Input } from 'flowbite-svelte';
const dispatch = createEventDispatcher();
export let value;
export let path;
function update() {
temp_val = value;
previous_val = value;
}
let temp_val;
let previous_val;
$: value, update();
const change = (event) => {
if (previous_val!=temp_val)
dispatch('updateText', {
s: event.target.value,
p: [path]
});
}
</script>
<Input bind:value={temp_val} on:keyup={change} type="text" placeholder="Enter some text" />

@ -1,87 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import AMap from "./AMap.svelte";
import AList from "./AList.svelte";
import ACounter from "./ACounter.svelte";
import AString from "./AString.svelte";
import ABoolean from "./ABoolean.svelte";
import ANumber from "./ANumber.svelte";
import ADate from "./ADate.svelte";
export let value;
export let type;
export let doc;
export let proxy;
export let path;
export let readonly = false;
function render_date(value) {
let time = value.toLocaleTimeString([],{
hour: "2-digit",
minute: "2-digit"
});
let date = value.toLocaleDateString([],{
year: "numeric",
month: "numeric",
day: "numeric",
});
return `${date} ${time}`;
}
</script>
{#if type==="map"}
<AMap {readonly} {value} {doc} on:updateText on:update {proxy} {path}/>
{:else if type==="list"}
<AList {readonly} {value} {doc} on:updateText on:update {proxy} {path}/>
{:else if type==="counter"}
{#if !readonly}
<ACounter {value} {doc} on:update {proxy} />
{:else}
: {value}
{/if}
{:else if type==="text"}
{#if !readonly}
<AString {value} on:updateText {path}/>
{:else}
: {value}
{/if}
{:else if type==="boolean"}
{#if !readonly}
<ABoolean {value} on:updateScalar/>
{:else}
: {value}
{/if}
{:else if type==="number"}
{#if !readonly}
<ANumber {value} on:updateScalar/>
{:else}
: {value}
{/if}
{:else if value?.toDateString || type==="timestamp"}
{#if !readonly}
<ADate {value} on:updateScalar/>
{:else}
: {render_date(value)}
{/if}
{:else}
: {value}
{/if}

@ -1,81 +0,0 @@
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
import { next as A } from "@automerge/automerge/slim";
const UINT = Symbol.for("_am_uint")
const INT = Symbol.for("_am_int")
const F64 = Symbol.for("_am_f64")
const COUNTER = Symbol.for("_am_counter")
const TEXT = Symbol.for("_am_text")
export function find_type(value) {
switch (typeof value) {
case "object":
if (value == null) {
return "null"
} else if (value[UINT]) {
return "uint"
} else if (value[INT]) {
return "number"
} else if (value[F64]) {
return "number"
} else if (value[COUNTER]) {
return "counter"
} else if (value instanceof Date) {
return "timestamp"
} else if (value instanceof A.RawString) {
return "str"
} else if (value instanceof Text) {
return "text"
} else if (value instanceof Uint8Array) {
return "bytes"
} else if (value instanceof Array) {
return "list"
} else if (Object.getPrototypeOf(value) === Object.getPrototypeOf({})) {
return "map"
}
case "boolean":
return "boolean"
case "number":
if (Number.isInteger(value)) {
return "number"
} else {
return "number"
}
case "string":
return "text"
}
}
export const new_prop_types = [
{value:'text',name:"text"},
{value:'number',name:"number"},
{value:'counter',name:"counter"},
{value:'boolean',name:"boolean"},
{value:'null',name:"null"},
{value:'timestamp',name:"timestamp"},
{value:'map',name:"map"},
{value:'list',name:"list"},
{value:'bytes',name:"bytes"}
];
export function new_value(new_prop_type_selected) {
switch (new_prop_type_selected) {
case 'text': return '';
case 'map': return {};
case 'list': return [];
case 'counter': return new A.Counter();
case 'number': return 0;
case 'boolean': return false;
case 'null': return null;
case 'timestamp': return new Date();
case 'bytes': return new Uint8Array(0);
}
}

@ -1,102 +0,0 @@
/**
MIT License
Copyright (c) 2022 Mox
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Source: https://github.com/HexMox/milkdown-plugin-placeholder
*/
import type { MilkdownPlugin, TimerType } from '@milkdown/ctx'
import type { EditorView } from '@milkdown/prose/view'
import { createSlice, createTimer } from '@milkdown/ctx'
import { InitReady, prosePluginsCtx } from '@milkdown/core'
import { Plugin, PluginKey } from '@milkdown/prose/state'
export const placeholderCtx = createSlice('Please input here...', 'placeholder')
export const placeholderTimerCtx = createSlice([] as TimerType[], 'editorStateTimer')
export const PlaceholderReady = createTimer('PlaceholderReady')
const key = new PluginKey('MILKDOWN_PLACEHOLDER')
export const placeholder: MilkdownPlugin = (ctx) => {
ctx.inject(placeholderCtx).inject(placeholderTimerCtx, [InitReady]).record(PlaceholderReady)
return async () => {
await ctx.waitTimers(placeholderTimerCtx)
const prosePlugins = ctx.get(prosePluginsCtx)
const update = (view: EditorView) => {
const placeholder = ctx.get(placeholderCtx)
const doc = view.state.doc
if (
view.editable &&
doc.childCount === 1 &&
doc.firstChild?.isTextblock &&
doc.firstChild?.content.size === 0 &&
doc.firstChild?.type.name === 'paragraph'
) {
view.dom.classList.add('editor_empty');
view.dom.setAttribute('data-placeholder', placeholder);
} else {
view.dom.classList.remove('editor_empty');
}
}
const plugins = [
...prosePlugins,
new Plugin({
key,
// props: {
// decorations(state) {
// const doc = state.doc
// if (
// doc.childCount === 1 &&
// doc.firstChild?.isTextblock &&
// doc.firstChild?.content.size === 0
// ) {
// return DecorationSet.create(doc, [
// Decoration.widget(1, (view) => {
// if (view.editable) {
// const span = document.createElement('span')
// span.classList.add('placeholder')
// span.textContent = placeholder
// return span
// }
// }),
// ])
// }
// },
// },
view(view) {
update(view)
return { update }
},
}),
]
ctx.set(prosePluginsCtx, plugins)
ctx.done(PlaceholderReady)
}
}

@ -1,59 +0,0 @@
/*
* Base64URL-ArrayBuffer
* https://github.com/herrjemand/Base64URL-ArrayBuffer
*
* Copyright (c) 2017 Yuriy Ackermann <ackermann.yuriy@gmail.com>
* Copyright (c) 2012 Niklas von Hertzen
* Licensed under the MIT license.
*
*/
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
// Use a lookup table to find the index.
var lookup = new Uint8Array(256);
for (var i = 0; i < chars.length; i++) {
lookup[chars.charCodeAt(i)] = i;
}
export const encode = function(arraybuffer) {
var bytes = new Uint8Array(arraybuffer),
i, len = bytes.length, base64 = "";
for (i = 0; i < len; i+=3) {
base64 += chars[bytes[i] >> 2];
base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
base64 += chars[bytes[i + 2] & 63];
}
if ((len % 3) === 2) {
base64 = base64.substring(0, base64.length - 1);
} else if (len % 3 === 1) {
base64 = base64.substring(0, base64.length - 2);
}
return base64;
};
export const decode = function(base64) {
var bufferLength = base64.length * 0.75,
len = base64.length, i, p = 0,
encoded1, encoded2, encoded3, encoded4;
var arraybuffer = new ArrayBuffer(bufferLength),
bytes = new Uint8Array(arraybuffer);
for (i = 0; i < len; i+=4) {
encoded1 = lookup[base64.charCodeAt(i)];
encoded2 = lookup[base64.charCodeAt(i+1)];
encoded3 = lookup[base64.charCodeAt(i+2)];
encoded4 = lookup[base64.charCodeAt(i+3)];
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
}
return arraybuffer;
};

File diff suppressed because it is too large Load Diff

@ -1,7 +1,7 @@
The MIT License (MIT)
Copyright (c) 2013 Nicolas CARLO and Fabien BERNARD
Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

@ -97,14 +97,7 @@ class GitgraphUserApi<TNode> {
data["parents"].forEach((parent) => {
let new_lane = this._graph.last_on_swimlanes.indexOf(parent);
if ( new_lane < lane ) {
if (lane != Number.MAX_VALUE)
this._graph.swimlanes[lane] = undefined;
lane = new_lane;
} else {
// we close that lane
//this._graph.swimlanes
this._graph.swimlanes[new_lane] = undefined;
}
});
branch = this._graph.swimlanes[lane];
@ -115,39 +108,35 @@ class GitgraphUserApi<TNode> {
this._graph.last_on_swimlanes[lane] = data["hash"];
} else {
branch = data["hash"];
this._graph.swimlanes.some((b, col) => {
// this._graph.swimlanes.some((b, col) => {
// console.log("is empty? ",col,!b);
if (!b) {
let r = this._graph.rows.getRowOf(this._graph.last_on_swimlanes[col]);
if (data["parents"].some( (parent) => this._graph.rows.getRowOf(parent) < r ))
return false;
lane = col;
return true;
}
});
if (!lane) {
// if (!b) {
// lane = col;
// return true;
// }
// });
// if (!lane) {
lane = this._graph.swimlanes.length;
this._graph.swimlanes.push(branch);
this._graph.last_on_swimlanes.push(branch);
} else {
this._graph.swimlanes[lane] = branch;
this._graph.last_on_swimlanes[lane] = branch;
}
// } else {
// this._graph.swimlanes[lane] = branch;
// this._graph.last_on_swimlanes[lane] = branch;
// }
}
// data["parents"].forEach((parent) => {
// let r = this._graph.rows.getRowOf(parent);
// let c = this._graph.commits[r];
// let b = c.branch;
// if (branch!=b) {
// this._graph.swimlanes.forEach((bb, col) => {
// if (bb == b) {
// this._graph.swimlanes[col] = undefined;
// }
// });
// }
// });
data["parents"].forEach((parent) => {
let r = this._graph.rows.getRowOf(parent);
let c = this._graph.commits[r];
let b = c.branch;
if (branch!=b) {
this._graph.swimlanes.forEach((bb, col) => {
if (bb == b) {
this._graph.swimlanes[col] = undefined;
}
});
}
});
// if (!this._graph.branches.has(branch)) {
// this._graph.createBranch({name:branch});

@ -100,8 +100,7 @@ function createGitgraph(
createG({
// Translate graph left => left-most branch label is not cropped (horizontal)
// Translate graph down => top-most commit tooltip is not cropped
translate: { x: 10, y: TOOLTIP_PADDING },
scale: 0.75,
translate: { x: 0, y: TOOLTIP_PADDING },
children: [renderBranchesPaths(branchesPaths), $commits],
}),
);
@ -313,17 +312,8 @@ function createGitgraph(
return message;
}
let msg = commit.message.split(" ");
const text = createText({
content: msg[0],
fill: commit.style.message.color || "",
font: commit.style.message.font,
onClick: commit.onMessageClick,
});
const text2 = createText({
content: msg[1],
content: commit.message,
fill: commit.style.message.color || "",
font: commit.style.message.font,
onClick: commit.onMessageClick,
@ -334,13 +324,6 @@ function createGitgraph(
children: [text],
});
let message2 = createG({
translate: { x: 0, y: commit.style.dot.size*2 },
children: [text2],
});
message.appendChild(message2);
if (commit.body) {
const body = createForeignObject({
width: 600,

@ -49,7 +49,6 @@ interface GOptions {
x: number;
y: number;
};
scale?: number;
fill?: string;
stroke?: string;
strokeWidth?: number;
@ -61,11 +60,11 @@ interface GOptions {
function createG(options: GOptions): SVGGElement {
const g = document.createElementNS(SVG_NAMESPACE, "g");
options.children.forEach((child) => child && g.appendChild(child));
let scale = options.scale ? ` scale(${options.scale})`: "";
if (options.translate) {
g.setAttribute(
"transform",
`translate(${options.translate.x}, ${options.translate.y})${scale}`,
`translate(${options.translate.x}, ${options.translate.y})`,
);
}
@ -119,7 +118,7 @@ function createText(options: TextOptions): SVGTextElement {
}
if (options.font) {
text.setAttribute("style", `font-family:monospace;font: Courier`);
text.setAttribute("style", `font: ${options.font}`);
}
if (options.anchor) {

@ -11,7 +11,7 @@ function createTooltip(commit: Commit): SVGElement {
const path = createPath({ d: "", fill: "#EEE" });
const text = createText({
translate: { x: OFFSET + PADDING, y: 0 },
content: `${commit.hashAbbrev}`,
content: `${commit.hashAbbrev} - ${commit.subject}`,
fill: "#333",
});

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -62,7 +62,7 @@
<div class="mb-20 mt-10">
<button
on:click={changeLang}
class="text-primary-700 bg-white bg-none ring-0 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55"
class="text-primary-700 bg-[#f6f6f6] bg-none ring-0 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55"
>
<Language
tabindex="-1"
@ -72,7 +72,7 @@
<br />
<button
on:click={displayNextgraphOrg}
class="text-primary-700 bg-white bg-none ring-0 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
class="text-primary-700 bg-[#f6f6f6] bg-none ring-0 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
>
{$t("common.about_nextgraph")}
</button>
@ -106,5 +106,7 @@
text-align: center;
width: fit-content;
}
li.clickable {
cursor: pointer;
}
</style>

@ -0,0 +1,187 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import {
Icon,
BugAnt,
DocumentText,
Window,
CodeBracket,
SquaresPlus,
ViewfinderCircle,
ArrowsPointingOut,
Cube,
Briefcase,
MagnifyingGlass,
RocketLaunch,
Sun,
TableCells,
ListBullet,
RectangleGroup,
Squares2x2,
MapPin,
CircleStack,
Envelope,
GlobeAlt,
DocumentChartBar,
Document,
ClipboardDocumentList,
Photo,
Film,
RectangleStack,
Microphone,
MusicalNote,
Ticket,
CursorArrowRays,
Megaphone,
User,
Clock,
CalendarDays,
Calendar,
Stop,
Flag,
HandRaised,
Newspaper,
PencilSquare,
CubeTransparent,
PresentationChartBar,
QuestionMarkCircle,
CheckCircle,
ChartPie,
Bars3BottomLeft,
Link,
Square2Stack,
Clipboard,
StopCircle,
Bolt,
Heart,
} from "svelte-heros-v2";
export let config = {};
export let dataClass: string;
const exact_mapping = {
page: Window,
"app/z": SquaresPlus,
class: ViewfinderCircle,
contract: Briefcase,
"query/text": MagnifyingGlass,
"query/web": MagnifyingGlass,
"data/graph": Sun,
"data/table": TableCells,
"data/collection": ListBullet,
"data/board": RectangleGroup,
"data/grid": Squares2x2,
"data/geomap": MapPin,
"e/email": Envelope,
"mc/text": Bars3BottomLeft,
"mc/link": Link,
"plato/card": Clipboard,
"plato/pad": Square2Stack,
"media/image": Photo,
"media/reel": Film,
"media/video": Film,
"media/album": RectangleStack,
"media/audio": Microphone,
"media/song": MusicalNote,
"media/subtitle": Ticket,
"media/overlay": CursorArrowRays,
"social/channel": Megaphone,
"social/stream": Bolt,
"social/contact": User,
"social/event": Clock,
"social/calendar": CalendarDays,
"social/scheduler": Calendar,
"social/reaction": Heart,
"prod/task": Stop,
"prod/project": Flag,
"prod/issue": HandRaised,
"prod/form": Newspaper,
"prod/filling": PencilSquare,
"prod/cad": CubeTransparent,
"prod/slides": PresentationChartBar,
"prod/question": QuestionMarkCircle,
"prod/answer": CheckCircle,
"prod/poll": QuestionMarkCircle,
"prod/vote": CheckCircle,
};
const prefix_mapping = {
"post/": DocumentText,
code: CodeBracket,
schema: ArrowsPointingOut,
service: Cube,
"e/": GlobeAlt,
"app/": StopCircle,
"query/": RocketLaunch,
"data/": CircleStack,
"doc/diagram": DocumentChartBar,
"doc/chart": ChartPie,
"doc/viz": ChartPie,
"doc/": ClipboardDocumentList,
file: Document,
};
const find = (t) => {
let e = exact_mapping[t];
if (e) return e;
for (let prefix of Object.entries(prefix_mapping)) {
if (t.startsWith(prefix[0])) return prefix[1];
}
return BugAnt;
};
</script>
<!--
did:ng:n:g:z:[official apps]
did:ng:n:g:ns
did:ng:n:g:x list of context used by nextgraph
rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns#
rdfs: http://www.w3.org/2000/01/rdf-schema#
schema: https://schema.org/
skos: http://www.w3.org/2004/02/skos/core#
owl: http://www.w3.org/2002/07/owl#
foaf: http://xmlns.com/foaf/0.1/
relationship: http://purl.org/vocab/relationship/
dcterms: http://purl.org/dc/terms/
dcmitype: http://purl.org/dc/dcmitype/
sh: http://www.w3.org/ns/shacl#
shex: http://www.w3.org/ns/shex#
xsd: http://www.w3.org/2001/XMLSchema#
as: https://www.w3.org/ns/activitystreams#
ldp: http://www.w3.org/ns/ldp#
vcard: http://www.w3.org/2006/vcard/ns#
sec: https://w3id.org/security#
wgs: http://www.w3.org/2003/01/geo/wgs84_pos#
cc: http://creativecommons.org/ns#
gn: https://www.geonames.org/ontology#
geo: http://www.opengis.net/ont/geosparql#
time: http://www.w3.org/2006/time#
ng: did:ng:n:g:ns# or http://nextgraph.org/ns#
did:ng:n:g:ns#post/rich
ng:class => shortcut for did:ng:n:g:ns#class
a rdfs:Class
a ng:class
did:ng:o:xxxx:yy:yy
did:ng:n:xx.xx#name
did:ng:n:x: curated list of ontologies
did:ng:k common list of things (keyword)
did:ng:n:c common data
did:ng:n:z: curated list of external apps and services
http://nextgraph.org/ns# => the ng: ontology (did:ng:n:g:ns#)
ng:compat -> owl:unionOf rdf:List (alphabetical order, including itself as first element)
-->
<Icon {...config} variation="outline" color="black" icon={find(dataClass)} />

@ -1,224 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import {
branch_subscribe,
active_session,
cannot_load_offline,
open_doc_popup
} from "../store";
import {
Pencil,
PencilSquare
} from "svelte-heros-v2";
import { t } from "svelte-i18n";
import { Button, Progressbar, Spinner, Alert } from "flowbite-svelte";
import { inview } from 'svelte-inview';
import { cur_tab, cur_tab_view_or_edit, cur_tab_doc_can_edit, can_have_header, header_icon, header_title, header_description, cur_branch, set_header_in_view, edit_header_button, cur_app, load_official_app, nav_bar_reset_newest, set_view_or_edit } from "../tab";
import NavIcon from "./icons/NavIcon.svelte";
export let nuri = "";
let width;
let center;
$: center = width > 1024 && !$cur_app?.full_width
let commits;
// TODO deals with cases when nuri has :r :w :l (remove them from nuri that should only have :o:v format , and add them in cur_tab)
$: commits = $active_session && nuri && branch_subscribe(nuri, true);
const inview_options = {};//{rootMargin: "-44px"};
function openEditHeader() {
open_doc_popup("header");
}
</script>
<div bind:clientWidth={width}>
{#if $cannot_load_offline}
<div class="row p-4">
<Alert color="yellow">
{@html $t("doc.cannot_load_offline")}
<a href="#/user">{$t("pages.user_panel.title")}</a>.
</Alert>
</div>
{:else}
<div class="flex justify-left" class:justify-center={center} use:inview={inview_options} on:inview_change={(event) => {
const { inView, entry, scrollDirection, observer, node} = event.detail;
if ($cur_branch) { set_header_in_view(inView); }
if (inView) nav_bar_reset_newest();
}}>
<div class="flex flex-col" class:grow={width<=1024 || $cur_app?.full_width}>
{#if $can_have_header}
<div class:max-w-screen-lg={center} class="flex p-4 justify-start flex-wrap" class:w-[1024px]={center} >
{#if $header_icon}
<NavIcon img={$header_icon} config={{
tabindex:"-1",
class:"w-8 h-8 mr-2 mb-2 flex-none focus:outline-none"
}}/>
{/if}
{#if $cur_tab_view_or_edit && $cur_tab_doc_can_edit}
<button class="p-1 mr-2 mb-2 w-8 h-8 flex-none" on:click={()=>{set_view_or_edit(false);}} title={$t("doc.header.buttons.edit")}>
<PencilSquare tabindex=-1 class="w-5 h-5 focus:outline-none" />
</button>
{/if}
{#if !$header_title} <span class="font-mono h-8 py-1 inline-block align-middle mr-2"> {$cur_tab.doc.nuri.substring(2,9)} </span> {/if}
{#if !$cur_tab_view_or_edit}
<button class="p-1 mr-2 mb-2 w-8 h-8 flex-none" on:click={openEditHeader} title={$t($edit_header_button)}>
<Pencil tabindex=-1 class="w-5 h-5 focus:outline-none" />
</button>{#if !$header_title}<span role="button" on:click={openEditHeader} on:keypress={openEditHeader} tabindex="-1" class="h-8 py-1 inline-block align-middle ">{$t($edit_header_button)}</span> {/if}
{/if}
{#if $header_title}
<h1 class="grow text-left text-2xl">{$header_title}</h1>
{/if}
</div>
{#if $header_description}
<div class:max-w-screen-lg={center} class="flex p-4 text-left text-gray-600 dark:text-white" class:w-[1024px]={center}>
{$header_description}
</div>
{/if}
{/if}
{#if commits}
{#await commits.load()}
<div class="flex flex-col justify-center text-primary-700">
<div class:max-w-screen-lg={center} class="p-4" class:w-[1024px]={center}>
<svg
class="animate-spin mt-4 h-10 w-10 mb-4 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<p class="text-center">{$t("connectivity.loading")}...</p>
</div>
</div>
{:then}
{#if $cur_app}
{#await load_official_app($cur_app)}
<div class="flex flex-col justify-center text-primary-700">
<div class:max-w-screen-lg={center} class="p-4" class:w-[1024px]={center}>
<svg
class="animate-spin mt-4 h-10 w-10 mb-4 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<p class="text-center">{$t("connectivity.loading")}...</p>
</div>
</div>
{:then app}
<div class:max-w-screen-lg={center} class="flex flex-col break-all" style="overflow-wrap: anywhere;" class:w-[1024px]={center} >
<svelte:component this={app} commits={$commits}/>
</div>
{/await}
{:else}
<div class="flex flex-col justify-center text-primary-700">
<div class:max-w-screen-lg={center} class="p-4" class:w-[1024px]={center}>
<svg
class="animate-spin mt-4 h-10 w-10 mb-4 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<p class="text-center">{$t("connectivity.loading")}...</p>
</div>
</div>
{/if}
{/await}
{:else}
<div class="flex flex-col justify-center text-primary-700">
<div class:max-w-screen-lg={center} class="p-4" class:w-[1024px]={center}>
<svg
class="animate-spin mt-4 h-10 w-10 mb-4 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<p class="text-center">{$t("connectivity.loading")}...</p>
</div>
</div>
{/if}
</div>
</div>
{/if}
</div>

File diff suppressed because it is too large Load Diff

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -10,30 +10,16 @@
-->
<script lang="ts">
import { onMount, tick } from "svelte";
import { t } from "svelte-i18n";
import FullLayout from "./FullLayout.svelte";
import Document from "./Document.svelte";
import {
active_session,
} from "../store";
import {
change_nav_bar,reset_in_memory
} from "../tab";
import Test from "./Test.svelte";
import {
PaperAirplane,
Bell,
ArrowRightOnRectangle,
User,
Bookmark,
Sparkles,
Square3Stack3d,
ArchiveBox,
Users,
} from "svelte-heros-v2";
import Logo from "./components/Logo.svelte";
import NavBar from "./components/NavBar.svelte";
let top;
let width: number;
let breakPoint: number = 662;
let mobile = false;
@ -42,41 +28,31 @@
} else {
mobile = true;
}
function scrollToTop() {
top.scrollIntoView();
}
onMount(() => {
change_nav_bar("nav:private",$t("doc.private_store"), false);
reset_in_memory();
});
let nuri = $active_session && $active_session.private_store_id;
</script>
<FullLayout withoutNavBar={true}>
<FullLayout>
{#if mobile}
<nav bind:this={top}
style="background-color: #f6f6f6;" class="border-t border-solid border-gray-200 text-gray-700 dark:text-gray-200 dark:border-gray-700 divide-gray-100 dark:divide-gray-700 px-2 sm:px-4 py-2.5 w-full"
<nav
class="border-t border-solid border-gray-200 bg-white dark:bg-gray-900 text-gray-700 dark:text-gray-200 dark:border-gray-700 divide-gray-100 dark:divide-gray-700 px-2 sm:px-4 py-2.5 w-full"
>
<div
class="mx-auto flex flex-wrap justify-between items-center w-full xxs:px-8 xs:px-10"
class="mx-auto flex flex-wrap justify-between items-center w-full px-2 xxs:px-8 xs:px-10"
>
<a href="#/user" class="flex items-center" >
<a href="#/user" class="flex items-center" on:click>
<Logo className="w-7 h-7 tall:w-10 tall:h-10" />
<span
class="ml-2 self-center text-base font-normal text-gray-900 rounded-lg dark:text-white whitespace-nowrap"
class="ml-2 self-center text-lg font-normal text-gray-900 rounded-lg dark:text-white whitespace-nowrap"
>NextGraph</span
>
</a>
<div class="w-auto flex row">
<a href="#/site" class="row items-center" on:click={scrollToTop}>
<User
<a href="#/shared" class="row items-center" on:click>
<Users
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white focus:outline-none"
/>
</a>
<a href="#/messages" class="ml-4 row items-center" >
<a href="#/messages" class="ml-4 row items-center" on:click>
<PaperAirplane
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white focus:outline-none"
@ -88,7 +64,7 @@
</span>
</a>
<a href="#/notifications" class="ml-4 row items-center" >
<a href="#/notifications" class="ml-4 row items-center" on:click>
<Bell
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white focus:outline-none"
@ -102,24 +78,9 @@
</div>
</div>
</nav>
<div class="sticky top-0 w-full" style="z-index:39;">
<NavBar {scrollToTop}/>
</div>
{/if}
<div class="bg-gray-100 flex p-1 justify-around md:justify-start h-11 gap-0 xs:gap-3 text-gray-500">
<div class="overflow-hidden w-24 sm:ml-3 flex justify-start mr-1" role="button" tabindex="0">
<Bookmark tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.bookmarked")}</div></div>
</div>
<div class="overflow-hidden w-32 sm:ml-3 flex justify-start mr-1" role="button" tabindex="0" title={$t("doc.menu.items.mc.desc")}>
<Sparkles tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.menu.items.mc.label")}</div></div>
</div>
<div class="overflow-hidden w-28 sm:ml-3 flex justify-start" role="button" tabindex="0">
<Square3Stack3d tabindex="-1" class="mt-1 flex-none w-7 h-7 mr-1 focus:outline-none "/><div class="text-xs xs:text-sm flex items-center"><div style="overflow-wrap: anywhere;" class="max-h-8 xs:max-h-10">{$t("doc.header.buttons.all_docs")}</div></div>
</div>
</div>
<div />
<Document {nuri}/>
<Test />
</FullLayout>
<svelte:window bind:innerWidth={width} />

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -51,7 +51,7 @@
</Alert>
{/if}
<div class="row mt-5">
<a href="https://nextgraph.org/download/#android" target="_blank">
<a href="#/">
<button
tabindex="-1"
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
@ -71,7 +71,7 @@
</a>
</div>
<div class="row mt-5">
<a href="https://nextgraph.org/download/#android" target="_blank">
<a href="https://nextgraph.org/download/#android">
<button
tabindex="-1"
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
@ -91,7 +91,7 @@
</a>
</div>
<div class="row mt-5">
<a href="https://nextgraph.org/download/#ios" target="_blank">
<a href="#/">
<button
tabindex="-1"
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
@ -112,7 +112,7 @@
</div>
<div class="row mt-5">
<a href="https://nextgraph.org/download/#macos" target="_blank">
<a href="https://nextgraph.org/download/#macos">
<button
tabindex="-1"
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
@ -133,7 +133,7 @@
</div>
<div class="row mt-5">
<a href="https://nextgraph.org/download/#linux" target="_blank">
<a href="https://nextgraph.org/download/#linux">
<button
tabindex="-1"
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
@ -154,7 +154,7 @@
</div>
<div class="row mt-5">
<a href="https://nextgraph.org/download/#windows" target="_blank">
<a href="https://nextgraph.org/download/#windows">
<button
tabindex="-1"
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
@ -174,8 +174,8 @@
</a>
</div>
<!-- <div class="row mt-5 mb-12">
<a href="https://nextgraph.org/self-host" target="_blank">
<div class="row mt-5 mb-12">
<a href="https://nextgraph.org/self-host">
<button
tabindex="-1"
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
@ -193,5 +193,5 @@
{$t("pages.install.other_platforms")}
</button>
</a>
</div> -->
</div>
</main>

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -27,12 +27,12 @@
Backspace,
ArrowPath,
LockOpen,
Key,
CheckCircle,
ArrowLeft,
} from "svelte-heros-v2";
import PasswordInput from "./components/PasswordInput.svelte";
import Spinner from "./components/Spinner.svelte";
import { display_error } from "../store";
//import Worker from "../worker.js?worker&inline";
export let wallet;
export let for_import = false;
@ -48,13 +48,9 @@
onMount(async () => {
loaded = false;
if (for_import) {
device_name = await ng.get_device_name();
}
load_svg();
//console.log(wallet);
await init();
});
async function init() {
@ -89,7 +85,7 @@
unlockWith = "pazzle";
scrollToTop();
}
async function start_with_mnemonic() {
function start_with_mnemonic() {
loaded = false;
step = "mnemonic";
unlockWith = "mnemonic";
@ -126,8 +122,6 @@
let unlockWith: "pazzle" | "mnemonic" | undefined;
let device_name;
function order() {
step = "order";
ordered = [];
@ -169,12 +163,11 @@
pazzle.push((emoji.cat << 4) + emoji.index);
}
const mnemonic_words = mnemonic.split(" ").filter((t) => t !== "");
const mnemonic_words = mnemonic.split(" ");
// open the wallet
try {
if (tauri_platform) {
// TODO @niko: Add device_name as param to open_with_* APIs
let opened_wallet =
unlockWith === "pazzle"
? await ng.wallet_open_with_pazzle(wallet, pazzle, pin_code)
@ -198,10 +191,11 @@
wallet: opened_wallet,
id: opened_wallet.V0.wallet_id,
trusted,
device_name,
});
} else {
let worker_import = await import("../worker.js?worker&inline");
let worker_import = await import(
"../workers/wallet-worker.js?worker&inline"
);
const myWorker = new worker_import.default();
myWorker.onerror = (e) => {
console.error(e);
@ -219,14 +213,9 @@
//console.log("Message received from worker", msg.data);
if (msg.data.loaded) {
if (unlockWith === "pazzle") {
myWorker.postMessage({ wallet, pazzle, pin_code, device_name });
myWorker.postMessage({ wallet, pazzle, pin_code });
} else {
myWorker.postMessage({
wallet,
mnemonic_words,
pin_code,
device_name,
});
myWorker.postMessage({ wallet, mnemonic_words, pin_code });
}
//console.log("postMessage");
} else if (msg.data.success) {
@ -246,7 +235,6 @@
wallet: msg.data.success,
id: msg.data.success.V0.wallet_id,
trusted,
device_name,
});
} else {
console.error(msg.data.error);
@ -258,7 +246,6 @@
}
} catch (e) {
console.error(e);
if (e.message && e.message.includes("constructor") || (typeof e === "string" && e.includes("constructor") )) e = "BrowserTooOld";
error = e;
step = "end";
dispatch("error", { error: e });
@ -273,9 +260,6 @@
async function on_pin_key(val) {
pin_code = [...pin_code, val];
if (pin_code.length == 4) {
setTimeout(()=>window.document.getElementById("confirm_pin_btn").focus(),50);
}
}
async function select_order(val) {
@ -347,8 +331,8 @@
<div
class="flex-col justify-center md:max-w-2xl py-4 sm:px-8"
class:h-screen={step !== "load" && height > 640}
class:flex={height > 640}
class:h-screen={step !== "load" && height > 660}
class:flex={height > 660}
bind:this={top}
>
{#if step == "load"}
@ -405,26 +389,13 @@
<Toggle class="" bind:checked={trusted}
>{$t("pages.login.trust_device_yes")}</Toggle
>
<!-- Ask for Device Name -->
</div>
</div>
{/if}
<div class=" max-w-xl lg:px-8 mx-auto px-4 text-primary-700">
<div class="flex flex-col justify-centerspace-x-12 mt-4 mb-4">
<!-- Device Name, if trusted-->
{#if for_import && trusted}
<label for="device-name-input" class="text-sm text-black">
{$t("pages.login.device_name_label")}
</label>
<input
id="device-name-input"
bind:value={device_name}
placeholder={$t("pages.login.device_name_placeholder")}
type="text"
class="w-full mb-10 lg:px-8 mx-auto px-4 bg-gray-50 border border-gray-300 text-xs rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
/>
{/if}
{#if !loaded}
{$t("pages.login.loading_pazzle")}...
<Spinner className="my-4 h-14 w-14 mx-auto" />
@ -453,7 +424,7 @@
on:keypress={start_with_mnemonic}
role="link"
tabindex="0"
class="mt-1 text-lg px-5 py-2.5 text-center inline-flex items-center underline cursor-pointer"
class="mt-1 text-lg px-5 py-2.5 text-center inline-flex items-center mb-2 underline cursor-pointer"
>
{$t("pages.login.open_with_mnemonic")}
</span>
@ -464,8 +435,8 @@
{:else if step == "pazzle" || step == "order" || step == "pin" || step == "mnemonic"}
<div
class="flex-col justify-center h-screen"
class:flex={height > 640}
class:min-w-[300px]={mobile}
class:flex={height > 660}
class:min-w-[310px]={mobile}
class:min-w-[500px]={!mobile}
class:max-w-[370px]={mobile}
class:max-w-[600px]={!mobile}
@ -490,7 +461,7 @@
<div class="flex">
<Button
type="submit"
class="mt-3 mb-2 ml-auto text-white bg-primary-700 hover:bg-primary-700/90 disabled:opacity-65 focus:ring-4 focus:ring-blue-500 focus:border-blue-500 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-blue-500 dark:focus:border-blue-500"
class="mt-3 mb-2 ml-auto text-white bg-primary-700 disabled:opacity-65 focus:ring-4 focus:ring-blue-500 focus:border-blue-500 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-blue-500 dark:focus:border-blue-500"
on:click={start_pin}
disabled={mnemonic.split(" ").length !== 12}
><CheckCircle
@ -589,9 +560,9 @@
tabindex="0"
class="pindigit m-1 disabled:opacity-15 disabled:text-gray-200 select-none align-bottom text-7xl p-0 w-full aspect-square border-0"
class:h-[160px]={!mobile}
class:h-[93px]={mobile}
class:h-[100px]={mobile}
class:text-8xl={!mobile}
on:click={async () => {window.document.activeElement.blur(); await on_pin_key(num)}}
on:click={async () => await on_pin_key(num)}
disabled={pin_code.length >= 4}
>
<span>{num}</span>
@ -605,19 +576,17 @@
tabindex="0"
class="pindigit disabled:opacity-15 m-1 disabled:text-gray-200 select-none align-bottom text-7xl p-0 w-full aspect-square border-0"
class:h-[160px]={!mobile}
class:h-[93px]={mobile}
class:h-[100px]={mobile}
class:text-8xl={!mobile}
on:click={async () => {window.document.activeElement.blur();await on_pin_key(shuffle_pin[9])}}
on:click={async () => await on_pin_key(shuffle_pin[9])}
disabled={pin_code.length >= 4}
>
<span>{shuffle_pin[9]}</span>
</button>
<Button
tabindex="0"
id="confirm_pin_btn"
class="w-full bg-green-300 hover:bg-green-300/90 enabled:animate-bounce disabled:bg-gray-200 disabled:opacity-15 m-1 select-none align-bottom text-7xl p-0 aspect-square border-0"
on:click={async () => await finish()}
on:keypress={async () => await finish()}
disabled={pin_code.length < 4}
>
<LockOpen
@ -626,7 +595,7 @@
/>
</Button>
</div>
<span class="select-none text-9xl h-[4rem] text-center"
<span class="mt-3 text-9xl min-h-[8rem] text-center"
>{#each pin_code as pin_key}*{/each}</span
>
{/if}
@ -682,7 +651,7 @@
/>
</svg>
<Alert color="red" class="mt-5">
{display_error(error)}
{$t("errors." + error)}
</Alert>
</div>
<div class="flex justify-between mt-auto gap-4">
@ -733,7 +702,7 @@
<style>
.pindigit {
min-height: 93px;
min-height: 99px;
}
/* .pazzleline {

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -24,7 +24,7 @@
</script>
<nav
style="background-color: #f6f6f6;" class="border-t border-solid border-gray-200 text-gray-700 dark:text-gray-200 dark:border-gray-700 divide-gray-100 dark:divide-gray-700 px-2 sm:px-4 py-2.5 w-full fixed bottom-0 left-0 z-20"
class="border-t border-solid border-gray-200 bg-white dark:bg-gray-900 text-gray-700 dark:text-gray-200 dark:border-gray-700 divide-gray-100 dark:divide-gray-700 px-2 sm:px-4 py-2.5 w-full fixed bottom-0 left-0 z-20"
>
<div
class="mx-auto flex flex-wrap justify-between items-center w-full px-2 xxs:px-8 xs:px-10"

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -13,7 +13,6 @@
import { getContext } from "svelte";
import type { ComponentType } from "svelte";
import { Icon } from "svelte-heros-v2";
import { onMount, onDestroy, tick } from "svelte";
export let href: string = "";
export let icon: ComponentType;
@ -23,20 +22,14 @@
};
let sidebarUrl = "";
let unsub;
onMount( () => {
unsub = activeUrlStore.subscribe((value) => {
activeUrlStore.subscribe((value) => {
sidebarUrl = value;
});
});
onDestroy( () => {
if (unsub) unsub();
});
$: active = sidebarUrl ? href === sidebarUrl : false;
</script>
<a {href} class="flex items-center">
<a {href} class="flex items-center" on:click>
<Icon
tabindex="-1"
color="black"

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -27,7 +27,7 @@
<div class="row">
<Logo class="logo block h-40" alt={$t("common.logo")} />
</div>
<h1 class="text-2xl text-center mb-10">{$t("pages.no_wallet.welcome")}</h1>
<h1 class="text-2xl mb-10">{$t("pages.no_wallet.welcome")}</h1>
<p class="max-w-sm">
{@html $t("pages.no_wallet.description")}
@ -39,7 +39,7 @@
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<svg
class="w-8 h-8 -ml-1 mr-2"
class="w-8 h-8 -ml-1"
fill="none"
stroke="currentColor"
stroke-width="1.5"

@ -1,41 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
export let pane_name = "";
const panes = {
"history": "History",
"files": "Files",
};
const load_pane = async (pane_name) => {
if (!panes[pane_name]) return false;
let component = await import(`./panes/${panes[pane_name]}.svelte`);
return component.default;
};
</script>
<div>
{#if pane_name}
{#await load_pane(pane_name) then pane}
{#if pane}
<div class="flex w-full" style="overflow-wrap: anywhere;">
<svelte:component this={pane}/>
</div>
{/if}
{/await}
{/if}
</div>

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -10,24 +10,632 @@
-->
<script lang="ts">
import {
createGitgraph,
templateExtend,
TemplateName,
} from "../history/gitgraph-js/gitgraph";
import ng from "../api";
import {
branch_subscribe,
branch_subs,
active_session,
cannot_load_offline,
online,
get_blob,
upload_file,
} from "../store";
import { link } from "svelte-spa-router";
import { onMount, onDestroy, tick } from "svelte";
import { Button, Progressbar, Spinner } from "flowbite-svelte";
import DataClassIcon from "./DataClassIcon.svelte";
import { t } from "svelte-i18n";
let is_tauri = import.meta.env.TAURI_PLATFORM;
let upload_progress: null | { total: number; current: number; error?: any } =
null;
let files = $active_session && branch_subs($active_session.private_store_id);
let gitgraph;
let next = [
{
hash: "I",
subject: "niko2",
author: "",
parents: ["G"],
},
{
hash: "T",
subject: "niko2",
author: "",
parents: ["D", "H"],
},
{
hash: "Z",
subject: "niko2",
author: "",
parents: ["E"],
},
{
hash: "L",
subject: "niko2",
author: "",
parents: ["H"],
},
{
hash: "J",
subject: "niko2",
author: "",
parents: ["L", "Z", "I"],
},
{
hash: "K",
subject: "niko2",
author: "",
parents: ["G", "E"],
},
{
hash: "X",
subject: "niko2",
author: "",
parents: ["I"],
},
{
hash: "Q",
subject: "niko2",
author: "",
parents: ["L", "X"],
},
];
function add() {
let n = next.shift();
if (n) gitgraph.commit(n);
}
onMount(async () => {
const graphContainer = document.getElementById("graph-container");
gitgraph = createGitgraph(graphContainer, {
template: templateExtend(TemplateName.Metro, {
branch: { label: { display: false } },
commit: { message: { displayAuthor: false } },
}),
});
gitgraph.swimlanes(["A", "F", "C"]);
gitgraph.import([
{
hash: "A",
subject: "niko2",
branch: "A",
parents: [],
author: "",
x: 0,
y: 0,
},
{
hash: "B",
subject: "niko2",
branch: "A",
author: "",
parents: ["A"],
x: 0,
y: 1,
},
{
hash: "D",
subject: "niko2",
branch: "A",
author: "",
parents: ["B"],
x: 0,
y: 2,
},
{
hash: "C",
subject: "niko2",
branch: "C",
author: "",
parents: ["A"],
x: 2,
y: 3,
},
{
hash: "F",
subject: "niko2",
branch: "F",
author: "",
parents: ["B", "C"],
x: 1,
y: 4,
},
{
hash: "G",
subject: "niko2",
branch: "F",
parents: ["F"],
author: "",
x: 1,
y: 5,
},
{
hash: "E",
subject: "niko2",
branch: "C",
author: "",
parents: ["C"],
x: 2,
y: 6,
},
// {
// hash: "H",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["D", "G"],
// x: 0,
// y: 7,
// },
// {
// hash: "I",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["D", "H"],
// x: 0,
// y: 8,
// },
// {
// hash: "H",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["D", "G", "E"],
// x: 0,
// y: 7,
// },
]);
// window.setTimeout(() => {
// gitgraph.commit({
// hash: "H",
// subject: "niko2",
// author: "",
// parents: ["D", "G", "E"],
// });
// }, 0);
window.setTimeout(() => {
gitgraph.commit({
hash: "H",
subject: "niko2",
author: "",
parents: ["G", "E"],
});
}, 0);
// window.setTimeout(() => {
// gitgraph.commit({
// hash: "H",
// subject: "niko2",
// author: "",
// parents: ["G"],
// });
// }, 0);
// gitgraph.swimlanes(["A", "B", false, "D"]);
// gitgraph.import([
// {
// hash: "A",
// subject: "niko2",
// branch: "A",
// parents: [],
// author: "",
// x: 0,
// y: 0,
// },
// {
// hash: "C",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["A"],
// x: 0,
// y: 1,
// },
// {
// hash: "D",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["C"],
// x: 0,
// y: 2,
// },
// {
// hash: "E",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["D"],
// x: 0,
// y: 3,
// },
// {
// hash: "B",
// subject: "niko2",
// branch: "C",
// author: "",
// parents: ["A"],
// x: 2,
// y: 4,
// },
// {
// hash: "G",
// subject: "niko2",
// branch: "C",
// parents: ["B"],
// author: "",
// x: 2,
// y: 5,
// },
// {
// hash: "F",
// subject: "niko2",
// branch: "B",
// author: "",
// parents: ["D", "G"],
// x: 1,
// y: 6,
// },
// {
// hash: "H",
// subject: "niko2",
// branch: "D",
// author: "",
// parents: ["G"],
// x: 3,
// y: 7,
// },
// // {
// // hash: "I",
// // subject: "niko2",
// // branch: "A",
// // author: "",
// // parents: ["E", "F", "H"],
// // x: 0,
// // y: 8,
// // },
// ]);
// gitgraph.swimlanes([
// "A",
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// false,
// ]);
// gitgraph.import([
// {
// hash: "A",
// subject: "niko2",
// branch: "A",
// parents: [],
// author: "",
// x: 0,
// y: 0,
// },
// {
// hash: "B",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["A"],
// x: 0,
// y: 1,
// },
// {
// hash: "C",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["B"],
// x: 0,
// y: 2,
// },
// {
// hash: "D",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["C"],
// x: 0,
// y: 3,
// },
// {
// hash: "E",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["D"],
// x: 0,
// y: 4,
// },
// {
// hash: "J",
// subject: "niko2",
// branch: "J",
// parents: ["A"],
// author: "",
// x: 2,
// y: 5,
// },
// {
// hash: "K",
// subject: "niko2",
// branch: "J",
// author: "",
// parents: ["J"],
// x: 2,
// y: 6,
// },
// {
// hash: "L",
// subject: "niko2",
// branch: "L",
// author: "",
// parents: ["A"],
// x: 3,
// y: 7,
// },
// {
// hash: "M",
// subject: "niko2",
// branch: "L",
// author: "",
// parents: ["L"],
// x: 3,
// y: 8,
// },
// {
// hash: "G",
// subject: "niko2",
// branch: "G",
// author: "",
// parents: ["C", "K", "M"],
// x: 1,
// y: 9,
// },
// {
// hash: "H",
// subject: "niko2",
// branch: "G",
// author: "",
// parents: ["G"],
// x: 1,
// y: 10,
// },
// {
// hash: "I",
// subject: "niko2",
// branch: "G",
// author: "",
// parents: ["H"],
// x: 1,
// y: 11,
// },
// {
// hash: "F",
// subject: "niko2",
// branch: "A",
// author: "",
// parents: ["E", "I"],
// x: 0,
// y: 12,
// },
// {
// hash: "1",
// subject: "niko2",
// branch: "1",
// author: "",
// parents: ["A"],
// x: 4,
// y: 13,
// },
// {
// hash: "2",
// subject: "niko2",
// branch: "2",
// author: "",
// parents: ["A"],
// x: 5,
// y: 14,
// },
// {
// hash: "3",
// subject: "niko2",
// branch: "3",
// author: "",
// parents: ["A"],
// x: 6,
// y: 15,
// },
// {
// hash: "4",
// subject: "niko2",
// branch: "4",
// author: "",
// parents: ["A"],
// x: 7,
// y: 16,
// },
// {
// hash: "5",
// subject: "niko2",
// branch: "5",
// author: "",
// parents: ["A"],
// x: 8,
// y: 17,
// },
// {
// hash: "6",
// subject: "niko2",
// branch: "6",
// author: "",
// parents: ["A"],
// x: 9,
// y: 18,
// },
// {
// hash: "7",
// subject: "niko2",
// branch: "7",
// author: "",
// parents: ["A"],
// x: 10,
// y: 19,
// },
// {
// hash: "8",
// subject: "niko2",
// branch: "8",
// author: "",
// parents: ["A"],
// x: 11,
// y: 20,
// },
// {
// hash: "9",
// subject: "niko2",
// branch: "9",
// author: "",
// parents: ["A"],
// x: 12,
// y: 21,
// },
// ]);
});
let fileinput;
const onFileSelected = async (e) => {
let image = e.target.files[0];
if (!image) return;
//console.log(image);
await upload_file($active_session.session_id, image, (progress) => {
upload_progress = progress;
});
// Make progress bar disappear
setTimeout(() => {
upload_progress = null;
}, 2_500);
fileinput.value = "";
};
</script>
<div>
<div id="graph-container"></div>
{#if $cannot_load_offline}
<div class="row p-4">
<p>
{@html $t("pages.test.cannot_load_offline")}
<a href="#/user">{$t("pages.user_panel.title")}</a>.
</p>
</div>
{:else}
<div class="row pt-2">
<!-- <a use:link href="/">
<button tabindex="-1" class=" mr-5 select-none"> Back home </button>
</a> -->
<Button
type="button"
on:click={() => {
add();
}}
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mr-2 mb-2"
>
g
</Button>
<Button
disabled={!$online && !is_tauri}
type="button"
on:click={() => {
fileinput.click();
}}
class="text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mr-2 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
/>
</svg>
{$t("pages.test.add_image")}
</Button>
<input
style="display:none"
type="file"
accept=".jpg, .jpeg, .png"
on:change={(e) => onFileSelected(e)}
bind:this={fileinput}
/>
</div>
{#if upload_progress !== null}
<div class="mx-6 mt-2">
<Progressbar
progress={(
(100 * upload_progress.current) /
upload_progress.total
).toFixed(0)}
labelOutside={$t("pages.test.upload_progress")}
/>
</div>
{/if}
{#if files}
{#await files.load()}
<p>{$t("connectivity.loading")}...</p>
{:then}
{#each $files as file}
<p>
{file.name}
{#await get_blob(file)}
<div class="ml-2">
<Spinner />
</div>
{:then url}
{#if url}
<img src={url} title={file.nuri} alt={file.name} />
{/if}
{/await}
</p>
{/each}
{/await}
{/if}
{/if}
</div>

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -70,38 +70,28 @@
CloudArrowDown,
Beaker,
Eye,
Square3Stack3d,
QueueList,
} from "svelte-heros-v2";
import JsonIcon from "./JsonIcon.svelte";
import JsonLdIcon from "./JsonLdIcon.svelte";
import RdfIcon from "./RdfIcon.svelte";
import TurtleIcon from "./TurtleIcon.svelte";
import GraphQLIcon from "./GraphQLIcon.svelte";
export let config = {};
export let zera: string;
const exact_mapping = {
json_ld_editor: JsonLdIcon,
json_editor: JsonIcon,
triple_editor: RdfIcon,
turtle_viewer: TurtleIcon,
json_ld_editor: TableCells,
json_editor: TableCells,
triple_editor: Share,
rdf_viewer: CircleStack,
graph_viewer: Sun,
compose: QueueList,
sparql_query: RocketLaunch,
sparnatural: CursorArrowRays,
graphql: GraphQLIcon,
graphql: Cube,
invoke: Play,
ontology_viewer: ArrowsPointingOut,
download: DocumentArrowDown,
edit: PencilSquare,
post_edit: PencilSquare,
file: Document,
source: CodeBracket,
post: DocumentText,
pad: Square2Stack,
container: Square3Stack3d,
card: Clipboard,
gallery: RectangleStack,
load_graph: CloudArrowUp,
@ -127,4 +117,4 @@
};
</script>
<Icon {...config} variation="outline" color="currentColor" icon={find(zera)} />
<Icon {...config} variation="outline" color="black" icon={find(zera)} />

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -11,8 +11,7 @@
<script lang="ts">
export let value: string = "";
export let id: string | undefined = undefined;
export let rows: number = 3;
export let id: string;
let has_success: boolean = false;
@ -38,11 +37,10 @@
<div class="relative">
<textarea
{id}
{rows}
rows="3"
style="resize: none;"
{value}
class="col-span-6 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500"
class:pr-11={!tauri_platform}
class="col-span-6 pr-11 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500"
disabled
readonly
/>

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -28,7 +28,7 @@ Provide classes using the `className` prop.
export let className: string = "";
let connection_status_class = "logo-blue";
// Color is adjusted to connection status.
$: if ($connection_status === "connecting" || $connection_status === "starting") {
$: if ($connection_status === "connecting") {
connection_status_class = "logo-pulse";
} else if ($connection_status === "disconnected") {
connection_status_class = "logo-gray";

@ -1,57 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
export let clickable;
export let extraClass = "";
export let selected = false;
export let title = "";
export let dropdown = undefined;
export let offset = false;
import {
ChevronUp,
ChevronDown,
} from "svelte-heros-v2";
</script>
{#if clickable}
<li {title} role="menuitem" tabindex="0" class:text-primary-600={selected} class:text-gray-800={!selected} class:dark:text-white={!selected} class:dark:text-primary-300={selected}
class="{extraClass} select-none clickable focus:outline-2 focus:outline flex items-center pl-2 py-1 text-base font-normal rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 mt-1 pr-0"
on:click={(e) => { e.currentTarget.blur(); clickable();}} on:keypress={clickable} on:keydown={(e) => {if (e.code=='Space') { e.preventDefault(); clickable();} }}>
<slot />
{#if dropdown!==undefined}
<div class="grow"></div>
{#if dropdown}
<ChevronUp/>
{:else}
<ChevronDown/>
{/if}
{#if offset}
<div style="width:35px;">
</div>
{/if}
{/if}
</li>
{:else if clickable === false}
<li {title} class="{extraClass} select-none flex items-center px-2 py-1 text-base font-normal text-primary-600 rounded-lg dark:text-primary-300 mt-1">
<slot />
</li>
{:else}
<li {title} class="{extraClass} select-none flex items-center px-2 py-1 text-base font-normal deactivated-menu text-gray-400 rounded-lg dark:text-gray-400 mt-1" >
<slot />
</li>
{/if}

@ -1,64 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import {
Toast,
} from "flowbite-svelte";
import {
remove_toast
} from "../../store";
import { onMount, onDestroy, tick } from "svelte";
const toast_color = {
"error":"red",
"warning":"orange",
"success":"green",
"info":"blue"
};
const toast_icon = {
"error": XCircle,
"warning": ExclamationCircle,
"success": CheckCircle,
"info": InformationCircle,
}
import {
CheckCircle,
XCircle,
ExclamationCircle,
InformationCircle,
Icon,
} from "svelte-heros-v2";
export let toast;
export let i;
onMount(()=>{
toast.i = i;
if (toast.level=="success")
{
toast.timer = setTimeout(()=>{remove_toast(i);}, toast.timeout || 10000);
}
});
</script>
<div class="toast fixed flex w-full max-w-xs" style="top:{16+i*74}px;"
on:click|capture|stopPropagation={()=>{remove_toast(toast.i);}}
on:keypress={()=>{}}
>
<Toast color="{toast_color[toast.level]}" >
<Icon tabindex="-1" slot="icon" class="w-8 h-8 p-1 focus:outline-none" variation="outline" color="currentColor" icon={toast_icon[toast.level]} />
{toast.text}
</Toast>
</div>

@ -1,94 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import {
ArrowLeft,
ChevronDoubleUp,
CheckCircle,
CheckBadge,
EllipsisVertical,
ExclamationTriangle,
} from "svelte-heros-v2";
import NavIcon from "../icons/NavIcon.svelte";
import {
Popover,
} from "flowbite-svelte";
import {nav_bar_newest, save, nav_bar, showMenu, cur_tab_header_in_view, cur_tab_store_name_override, cur_tab_store_icon_override, cur_tab_persistent_error, nav_bar_save,
cur_tab_view_or_edit, cur_tab_graph_or_discrete, nav_bar_back, nav_bar_title, nav_bar_icon} from "../../tab";
export let scrollToTop = () => {};
const back = () => {
// going back
window.history.go(-1);
}
const closeErrorPopup = () => {
window.document.getElementById("error_popover_btn").click();
}
</script>
<div style="background-color: #fbfbfb;" class="h-11 pb-1 flex text-center text-gray-700 dark:text-white">
{#if $nav_bar_back}
<div role="button" tabindex="0" on:click={back} on:keypress={back} class="flex-none w-10 flex justify-center items-center">
<ArrowLeft tabindex="-1" class="w-8 h-8 focus:outline-none"/>
</div>
{/if}
{#if $cur_tab_store_icon_override || $nav_bar_icon}
<div style="cursor:pointer;" class:w-10={!$nav_bar_back} class:ml-3={!$nav_bar_back} class="flex-none w-8 m-1 " on:click={scrollToTop} on:keypress={scrollToTop}>
<NavIcon img={$cur_tab_store_icon_override || $nav_bar_icon} config={{
tabindex:"-1",
class:"w-8 h-8 focus:outline-none"
}}/>
</div>
{/if}
<div style="cursor:pointer;" class:pl-3={!$nav_bar_back && !$nav_bar_icon} class="grow w-10 items-center flex px-1" on:click={scrollToTop} on:keypress={scrollToTop}>
<span class="inline-block truncate" > {$cur_tab_store_name_override || $nav_bar_title} </span>
</div>
{#if $nav_bar_newest && !$cur_tab_header_in_view}
<div role="button" tabindex="0" class="flex-none m-1 rounded-full bg-primary-700 text-white dark:bg-primary-700" on:click={scrollToTop} on:keypress={scrollToTop}>
<div class="flex items-center grow pr-2">
<ChevronDoubleUp tabindex="-1" class="w-6 h-6 m-1 focus:outline-none"/>
<span class="inline-block">{@html $nav_bar_newest < 100 ? "+"+$nav_bar_newest : "<span class=\"text-xl\">&infin;</span>"}</span>
</div>
</div>
{/if}
{#if $cur_tab_persistent_error}
<div id="error_popover_btn" tabindex="0" class="flex-none w-10" role="button" title={$cur_tab_persistent_error.title + ". Click for more details"}>
<ExclamationTriangle variation="outline" tabindex="-1" strokeWidth="2" class="w-9 h-9 mt-1 text-red-700 focus:outline-none"/>
</div>
<Popover class="text-left text-black w-[300px] text-sm error-popover" title={$cur_tab_persistent_error.title} triggeredBy="#error_popover_btn" trigger="click" placement = 'bottom'
open={true}
>{@html $cur_tab_persistent_error.desc}
<br/><br/><span class="text-primary-700" on:click={closeErrorPopup} on:keypress={closeErrorPopup} role="button" tabindex="0">Dismiss</span>
</Popover>
{:else if $nav_bar_save !== undefined && !$cur_tab_view_or_edit && !$cur_tab_graph_or_discrete}
{#if $nav_bar_save }
<div tabindex="0" class="flex-none w-10" role="button" on:click={save} on:keypress={save} title="Save">
<CheckCircle variation="solid" tabindex="-1" strokeWidth="3" class="w-10 h-10 text-primary-400 focus:outline-none"/>
</div>
{:else}
<div class="flex-none w-10" title="Saved!">
<CheckBadge tabindex="-1" class="w-8 h-8 m-1 text-green-500 focus:outline-none"/>
</div>
{/if}
{/if}
<div tabindex="0" class="flex-none w-10 " role="button" title="Open Menu" on:click={showMenu} on:keypress={showMenu}>
<EllipsisVertical tabindex="-1" class="w-8 h-8 my-1 mr-2"/>
</div>
</div>

@ -1,49 +0,0 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import {
XMark,
Icon,
CheckCircle,
CheckBadge,
EllipsisVertical,
} from "svelte-heros-v2";
import { t } from "svelte-i18n";
import {cur_tab_update} from "../../tab";
export let pane_name = "";
export let pane_items = {};
const closePane = (pane:string|boolean) => {
cur_tab_update(($cur_tab) => {
if (pane=="folders") {
$cur_tab.folders_pane = false;
} else if (pane=="toc") {
$cur_tab.toc_pane = false;
} else {
$cur_tab.right_pane = "";
}
return $cur_tab;
});
}
</script>
<div style="height:44px; background-color: rgb(251, 251, 251);" class={`${$$props.class || ''} fixed top-0 w-10 h-10 p-1 bg-white dark:bg-black; rounded-bl-xl`} role="button" aria-label="Close pane" title="Close pane"
on:click={()=>closePane(pane_name)}
on:keypress={()=>closePane(pane_name)}
tabindex="0">
<XMark class="w-8 h-8 p-1 text-gray-400 focus:outline-none dark:text-white"/>
</div>
<div style="height:44px; background-color: rgb(251, 251, 251);" class="p-1 flex items-center">
<Icon tabindex="-1" class="w-8 h-8 text-gray-400 dark:text-white focus:outline-none " variation="outline" color="currentColor" icon={pane_items[pane_name]} />
<span class="ml-2 inline-block text-gray-500 select-none dark:text-white">{$t(`doc.menu.items.${pane_name}.label`)}</span>
</div>

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
@ -40,14 +40,12 @@
</script>
<div class="relative">
<!-- svelte-ignore a11y-autofocus -->
<input
bind:this={input}
{value}
{placeholder}
{id}
{type}
autofocus
on:input={handleInput}
class={`${className} pr-12 text-md block`}
autocomplete={auto_complete}

@ -1,5 +1,5 @@
<!--
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>

@ -1,3 +0,0 @@
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" fill="currentColor" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''}>
<path d="M4 21.25c-1.519 0-2.75 1.231-2.75 2.75s1.231 2.75 2.75 2.75c1.519 0 2.75-1.231 2.75-2.75v0c-0.002-1.518-1.232-2.748-2.75-2.75h-0zM4 25.25c-0.69 0-1.25-0.56-1.25-1.25s0.56-1.25 1.25-1.25c0.69 0 1.25 0.56 1.25 1.25v0c-0.001 0.69-0.56 1.249-1.25 1.25h-0zM20 21.25c-1.519 0-2.75 1.231-2.75 2.75s1.231 2.75 2.75 2.75c1.519 0 2.75-1.231 2.75-2.75v0c-0.002-1.518-1.232-2.748-2.75-2.75h-0zM20 25.25c-0.69 0-1.25-0.56-1.25-1.25s0.56-1.25 1.25-1.25c0.69 0 1.25 0.56 1.25 1.25v0c-0.001 0.69-0.56 1.249-1.25 1.25h-0zM12 13.25c-1.519 0-2.75 1.231-2.75 2.75s1.231 2.75 2.75 2.75c1.519 0 2.75-1.231 2.75-2.75v0c-0.002-1.518-1.232-2.748-2.75-2.75h-0zM12 17.25c-0.69 0-1.25-0.56-1.25-1.25s0.56-1.25 1.25-1.25c0.69 0 1.25 0.56 1.25 1.25v0c-0.001 0.69-0.56 1.249-1.25 1.25h-0zM20 13.25c-1.519 0-2.75 1.231-2.75 2.75s1.231 2.75 2.75 2.75c1.519 0 2.75-1.231 2.75-2.75v0c-0.002-1.518-1.232-2.748-2.75-2.75h-0zM20 17.25c-0.69 0-1.25-0.56-1.25-1.25s0.56-1.25 1.25-1.25c0.69 0 1.25 0.56 1.25 1.25v0c-0.001 0.69-0.56 1.249-1.25 1.25h-0zM28 5.25c-1.519 0-2.75 1.231-2.75 2.75s1.231 2.75 2.75 2.75c1.519 0 2.75-1.231 2.75-2.75v0c-0.002-1.518-1.232-2.748-2.75-2.75h-0zM28 9.25c-0.69 0-1.25-0.56-1.25-1.25s0.56-1.25 1.25-1.25c0.69 0 1.25 0.56 1.25 1.25v0c-0.001 0.69-0.56 1.249-1.25 1.25h-0zM4 5.25c-1.519 0-2.75 1.231-2.75 2.75s1.231 2.75 2.75 2.75c1.519 0 2.75-1.231 2.75-2.75v0c-0.002-1.518-1.232-2.748-2.75-2.75h-0zM4 9.25c-0.69 0-1.25-0.56-1.25-1.25s0.56-1.25 1.25-1.25c0.69 0 1.25 0.56 1.25 1.25v0c-0.001 0.69-0.56 1.249-1.25 1.25h-0zM20 5.25c-1.519 0-2.75 1.231-2.75 2.75s1.231 2.75 2.75 2.75c1.519 0 2.75-1.231 2.75-2.75v0c-0.002-1.518-1.232-2.748-2.75-2.75h-0zM20 9.25c-0.69 0-1.25-0.56-1.25-1.25s0.56-1.25 1.25-1.25c0.69 0 1.25 0.56 1.25 1.25v0c-0.001 0.69-0.56 1.249-1.25 1.25h-0z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

@ -1,13 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512" fill="currentColor" width="24" height="24" tabindex="-1" class={$$props.class || ''} style={$$props.style || ''}>
<path d="M365.1,74.6c-43.8,0-80.2,36.4-80.2,80.2c0,38.2,27,70.9,64.3,78.3c-0.9,21.4-12.1,33.6-30.8,48.5
c-23.3,17.7-53.2,23.3-74.6,27c-46.6,8.4-71.8,30.8-83,45.7V159.5c16.8-2.8,32.6-12.1,44.8-25.2c13.1-14.9,20.5-33.6,20.5-54.1
C226.2,36.4,189.8,0,146,0S65.7,36.4,65.7,80.2c0,19.6,7.5,38.2,19.6,53.2c11.2,13.1,26.1,21.4,42.9,25.2v195.8
c-16.8,3.7-31.7,13.1-42.9,25.2c-13.1,14.9-19.6,33.6-19.6,52.2c0,43.8,36.4,80.2,80.2,80.2s80.2-36.4,80.2-80.2
c0-27-13.1-51.3-35.4-66.2c10.3-11.2,28-22.4,58.8-28c25.2-4.7,60.6-11.2,88.6-32.6c27-20.5,42-42,43.8-73.7
c37.3-7.5,64.3-40.1,64.3-78.3C445.3,110,408.9,74.6,365.1,74.6L365.1,74.6z M97.5,81.1c0-26.1,21.4-48.5,48.5-48.5
c26.1,0,48.5,21.4,48.5,48.5S173,129.6,146,129.6C118.9,129.6,97.5,107.2,97.5,81.1z M193.5,433.7c0,26.1-21.4,48.5-48.5,48.5
c-26.1,0-48.5-21.4-48.5-48.5s21.4-48.5,48.5-48.5C172.1,386.1,193.5,407.5,193.5,433.7z M365.1,202.4c-26.1,0-48.5-21.4-48.5-48.5
c0-26.1,21.4-48.5,48.5-48.5c26.1,0,48.5,21.4,48.5,48.5C412.7,180.9,391.2,202.4,365.1,202.4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save