--- /dev/null
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "bumpalo"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "Could not get crate checksum"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "Could not get crate checksum"
+
+[[package]]
+name = "futf"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b"
+dependencies = [
+ "mac",
+ "new_debug_unreachable",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "Could not get crate checksum"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+]
+
+[[package]]
+name = "html5ever"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b"
+dependencies = [
+ "log",
+ "mac",
+ "markup5ever",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
+
+[[package]]
+name = "js-sys"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.138"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
+
+[[package]]
+name = "log"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "Could not get crate checksum"
+dependencies = [
+ "cfg-if 0.1.10",
+]
+
+[[package]]
+name = "mac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
+
+[[package]]
+name = "markup5ever"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aae38d669396ca9b707bfc3db254bc382ddb94f57cc5c235f34623a669a01dab"
+dependencies = [
+ "log",
+ "phf",
+ "phf_codegen",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "string_cache",
+ "string_cache_codegen",
+ "tendril",
+]
+
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cdc457076c78ab54d5e0d6fa7c47981757f1e34dc39ff92787f217dede586c4"
+dependencies = [
+ "unreachable",
+]
+
+[[package]]
+name = "phf"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "Could not get crate checksum"
+dependencies = [
+ "phf_shared",
+ "rand",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "Could not get crate checksum"
+
+[[package]]
+name = "precomputed-hash"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "Could not get crate checksum"
+dependencies = [
+ "getrandom",
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+ "rand_pcg",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
+
+[[package]]
+name = "serde"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399"
+
+[[package]]
+name = "serde_derive"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sharedmodel"
+version = "0.1.0"
+dependencies = [
+ "html5ever",
+ "string_cache",
+ "uuid",
+]
+
+[[package]]
+name = "sharedmodel_wasm"
+version = "0.1.0"
+dependencies = [
+ "js-sys",
+ "sharedmodel",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83da420ee8d1a89e640d0948c646c1c088758d3a3c538f943bfa97bdac17929d"
+
+[[package]]
+name = "string_cache"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2940c75beb4e3bf3a494cef919a747a2cb81e52571e212bfbd185074add7208a"
+dependencies = [
+ "lazy_static",
+ "new_debug_unreachable",
+ "phf_shared",
+ "precomputed-hash",
+ "serde",
+]
+
+[[package]]
+name = "string_cache_codegen"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "Could not get crate checksum"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tendril"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9de21546595a0873061940d994bbbc5c35f024ae4fd61ec5c5b159115684f508"
+dependencies = [
+ "futf",
+ "mac",
+ "utf-8",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
+
+[[package]]
+name = "unreachable"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
+dependencies = [
+ "void",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
+
+[[package]]
+name = "uuid"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "Could not get crate checksum"
+
+[[package]]
+name = "void"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
+dependencies = [
+ "cfg-if 1.0.0",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
+
+[[package]]
+name = "web-sys"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
--- /dev/null
+[workspace]
+members = ['sharedmodel', 'sharedmodel_wasm']
--- /dev/null
+WebRTC
+
+https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Simple_RTCDataChannel_sample
+
+WebSocket SaaS
+https://github.com/ably/ably-js
+
+Conflict-free replicated data types
+
+https://crdt.tech/papers.html
+https://arxiv.org/abs/1806.10254
+
+Bazel
+
+https://brentley.dev/patching-bazel-external-dependencies/
+https://bazelbuild.github.io/rules_rust/
+
+Rust
+
+https://salsa.debian.org/rust-team/debcargo/
+https://wiki.debian.org/Teams/RustPackaging/Policy
+https://docs.rs/tui/latest/tui/
+
+LC in-browser initiative
+name "shared model"
+Speedcounting relies on strikebot to react fast, which leads to mistake strikes
+and requires rate limit circumvention.
+
+With "shared model" we enable strike bot to react much slower, while giving
+human feedback for speedcounting with even lower latency than strike bot can.
+
+Each in-browser view computes which updates to display as stricken, as
+prediction of the eventual thread state. This relies on the following aspects:
+* Views are eventually consistent on the set of updates in the viewport, their
+order, and which ones are stricken and deleted, without needing to refresh.
+At the most basic this will require transpo handling, but it also requires
+pulling liveupdate listings, critically when there is a websocket
+discontinuity, but at other times also: e.g. to confirm strikes.
+* Strike bot broadcasts thread tracking information periodically. The tracking
+information has to cover from the strike bot "frozen" timestamp (prior to this
+timestamp, the count tracking is reflected in reddit data and strike bot won't
+issue new strikes or deletes; can be far in the past and/or many updates ago
+if strike bot is overloaded) up until relatively recently (~25 updates ago),
+because views will have to load this far back in order to do local thread
+tracking. This may not have to include an entry for every update ID, and
+probably shouldn't due to traffic volume.
+
+We can actually do this WITHOUT P2P which is good because otherwise it won't
+happen.
+
+Strike bot HA: two or more instances can coordinate via reddit posts (comments)
+to elect a leader; the followers don't strike or delete but remain ready to
+promote if the leader disappears. No need to load-balance. Basically each
+instance posts "I'm alive" periodically to a separate area (editing a reddit
+comment); the leader instance is the lowest-numbered area whose timestamp isn't
+too stale.
+
+Strike bots do have to broadcast with this system so we need to be able to
+WebSocket connect to them. Hosting: bingus, strig (while exists), jcornell.net,
+occs?
+
+Ooh or we could use reddit for the broadcasts...hehehe.
+Basically we have some fixed amount of space (10000 code points: use base65536)
+and we want to encode...essentially a "valid count" list of update IDs.
+
+Handling count_better resets:
+- find the very next update that "can" be a valid count:
+ - has a number
+ - not stricken nor deleted
+ - it could be the same one as contains the reset command.
+- assume this one is valid; subsequent updates are (re)interpreted accordingly.
+- now find the latest valid count that *precedes* the reset. Try every
+ subsequent update, look for one that begins a chain of valid counts leading
+ up to the post-reset count.
+
+Algorithm for thread interpretation
+Start with no last_valid_ix, empty set of restart points, reset flag clear, ix = 0
+Loop start
+ If ix is out of bounds
+ If last_valid
+ Mark everything invalid after last_valid
+ Break
+ Failure
+ If update ix is not stricken
+ If update ix is reset command
+ Set reset flag
+ If update ix has number (n)
+ if update ix matches last_valid
+ Matching means last_valid was n-1 and the author is different
+ Set valid=true
+ Else if reset flag OR (no last_valid AND this update is blessed in the latest leader message)
+ Set ix to the earliest matching restart point, if any
+ Matching means the end of the restart point is n-1 with different author
+ Set valid=true
+ Else
+ If last_valid
+ Mark ix invalid
+ Is there a matching restart point?
+ Update the end of the earliest matching restart point to ix and remove other matching restart points
+ If no restart points matched
+ Create a new one with ix as start and end
+ If valid:
+ Clear the set of restart points
+ Clear the reset flag
+ Mark ix valid
+ Set last_valid to ix
+ Increment ix
--- /dev/null
+[package]
+name = "sharedmodel"
+version = "0.1.0"
+authors = ["Andrea Cornell <anders@acorn.pw>"]
+edition = "2018"
+
+#[lib]
+#name = "sharedmodel"
+#path = "src/lib.rs"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+#[[bin]]
+#name = "sharedmodel"
+#path = "src/main.rs"
+
+[dependencies]
+uuid = "0.8"
+html5ever = "0.25"
+string_cache = "0.8"
--- /dev/null
+use std::borrow::Borrow;
+
+#[derive(Debug)]
+pub enum HtmlNode<T, U> {
+ Text(T),
+ Element(U)
+}
+
+pub trait HtmlElement: Sized {
+ type Text1: AsRef<str>;
+ type Text2: AsRef<str>;
+ type ContentsIter: DoubleEndedIterator<Item=HtmlNode<Self::Text1, Self>>;
+ fn name(&self) -> &str;
+ fn contents(self) -> Self::ContentsIter;
+ fn text(&self) -> Self::Text2;
+}
--- /dev/null
+mod live_event_buffer;
+mod html_nodes;
+
+pub use live_event_buffer::LiveUpdateBuffer;
+pub use html_nodes::{HtmlNode, HtmlElement};
--- /dev/null
+use uuid::Uuid;
+use crate::html_nodes::HtmlElement;
+
+pub struct LiveUpdateMeta {
+ id: Uuid,
+ author: String,
+ stricken: bool
+}
+
+struct BufferedUpdate {
+ inner: LiveUpdateMeta,
+ counted_number: Option<i32>,
+ is_reset: bool,
+ bad_format: bool,
+ is_valid: bool
+}
+
+pub struct LiveUpdateBuffer {
+ capacity: usize,
+ buf: Vec<BufferedUpdate>
+}
+
+pub struct LeaderMessage {
+ blessed_ids: Vec<Uuid>
+}
+
+pub enum BufferEvent<T> {
+ Add { before: Option<Uuid>, meta: LiveUpdateMeta, body: T },
+ SetValid { id: Uuid, valid: bool },
+ Strike { id: Uuid },
+ Remove { id: Uuid }
+}
+
+impl LiveUpdateBuffer {
+ pub fn handle_updates<T: HtmlElement>(&mut self, u: &mut Vec<(LiveUpdateMeta, &T)>, ev: &mut Vec<BufferEvent<&T>>) {
+
+ }
+
+ pub fn handle_strike(&mut self, id: Uuid, ev: &mut Vec<BufferEvent<()>>) {
+
+ }
+
+ pub fn handle_delete(&mut self, id: Uuid, ev: &mut Vec<BufferEvent<()>>) {
+
+ }
+
+ pub fn handle_leader_msg(&mut self, data: LeaderMessage, ev: &mut Vec<BufferEvent<()>>) {
+
+ }
+
+ fn can_follow(&self, ix1: usize, ix2: usize) -> bool {
+ let upd1 = self.buf.get(ix1).unwrap();
+ let upd2 = self.buf.get(ix2).unwrap();
+ if upd1.inner.stricken || upd2.inner.stricken || upd1.inner.author.eq(&upd2.inner.author) {
+ return false;
+ }
+ match (upd1.counted_number, upd2.counted_number) {
+ (Some(n1), Some(n2)) => n2 == n1 + 1,
+ _ => false
+ }
+ }
+
+ fn mark_valid(&mut self, ix: usize, valid: bool, ev: &mut Vec<BufferEvent<()>>) {
+ let upd = self.buf.get_mut(ix).unwrap();
+ upd.is_valid = valid;
+ ev.push(BufferEvent::SetValid { id: upd.inner.id.clone(), valid })
+ }
+
+ fn interpret_thread(&mut self, ev: &mut Vec<BufferEvent<()>>) {
+ let mut last_valid: Option<usize> = None;
+ let mut restart_points = Vec::<(usize, usize)>::new();
+ let mut reset_pending = false;
+ let mut ix = 0;
+ loop {
+ if ix >= self.buf.len() {
+ if let Some(last_valid) = last_valid {
+ for ix in last_valid..ix {
+ self.mark_valid(ix, false, ev);
+ }
+ } // else: failure
+ break;
+ }
+ let upd = self.buf.get(ix).unwrap();
+ if !upd.inner.stricken {
+ if upd.is_reset {
+ reset_pending = true;
+ }
+ if let Some(n) = upd.counted_number {
+ let is_valid = last_valid.map_or(self.check_blessed(ix), |last_valid| self.can_follow(last_valid, ix)) || reset_pending;
+ if reset_pending {
+ if let Some(restart_from) = restart_points.iter().find_map(|(restart_from, restart_to)| self.can_follow(*restart_to, ix).then_some(restart_from) ) {
+// if let Some(restart_from) = restart_points.iter().find_map(|(restart_from, restart_to)| /*self.can_follow(*restart_to, ix).then_some(restart_from)*/ if self.can_follow(*restart_to, ix) {Some(restart_from)} else {None}) {
+ ix = *restart_from;
+ }
+ }
+ if is_valid {
+ restart_points.clear();
+ reset_pending = false;
+ self.mark_valid(ix, true, ev);
+ last_valid = Some(ix);
+ }
+ else {
+ if last_valid.is_some() {
+ self.mark_valid(ix, false, ev);
+ }
+ let mut cur = 0;
+ let mut matching = None;
+ restart_points.retain(|(restart_from, restart_to)| {
+ let cur0 = cur;
+ cur += 1;
+ if !self.can_follow(*restart_to, ix) {
+ return true;
+ }
+ if matching.is_none() {
+ matching = Some(cur0);
+ return true;
+ }
+ false
+ });
+ match matching {
+ Some(ix) => {
+ let o: &mut (usize, usize) = restart_points.get_mut(ix).unwrap();
+ o.1 = ix },
+ None => restart_points.push((ix, ix))
+ }
+ }
+ }
+ }
+ ix += 1;
+ }
+ }
+
+ fn check_blessed(&self, ix: usize) -> bool {
+ // Check the latest leader message to see if the given update is blessed
+ false
+ }
+}
--- /dev/null
+use std::cell::{Cell, RefCell};
+use std::rc::{Rc, Weak};
+use std::borrow::Cow;
+
+use html5ever::tree_builder::{TreeSink, ElementFlags};
+use html5ever::{QualName, ExpandedName, LocalNameStaticSet, Attribute};
+use html5ever::tendril::{StrTendril, TendrilSink};
+use html5ever::interface::{NodeOrText, QuirksMode};
+use html5ever::driver::ParseOpts;
+use string_cache::Atom;
+
+use sharedmodel::{HtmlNode, HtmlElement};
+
+fn main() {
+ let mut x = html5ever::parse_fragment(RcHtmlElement(Rc::new(HtmlElementInner::root())), ParseOpts::default(), QualName::new(None, Atom::from("http://www.w3.org/1999/xhtml"), Atom::from("body")), Vec::new());
+ //let html = "<p>This is <em>a <a href=\"#\">paragraph</a></em>. There are</p><ul><li>nested</li><li>tags, <strong>especially in <em>here</em></strong></li></ul>";
+ //let html = "<p>A paragraph</p>";
+ let html = "<section><p>Can you handle this</p><table><tr><th>a</th><th>b</th></tr><tr><td>1</td><td>2</td></tr><tr><td>3</td><td>4</td></tr></table></section>";
+ x.process(StrTendril::from(html));
+ let tr = x.finish();
+ println!("{:?}", tr);
+ println!("{}", tr.text())
+}
+
+type RcNode<'a> = HtmlNode<StrTendril, RcHtmlElement<'a>>;
+
+// #[derive(Debug)]
+struct HtmlElementInner<'a> {
+ name: QualName,
+ parent: Cell<Weak<HtmlElementInner<'a>>>,
+ contents: RefCell<Vec<RcNode<'a>>>
+}
+
+impl HtmlElementInner<'_> {
+ fn root() -> Self {
+ let name = QualName::new(None, Atom::from(""), Atom::from(""));
+ let parent = Cell::new(Weak::new());
+ let contents = RefCell::new(Vec::new());
+ HtmlElementInner { name, parent, contents }
+ }
+}
+
+//#[derive(Clone, Debug)]
+#[derive(Clone)]
+struct RcHtmlElement<'a>(Rc<HtmlElementInner<'a>>);
+
+fn nodeortext_to_htmlnode<'a>(v: NodeOrText<RcHtmlElement<'a>>) -> RcNode<'a> {
+ match v {
+ NodeOrText::AppendNode(h) => HtmlNode::Element(h),
+ NodeOrText::AppendText(s) => HtmlNode::Text(s)
+ }
+}
+
+fn node_eq_elem<'a>(node: &RcNode<'a>, elem: &RcHtmlElement<'a>) -> bool {
+ match node {
+ HtmlNode::Element(elem1) if Rc::ptr_eq(&elem.0, &elem1.0) => true,
+ _ => false
+ }
+}
+
+impl<'a> TreeSink for RcHtmlElement<'a> {
+ type Handle = Self;
+ type Output = UniqHtmlElement;
+ fn finish(self) -> UniqHtmlElement {
+ // let HtmlElementInner { name, parent, contents } = Rc::try_unwrap(self.0).map_err(|_| ()).expect("no refs left around");
+ let HtmlElementInner { ref name, ref contents, .. } = *self.0;
+ let name = name.local.clone();
+ let contents = contents.borrow_mut().drain(..).map(|node| {
+ match node {
+ HtmlNode::Text(s) => HtmlNode::Text(s),
+ HtmlNode::Element(e) => HtmlNode::Element(e.finish())
+ }
+ }).collect();
+ UniqHtmlElement { name, contents }
+ }
+
+ fn parse_error(&mut self, msg: Cow<'static, str>) {
+ println!("{}", msg);
+ panic!("how do parse??");
+ }
+
+ fn get_document(&mut self) -> Self::Handle {
+ self.clone()
+ }
+
+ fn elem_name<'b>(&'b self, target: &'b Self::Handle) -> ExpandedName<'b> {
+ target.0.name.expanded()
+ }
+
+ fn create_element(&mut self, name: QualName, attrs: Vec<Attribute>, flags: ElementFlags) -> Self::Handle {
+ assert_eq!(flags.template, false);
+ assert_eq!(flags.mathml_annotation_xml_integration_point, false);
+ let contents = RefCell::new(Vec::new());
+ let parent = Cell::new(Weak::new());
+ // handily, we never use attribute data. Toss it
+ RcHtmlElement(Rc::new(HtmlElementInner { name, parent, contents }))
+ }
+
+ fn create_comment(&mut self, text: StrTendril) -> Self::Handle {
+ panic!("HTML parser encountered a comment");
+ }
+
+ fn create_pi(&mut self, target: StrTendril, data: StrTendril) -> Self::Handle {
+ panic!("HTML parser encountered a processing instruction");
+ }
+
+ fn append(&mut self, parent: &Self::Handle, child: NodeOrText<Self::Handle>) {
+ let node = nodeortext_to_htmlnode(child);
+ if let HtmlNode::Element(ref elem) = node {
+ elem.0.parent.set(Rc::downgrade(&parent.0));
+ }
+ parent.0.contents.borrow_mut().push(node);
+ }
+
+ fn append_based_on_parent_node(&mut self, element: &Self::Handle, prev_element: &Self::Handle, child: NodeOrText<Self::Handle>) {
+ panic!("Not clear what this is for or if it needs to be implemented");
+ }
+
+ fn append_doctype_to_document(&mut self, name: StrTendril, public_id: StrTendril, system_id: StrTendril) {
+ panic!("This can likely be a no-op, just not sure if it gets called for these fragments");
+ }
+
+ fn get_template_contents(&mut self, target: &Self::Handle) -> Self::Handle {
+ panic!("HTML parser encountered a <template> element")
+ }
+
+ fn same_node(&self, x: &Self::Handle, y: &Self::Handle) -> bool {
+ Rc::ptr_eq(&x.0, &y.0)
+ }
+
+ fn set_quirks_mode(&mut self, mode: QuirksMode) {}
+
+ fn append_before_sibling(&mut self, sibling: &Self::Handle, new_node: NodeOrText<Self::Handle>) {
+ let parent_weak = sibling.0.parent.take();
+ let parent = parent_weak.upgrade().expect("parent is not dropped");
+ let mut children = parent.contents.borrow_mut();
+ sibling.0.parent.set(parent_weak);
+ let pos = children.iter().position(|node| node_eq_elem(node, sibling)).expect("sibling is child of parent");
+ children.insert(pos, nodeortext_to_htmlnode(new_node));
+ }
+
+ fn add_attrs_if_missing(&mut self, target: &Self::Handle, attrs: Vec<Attribute>) {}
+
+ fn remove_from_parent(&mut self, target: &Self::Handle) {
+ let parent = target.0.parent.replace(Weak::new()).upgrade().expect("target has parent");
+ let mut children = parent.contents.borrow_mut();
+ let pos = children.iter().position(|node| node_eq_elem(node, target)).expect("sibling is child of parent");
+ children.remove(pos);
+ }
+
+ fn reparent_children(&mut self, node: &Self::Handle, new_parent: &Self::Handle) {
+ new_parent.0.contents.borrow_mut().extend(node.0.contents.borrow_mut().drain(..));
+ }
+}
+
+
+#[derive(Debug)]
+struct UniqHtmlElement {
+ name: Atom<LocalNameStaticSet>,
+ contents: Vec<HtmlNode<StrTendril, UniqHtmlElement>>
+}
+
+/*fn node_text(node: &HtmlNode<StrTendril, UniqHtmlElement>) -> Cow<str> {
+ match node {
+ HtmlNode::Text(s) => Cow::Borrowed(s.as_ref()),
+ HtmlNode::Element(e) => e.text()
+ }
+}*/
+
+fn element_text_inner(buf: &mut Option<StrTendril>, elem: &UniqHtmlElement) {
+ for node in &elem.contents {
+ match node {
+ HtmlNode::Text(s) => match buf {
+ None => { *buf = Some(s.clone()); },
+ Some(tendril) => tendril.push_tendril(&s)
+ },
+ HtmlNode::Element(e) => element_text_inner(buf, &e)
+ }
+ }
+}
+
+impl HtmlElement for UniqHtmlElement {
+ type Text1 = StrTendril;
+ type Text2 = StrTendril;
+ type ContentsIter = <Vec<HtmlNode<Self::Text1, Self>> as IntoIterator>::IntoIter;
+ fn name(&self) -> &str {
+ &*self.name
+ }
+ fn contents(self) -> Self::ContentsIter {
+ self.contents.into_iter()
+ }
+ fn text(&self) -> StrTendril {
+ let mut result = None;
+ element_text_inner(&mut result, self);
+ result.unwrap_or(StrTendril::new())
+/* let mut init = self.contents.get(0).map(node_text).unwrap_or(Cow::Borrowed(""));
+ for node in &self.contents[1..] {
+ let text = node_text(node);
+ if text.len() > 0 {
+ init.to_mut().push_str(&text);
+ }
+ }
+ init*/
+ }
+}
--- /dev/null
+[package]
+name = "sharedmodel_wasm"
+version = "0.1.0"
+authors = ["Andrea Cornell <anders@acorn.pw>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+js-sys = "0.3"
+web-sys = { version = "0.3", features = ["HtmlElement", "NodeList"] }
+wasm-bindgen = "0.2"
+
+[dependencies.sharedmodel]
+path = "../sharedmodel"
--- /dev/null
+use sharedmodel::{HtmlElement, HtmlNode};
+
+struct JsDomHtmlElement {
+ tag_name: String,
+ elem: web_sys::HtmlElement
+}
+
+impl JsDomHtmlElement {
+ fn new(elem: web_sys::HtmlElement) -> Self {
+ let tag_name = elem.tag_name();
+ Self { tag_name, elem }
+ }
+}
+
+impl HtmlElement for JsDomHtmlElement {
+ type Text1 = String;
+ type Text2 = String;
+// type Node = HtmlNode<Self::Text1, Self>;
+ type ContentsIter = std::iter::FilterMap<JsDomContentsIter, fn(web_sys::Node) -> Option<HtmlNode<String, JsDomHtmlElement>>>;
+ fn name(&self) -> &str {
+ &self.tag_name
+/* static names = ["em", "strong", "del", "span", "sup", "code", "a", "th", "td",
+ "ul", "ol", "table", "thead", "tbody", "li", "p", "div", "blockquote",
+ "h1", "h2", "h3", "h4", "h5", "h6"];
+ static name_jsmap = Lazy::new(|| {
+ let v = js_sys::Map::new();
+ for (name, ix) in names.enumerate() {
+ v.add(JsValue::from_str(name), JsValue::from_f64(ix));
+ }
+ v
+ });*/
+ }
+ fn contents(self) -> Self::ContentsIter {
+ JsDomContentsIter::new(self.elem).filter_map(|node| {
+ use web_sys::Node;
+ use wasm_bindgen::JsCast;
+ match node.node_type() {
+ Node::ELEMENT_NODE => Some(HtmlNode::Element(JsDomHtmlElement::new(node.dyn_into().expect("Element node instanceof HtmlElement")))),
+ Node::TEXT_NODE => Some(HtmlNode::Text(node.text_content().unwrap_or(String::new()))),
+ _ => None
+ }
+ })
+ }
+ fn text(&self) -> String {
+ self.elem.text_content().unwrap_or(String::new())
+ }
+}
+
+struct JsDomContentsIter {
+ parent: web_sys::HtmlElement,
+ head: Option<web_sys::Node>,
+ tail: Option<web_sys::Node>,
+}
+
+//struct JsDomContentsIter(Option<(web_sys::Node, web_sys::Node)>);
+
+impl JsDomContentsIter {
+ fn new(elem: web_sys::HtmlElement) -> Self {
+ Self { parent: elem, head: None, tail: None }
+ }
+}
+
+impl Iterator for JsDomContentsIter {
+ type Item = web_sys::Node; // HtmlNode<String, JsDomHtmlElement>;
+ fn next(&mut self) -> Option<Self::Item> {
+ let next = self.head.as_ref().map_or_else(
+ || self.parent.first_child(),
+ |head| head.next_sibling()
+ );
+ if let Some(next) = next {
+ if self.tail.as_ref().filter(|tail| next.eq(tail)).is_none() {
+ self.head = Some(next.clone());
+ return Some(next);
+ }
+ }
+ None
+ } /*
+ self.head = self.head.or_else(
+ || self.parent.first_child(),
+ |head| head
+ );
+ self.head.filter(|head| !self.tail.and_then(|tail| head.eq(tail)))
+ }
+ if self.ended {
+ None
+ }
+ else {
+
+ self.ended = true;
+ }
+ // TODO need to skip over attribute nodes
+ let ret = self.nodelist.item(self.index).map(|node| {
+ use web_sys::Node;
+ use wasm_bindgen::JsCast;
+ match node.node_type() {
+ Node::ELEMENT_NODE => HtmlNode::Element(JsDomHtmlElement::new(node.dyn_into().expect("Element node instanceof HtmlElement"))),
+ _ => HtmlNode::Text(node.text_content().unwrap_or(String::new())) // Node::TEXT_NODE
+ }
+ });
+ self.index += 1;
+ ret
+ } */
+}
+
+impl DoubleEndedIterator for JsDomContentsIter {
+ fn next_back(&mut self) -> Option<Self::Item> { todo!() }
+}