Compare commits
125 Commits
chore/ng-a
...
master
@ -1,3 +1,7 @@ |
|||||||
liberapay: nextgraph |
liberapay: nextgraph |
||||||
ko_fi: nextgraph |
ko_fi: nextgraph |
||||||
custom: ["https://donate.stripe.com/8wMcOE3NI2B69NKeUU", "https://pay.vivawallet.com/par-le-peuple", "https://nextgraph.org/donate"] |
custom: |
||||||
|
[ |
||||||
|
"https://donate.stripe.com/8wMcOE3NI2B69NKeUU", |
||||||
|
"https://nextgraph.org/donate", |
||||||
|
] |
||||||
|
@ -0,0 +1,189 @@ |
|||||||
|
# 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 |
@ -0,0 +1,187 @@ |
|||||||
|
# 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. |
@ -0,0 +1,40 @@ |
|||||||
|
# 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 +1,2 @@ |
|||||||
tests |
tests |
||||||
|
local_broker_dev_env_peer_id.rs |
@ -0,0 +1 @@ |
|||||||
|
pub const PEER_ID: &str = "FtdzuDYGewfXWdoPuXIPb0wnd0SAg1WoA2B14S7jW3MA"; |
@ -1,3 +1,3 @@ |
|||||||
fn main() { |
fn main() { |
||||||
tauri_build::build() |
tauri_build::build() |
||||||
} |
} |
||||||
|
@ -0,0 +1,179 @@ |
|||||||
|
<!-- |
||||||
|
// 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 { 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> |
@ -0,0 +1,74 @@ |
|||||||
|
<!-- |
||||||
|
// 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 { 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} |
@ -0,0 +1,19 @@ |
|||||||
|
<!-- |
||||||
|
// 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 AutomergeEditor from "./AutomergeEditor.svelte"; |
||||||
|
|
||||||
|
export let commits = {}; |
||||||
|
|
||||||
|
</script> |
||||||
|
|
||||||
|
<AutomergeEditor {commits} readonly={true}/> |
@ -0,0 +1,101 @@ |
|||||||
|
<!-- |
||||||
|
// 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 { 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> |
@ -0,0 +1,88 @@ |
|||||||
|
<!-- |
||||||
|
// 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 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> |
@ -0,0 +1,159 @@ |
|||||||
|
<!-- |
||||||
|
// 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. |
||||||
|
--> |
||||||
|
|
||||||
|
<!-- |
||||||
|
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> |
@ -0,0 +1,200 @@ |
|||||||
|
<!-- |
||||||
|
// 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. |
||||||
|
--> |
||||||
|
|
||||||
|
<!-- |
||||||
|
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> |
||||||
|
|
@ -0,0 +1,197 @@ |
|||||||
|
<!-- |
||||||
|
// 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. |
||||||
|
--> |
||||||
|
|
||||||
|
<!-- |
||||||
|
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> |
||||||
|
|
@ -0,0 +1,105 @@ |
|||||||
|
<!-- |
||||||
|
// 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 { 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> |
@ -0,0 +1,120 @@ |
|||||||
|
<!-- |
||||||
|
// 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 { 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> |
||||||
|
|
@ -0,0 +1,126 @@ |
|||||||
|
<!-- |
||||||
|
// 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 { 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> |
@ -0,0 +1,78 @@ |
|||||||
|
<!-- |
||||||
|
// 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 { 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> |
@ -0,0 +1,129 @@ |
|||||||
|
<!-- |
||||||
|
// 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 { 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> |
||||||
|
|
@ -0,0 +1,84 @@ |
|||||||
|
<!-- |
||||||
|
// 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 { 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> |
||||||
|
|
@ -0,0 +1,131 @@ |
|||||||
|
<!-- |
||||||
|
// 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. |
||||||
|
--> |
||||||
|
|
||||||
|
<!-- |
||||||
|
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} |
@ -0,0 +1,19 @@ |
|||||||
|
<!-- |
||||||
|
// 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 YMapEditor from "./YMapEditor.svelte"; |
||||||
|
export let commits = {}; |
||||||
|
|
||||||
|
</script> |
||||||
|
|
||||||
|
<YMapEditor {commits} crdt="YArray"/> |
||||||
|
|
@ -0,0 +1,19 @@ |
|||||||
|
<!-- |
||||||
|
// 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 YMapSource from "./YMapSource.svelte"; |
||||||
|
export let commits = {}; |
||||||
|
|
||||||
|
</script> |
||||||
|
|
||||||
|
<YMapSource {commits} crdt="YArray"/> |
||||||
|
|
@ -0,0 +1,19 @@ |
|||||||
|
<!-- |
||||||
|
// 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 YMapViewer from "./YMapViewer.svelte"; |
||||||
|
export let commits = {}; |
||||||
|
|
||||||
|
</script> |
||||||
|
|
||||||
|
<YMapViewer {commits} crdt="YArray"/> |
||||||
|
|
@ -0,0 +1,291 @@ |
|||||||
|
<!-- |
||||||
|
// 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 { 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> |
@ -0,0 +1,79 @@ |
|||||||
|
<!-- |
||||||
|
// 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 { 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> |
||||||
|
|
@ -0,0 +1,189 @@ |
|||||||
|
<!-- |
||||||
|
// 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 { 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> |
@ -0,0 +1,36 @@ |
|||||||
|
|
||||||
|
<!-- |
||||||
|
// 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 { 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} /> |
@ -0,0 +1,65 @@ |
|||||||
|
|
||||||
|
<!-- |
||||||
|
// 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 { 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> |
@ -0,0 +1,71 @@ |
|||||||
|
|
||||||
|
<!-- |
||||||
|
// 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 { 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> |
@ -0,0 +1,118 @@ |
|||||||
|
|
||||||
|
<!-- |
||||||
|
// 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 { 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> |
@ -0,0 +1,131 @@ |
|||||||
|
|
||||||
|
<!-- |
||||||
|
// 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 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> |
@ -0,0 +1,42 @@ |
|||||||
|
|
||||||
|
<!-- |
||||||
|
// 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 { 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" /> |
@ -0,0 +1,43 @@ |
|||||||
|
|
||||||
|
<!-- |
||||||
|
// 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 { 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" /> |
@ -0,0 +1,87 @@ |
|||||||
|
|
||||||
|
<!-- |
||||||
|
// 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 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} |
@ -0,0 +1,81 @@ |
|||||||
|
// 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.
|
||||||
|
|
||||||
|
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); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,102 @@ |
|||||||
|
/** |
||||||
|
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) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,59 @@ |
|||||||
|
/* |
||||||
|
* 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; |
||||||
|
}; |
||||||
|
|
@ -1,187 +0,0 @@ |
|||||||
<!-- |
|
||||||
// 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)} /> |
|
@ -0,0 +1,224 @@ |
|||||||
|
<!-- |
||||||
|
// 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 { |
||||||
|
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> |
@ -0,0 +1,41 @@ |
|||||||
|
<!-- |
||||||
|
// 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"> |
||||||
|
|
||||||
|
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> |
@ -0,0 +1,57 @@ |
|||||||
|
|
||||||
|
<!-- |
||||||
|
// 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"> |
||||||
|
|
||||||
|
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} |
||||||
|
|
@ -0,0 +1,64 @@ |
|||||||
|
<!-- |
||||||
|
// 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 { |
||||||
|
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> |
@ -0,0 +1,94 @@ |
|||||||
|
<!-- |
||||||
|
// 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 { |
||||||
|
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\">∞</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> |
@ -0,0 +1,49 @@ |
|||||||
|
<!-- |
||||||
|
// 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 { |
||||||
|
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> |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 841 B |
@ -0,0 +1,191 @@ |
|||||||
|
<!-- |
||||||
|
// 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, |
||||||
|
ClipboardDocumentCheck, |
||||||
|
MagnifyingGlass, |
||||||
|
RocketLaunch, |
||||||
|
Sun, |
||||||
|
TableCells, |
||||||
|
ListBullet, |
||||||
|
RectangleGroup, |
||||||
|
Squares2x2, |
||||||
|
MapPin, |
||||||
|
CircleStack, |
||||||
|
Envelope, |
||||||
|
GlobeAlt, |
||||||
|
DocumentChartBar, |
||||||
|
Document, |
||||||
|
ClipboardDocumentList, |
||||||
|
Photo, |
||||||
|
Film, |
||||||
|
RectangleStack, |
||||||
|
SpeakerWave, |
||||||
|
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, |
||||||
|
Cog, |
||||||
|
Square3Stack3d, |
||||||
|
ChatBubbleLeftRight, |
||||||
|
Fire, |
||||||
|
ReceiptPercent, |
||||||
|
ArrowTrendingUp, |
||||||
|
CursorArrowRipple, |
||||||
|
VideoCamera, |
||||||
|
Variable, |
||||||
|
Language, |
||||||
|
QueueList, |
||||||
|
} from "svelte-heros-v2"; |
||||||
|
|
||||||
|
import PdfIcon from "./PdfIcon.svelte"; |
||||||
|
import BrailleIcon from "./BrailleIcon.svelte"; |
||||||
|
import ChemistryIcon from "./ChemistryIcon.svelte"; |
||||||
|
import GuitarIcon from "./GuitarIcon.svelte"; |
||||||
|
import JsonIcon from "./JsonIcon.svelte"; |
||||||
|
import JsIcon from "./JsIcon.svelte"; |
||||||
|
import TsIcon from "./TsIcon.svelte"; |
||||||
|
import RustIcon from "./RustIcon.svelte"; |
||||||
|
import SvelteIcon from "./SvelteIcon.svelte"; |
||||||
|
import ReactIcon from "./ReactIcon.svelte"; |
||||||
|
import GraphQLIcon from "./GraphQLIcon.svelte"; |
||||||
|
|
||||||
|
export let config = {}; |
||||||
|
export let dataClass: string; |
||||||
|
export let color = "currentColor"; |
||||||
|
|
||||||
|
const exact_mapping = { |
||||||
|
page: Window, |
||||||
|
"app": Cog, |
||||||
|
"app:z": SquaresPlus, |
||||||
|
class: ViewfinderCircle, |
||||||
|
contract: ClipboardDocumentCheck, |
||||||
|
"query:text": MagnifyingGlass, |
||||||
|
"query:web": MagnifyingGlass, |
||||||
|
"query:graphql": GraphQLIcon, |
||||||
|
"data:graph": Sun, |
||||||
|
"data:json": JsonIcon, |
||||||
|
"data:map": JsonIcon, |
||||||
|
"data:array": JsonIcon, |
||||||
|
"data:table": TableCells, |
||||||
|
"data:collection": ListBullet, |
||||||
|
"data:container": Square3Stack3d, |
||||||
|
"data:board": RectangleGroup, |
||||||
|
"data:grid": Squares2x2, |
||||||
|
"data:geomap": MapPin, |
||||||
|
"e:email": Envelope, |
||||||
|
"e:link": Link, |
||||||
|
"mc:text": Bars3BottomLeft, |
||||||
|
"mc:link": Link, |
||||||
|
"plato:card": Clipboard, |
||||||
|
"plato:pad": Square2Stack, |
||||||
|
"media:image": Photo, |
||||||
|
"media:reel": VideoCamera, |
||||||
|
"media:video": Film, |
||||||
|
"media:album": RectangleStack, |
||||||
|
"media:audio": SpeakerWave, |
||||||
|
"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, |
||||||
|
"social:chatroom": ChatBubbleLeftRight, |
||||||
|
"social:live": Fire, |
||||||
|
"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": CursorArrowRipple, |
||||||
|
"prod:vote": CheckCircle, |
||||||
|
"prod:spreadsheet": ReceiptPercent, |
||||||
|
"doc:compose": QueueList, |
||||||
|
"doc:maths": Variable, |
||||||
|
"doc:music:abc": MusicalNote, |
||||||
|
"doc:pdf": PdfIcon, |
||||||
|
"doc:braille": BrailleIcon, |
||||||
|
"doc:ancientscript": Language, |
||||||
|
"doc:chemistry": ChemistryIcon, |
||||||
|
"doc:music:guitar": GuitarIcon, |
||||||
|
"code:js": JsIcon, |
||||||
|
"code:ts": TsIcon, |
||||||
|
"code:rust": RustIcon, |
||||||
|
"code:svelte": SvelteIcon, |
||||||
|
"code:react": ReactIcon, |
||||||
|
}; |
||||||
|
|
||||||
|
const prefix_mapping = { |
||||||
|
"post:": DocumentText, |
||||||
|
code: CodeBracket, |
||||||
|
schema: ArrowsPointingOut, |
||||||
|
service: Cube, |
||||||
|
"e:": GlobeAlt, |
||||||
|
"app:": StopCircle, |
||||||
|
"query:": RocketLaunch, |
||||||
|
"data:": CircleStack, |
||||||
|
"diagram": DocumentChartBar, |
||||||
|
"chart": ChartPie, |
||||||
|
"viz": ArrowTrendingUp, |
||||||
|
"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> |
||||||
|
|
||||||
|
<Icon {...config} variation="outline" {color} icon={find(dataClass)} /> |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 665 B |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 725 B |
@ -0,0 +1,64 @@ |
|||||||
|
<!-- |
||||||
|
// 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. |
||||||
|
--> |
||||||
|
|
||||||
|
<!-- |
||||||
|
@component DeviceIcon |
||||||
|
Display an icon for a device class provided by the `device` attribute. |
||||||
|
Pass `config` for custom attributes. |
||||||
|
--> |
||||||
|
|
||||||
|
<script lang="ts"> |
||||||
|
import { |
||||||
|
Icon, |
||||||
|
Bolt, |
||||||
|
Megaphone, |
||||||
|
QuestionMarkCircle, |
||||||
|
ExclamationCircle, |
||||||
|
Key, |
||||||
|
LockClosed, |
||||||
|
GlobeAlt, |
||||||
|
UserGroup, |
||||||
|
PaperAirplane, |
||||||
|
} from "svelte-heros-v2"; |
||||||
|
|
||||||
|
import DataClassIcon from "./DataClassIcon.svelte"; |
||||||
|
|
||||||
|
export let config = {}; |
||||||
|
export let img: string; |
||||||
|
|
||||||
|
const mapping = { |
||||||
|
stream: Bolt, |
||||||
|
channel: Megaphone, |
||||||
|
private: Key, |
||||||
|
protected: LockClosed, |
||||||
|
public: GlobeAlt, |
||||||
|
group: UserGroup, |
||||||
|
dialog: PaperAirplane, |
||||||
|
unknown_doc: ExclamationCircle, |
||||||
|
}; |
||||||
|
|
||||||
|
const find = (dataClass: string) => { |
||||||
|
return mapping[dataClass] || QuestionMarkCircle; |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
{#if img.startsWith("blob:")} |
||||||
|
<img style="aspect-ratio:1;" class="rounded-full" src={img} alt="profile"/> |
||||||
|
{:else if img.startsWith("class:")} |
||||||
|
<DataClassIcon {config} dataClass={img.slice(6)} /> |
||||||
|
{:else if img.startsWith("nav:")} |
||||||
|
<Icon {...config} variation="outline" color="currentColor" icon={find(img.slice(4))} /> |
||||||
|
{:else} |
||||||
|
<QuestionMarkCircle {...config}/> |
||||||
|
{/if} |
||||||
|
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 429 B |
@ -0,0 +1,236 @@ |
|||||||
|
<!-- |
||||||
|
// 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 ng from "../../api"; |
||||||
|
import { |
||||||
|
branch_subscribe, |
||||||
|
active_session, |
||||||
|
online, |
||||||
|
get_blob, |
||||||
|
} from "../../store"; |
||||||
|
import { cur_tab, cur_tab_doc_can_edit } from "../../tab"; |
||||||
|
import { |
||||||
|
ExclamationTriangle, |
||||||
|
ArrowDownTray, |
||||||
|
ArrowUpTray, |
||||||
|
} from "svelte-heros-v2"; |
||||||
|
|
||||||
|
import { onMount, onDestroy, tick } from "svelte"; |
||||||
|
import { Button, Progressbar, Spinner } from "flowbite-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 commits = $active_session && branch_subscribe($cur_tab.branch.nuri+":"+$cur_tab.store.overlay, false); |
||||||
|
let fileinput; |
||||||
|
|
||||||
|
let file_urls = {}; |
||||||
|
const prepare_url = (nuri) => { |
||||||
|
if (!file_urls[nuri]) { |
||||||
|
file_urls[nuri] = { |
||||||
|
click: false |
||||||
|
}; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
const download = async (file) => { |
||||||
|
if (is_tauri) { |
||||||
|
await ng.file_save_to_downloads($active_session.session_id, file.reference, file.name, "did:ng:"+$cur_tab.branch.nuri+":"+$cur_tab.store.overlay); |
||||||
|
} else { |
||||||
|
file_urls[file.nuri].url = await get_blob(file, false); |
||||||
|
//console.log(file.name); |
||||||
|
//console.log(file_urls[file.nuri].click); |
||||||
|
await tick(); |
||||||
|
file_urls[file.nuri].click.click(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const isImage = async (url) : Promise<boolean> => { |
||||||
|
if ( typeof url === 'string' ) { |
||||||
|
let blob = await fetch(url).then(r => r.blob()); |
||||||
|
return blob.type.startsWith("image/"); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
function uploadFile(upload_id, nuri, file, success) { |
||||||
|
//console.log(nuri); |
||||||
|
let chunkSize = 1_048_564; |
||||||
|
let fileSize = file.size; |
||||||
|
let offset = 0; |
||||||
|
let readBlock = null; |
||||||
|
upload_progress = { total: fileSize, current: offset }; |
||||||
|
|
||||||
|
let onLoadHandler = async function (event) { |
||||||
|
let result = event.target.result; |
||||||
|
|
||||||
|
if (event.target.error == null) { |
||||||
|
offset += result.byteLength; |
||||||
|
upload_progress = { total: fileSize, current: offset }; |
||||||
|
|
||||||
|
// console.log("chunk", result); |
||||||
|
|
||||||
|
let res = await ng.upload_chunk( |
||||||
|
$active_session.session_id, |
||||||
|
upload_id, |
||||||
|
result, |
||||||
|
nuri |
||||||
|
); |
||||||
|
//console.log("chunk upload res", res); |
||||||
|
// if (onChunkRead) { |
||||||
|
// onChunkRead(result); |
||||||
|
// } |
||||||
|
} else { |
||||||
|
// if (onChunkError) { |
||||||
|
// onChunkError(event.target.error); |
||||||
|
// } |
||||||
|
upload_progress = { total: fileSize, current: fileSize, error: true }; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// If finished: |
||||||
|
if (offset >= fileSize) { |
||||||
|
//console.log("file uploaded"); |
||||||
|
let res = await ng.upload_chunk( |
||||||
|
$active_session.session_id, |
||||||
|
upload_id, |
||||||
|
[], |
||||||
|
nuri |
||||||
|
); |
||||||
|
//console.log("end upload res", res); |
||||||
|
if (success) { |
||||||
|
upload_progress = { total: fileSize, current: fileSize }; |
||||||
|
await success(res); |
||||||
|
// Make progress bar disappear |
||||||
|
setTimeout(() => { |
||||||
|
upload_progress = null; |
||||||
|
}, 1_000); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
readBlock(offset, chunkSize, file); |
||||||
|
}; |
||||||
|
|
||||||
|
readBlock = function (offset, length, file) { |
||||||
|
let fileReader = new FileReader(); |
||||||
|
let blob = file.slice(offset, length + offset); |
||||||
|
fileReader.onload = onLoadHandler; |
||||||
|
fileReader.readAsArrayBuffer(blob); |
||||||
|
}; |
||||||
|
|
||||||
|
readBlock(offset, chunkSize, file); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const onFileSelected = async (e) => { |
||||||
|
let image = e.target.files[0]; |
||||||
|
if (!image) return; |
||||||
|
//console.log(image.type); |
||||||
|
|
||||||
|
let start_request_payload = { |
||||||
|
RandomAccessFilePut: image.type, |
||||||
|
}; |
||||||
|
let nuri = "did:ng:"+$cur_tab.branch.nuri+":"+$cur_tab.store.overlay; |
||||||
|
let start_res = await ng.app_request_with_nuri_command(nuri, "FilePut", $active_session.session_id, start_request_payload); |
||||||
|
let upload_id = start_res.V0.FileUploading; |
||||||
|
|
||||||
|
uploadFile(upload_id, nuri, image, async (reference) => { |
||||||
|
if (reference) { |
||||||
|
let file_put_payload = { |
||||||
|
AddFile: { |
||||||
|
filename: image.name, |
||||||
|
object: reference.V0.FileUploaded, |
||||||
|
}, |
||||||
|
}; |
||||||
|
await ng.app_request_with_nuri_command(nuri, "FilePut", $active_session.session_id, file_put_payload); |
||||||
|
} |
||||||
|
}); |
||||||
|
fileinput.value = ""; |
||||||
|
}; |
||||||
|
|
||||||
|
</script> |
||||||
|
|
||||||
|
|
||||||
|
<div class="w-full"> |
||||||
|
{#if $cur_tab_doc_can_edit} |
||||||
|
<div class="row pt-2 w-full"> |
||||||
|
|
||||||
|
<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" |
||||||
|
> |
||||||
|
<ArrowUpTray class="w-8 h-8 mr-2 -ml-1"/> |
||||||
|
{$t("doc.file.upload")} |
||||||
|
</Button> |
||||||
|
<input |
||||||
|
style="display:none" |
||||||
|
type="file" |
||||||
|
on:change={(e) => onFileSelected(e)} |
||||||
|
bind:this={fileinput} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
{/if} |
||||||
|
{#if upload_progress !== null} |
||||||
|
<div class="mx-6 mt-2"> |
||||||
|
<Progressbar |
||||||
|
progress={( |
||||||
|
(100 * upload_progress.current) / |
||||||
|
upload_progress.total |
||||||
|
).toFixed(0)} |
||||||
|
labelOutside={$t("doc.file.upload_progress")} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
{/if} |
||||||
|
{#if commits} |
||||||
|
{#await commits.load()} |
||||||
|
<p>{$t("connectivity.loading")}...</p> |
||||||
|
{:then} |
||||||
|
{#each $commits.files as file} |
||||||
|
<p class="mb-5"> |
||||||
|
|
||||||
|
{#await get_blob(file, true)} |
||||||
|
<div class="ml-2"> |
||||||
|
<Spinner /> |
||||||
|
</div> |
||||||
|
{:then url} |
||||||
|
{#await isImage(url) then is} |
||||||
|
{#if is} |
||||||
|
<img src={url} title={file.nuri} alt={file.name} /> |
||||||
|
{/if} |
||||||
|
{/await} |
||||||
|
<span class="ml-2 text-gray-600">{file.name}<br/></span> |
||||||
|
{#if url === false} |
||||||
|
<span><ExclamationTriangle tabindex="-1" class="ml-2 w-6 h-8 focus:outline-none" style="display:inline"/>{$t("errors.cannot_load_this_file")}</span> |
||||||
|
{:else if prepare_url(file.nuri)} |
||||||
|
<a bind:this={file_urls[file.nuri].click} |
||||||
|
href={file_urls[file.nuri].url || ""} |
||||||
|
target="_blank" |
||||||
|
download={file.name} |
||||||
|
></a> |
||||||
|
<button class="ml-2 select-none p-1 pb-0 pt-0 text-gray-600" style="box-shadow:none;" on:click={()=>download(file)}> |
||||||
|
<span><ArrowDownTray tabindex="-1" class="w-6 h-8 mr-3 focus:outline-none" style="display:inline"/>{$t("doc.file.download")}</span> |
||||||
|
</button> |
||||||
|
{/if} |
||||||
|
{/await} |
||||||
|
</p> |
||||||
|
{/each} |
||||||
|
{/await} |
||||||
|
{/if} |
||||||
|
|
||||||
|
</div> |