Skip to content

Commit c2798a7

Browse files
authored
fix: Fix code block rendering in tw4 and put removed blocks back (#385)
* fix: Fix code block rendering in tw4 and put removed blocks back * Fix imports
1 parent 50144a5 commit c2798a7

File tree

4 files changed

+266
-11
lines changed

4 files changed

+266
-11
lines changed

src/app/docs/examples/gossip-chat/page.mdx

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,251 @@ In either case, we still print a ticket to the terminal.
528528

529529
The smallest change, but a very important one, is that we go from using the `subscribe` method to the `subscribe_and_join` method. The `subscribe` method would return a `Topic` immediately. The `subscribe_and_join` method takes the given topic, joins it, and waits for someone else to join the topic before returning.
530530

531+
```rust
532+
use std::{collections::HashMap, fmt, str::FromStr};
533+
534+
use anyhow::Result;
535+
use clap::Parser;
536+
use futures_lite::StreamExt;
537+
use iroh::{protocol::Router, Endpoint, NodeAddr, NodeId};
538+
use iroh_gossip::{
539+
net::{Event, Gossip, GossipEvent, GossipReceiver},
540+
proto::TopicId,
541+
};
542+
use serde::{Deserialize, Serialize};
543+
544+
/// Chat over iroh-gossip
545+
///
546+
/// This broadcasts unsigned messages over iroh-gossip.
547+
///
548+
/// By default a new node id is created when starting the example.
549+
///
550+
/// By default, we use the default n0 discovery services to dial by `NodeId`.
551+
#[derive(Parser, Debug)]
552+
struct Args {
553+
/// Set your nickname.
554+
#[clap(short, long)]
555+
name: Option<String>,
556+
/// Set the bind port for our socket. By default, a random port will be used.
557+
#[clap(short, long, default_value = "0")]
558+
bind_port: u16,
559+
#[clap(subcommand)]
560+
command: Command,
561+
}
562+
563+
#[derive(Parser, Debug)]
564+
enum Command {
565+
/// Open a chat room for a topic and print a ticket for others to join.
566+
Open,
567+
/// Join a chat room from a ticket.
568+
Join {
569+
/// The ticket, as base32 string.
570+
ticket: String,
571+
},
572+
}
573+
574+
#[tokio::main]
575+
async fn main() -> Result<()> {
576+
let args = Args::parse();
577+
578+
// parse the cli command
579+
let (topic, nodes) = match &args.command {
580+
Command::Open => {
581+
let topic = TopicId::from_bytes(rand::random());
582+
println!("> opening chat room for topic {topic}");
583+
(topic, vec![])
584+
}
585+
Command::Join { ticket } => {
586+
let Ticket { topic, nodes } = Ticket::from_str(ticket)?;
587+
println!("> joining chat room for topic {topic}");
588+
(topic, nodes)
589+
}
590+
};
591+
592+
let endpoint = Endpoint::builder().discovery_n0().bind().await?;
593+
594+
println!("> our node id: {}", endpoint.node_id());
595+
let gossip = Gossip::builder().spawn(endpoint.clone()).await?;
596+
597+
let router = Router::builder(endpoint.clone())
598+
.accept(iroh_gossip::ALPN, gossip.clone())
599+
.spawn();
600+
601+
// in our main file, after we create a topic `id`:
602+
// print a ticket that includes our own node id and endpoint addresses
603+
let ticket = {
604+
// Get our address information, includes our
605+
// `NodeId`, our `RelayUrl`, and any direct
606+
// addresses.
607+
let me = endpoint.node_addr().await?;
608+
let nodes = vec![me];
609+
Ticket { topic, nodes }
610+
};
611+
println!("> ticket to join us: {ticket}");
612+
613+
// join the gossip topic by connecting to known nodes, if any
614+
let node_ids = nodes.iter().map(|p| p.node_id).collect();
615+
if nodes.is_empty() {
616+
println!("> waiting for nodes to join us...");
617+
} else {
618+
println!("> trying to connect to {} nodes...", nodes.len());
619+
// add the peer addrs from the ticket to our endpoint's addressbook so that they can be dialed
620+
for node in nodes.into_iter() {
621+
endpoint.add_node_addr(node)?;
622+
}
623+
};
624+
let (sender, receiver) = gossip.subscribe_and_join(topic, node_ids).await?.split();
625+
println!("> connected!");
626+
627+
// broadcast our name, if set
628+
if let Some(name) = args.name {
629+
let message = Message::new(MessageBody::AboutMe {
630+
from: endpoint.node_id(),
631+
name,
632+
});
633+
sender.broadcast(message.to_vec().into()).await?;
634+
}
635+
636+
// subscribe and print loop
637+
tokio::spawn(subscribe_loop(receiver));
638+
639+
// spawn an input thread that reads stdin
640+
// create a multi-provider, single-consumer channel
641+
let (line_tx, mut line_rx) = tokio::sync::mpsc::channel(1);
642+
// and pass the `sender` portion to the `input_loop`
643+
std::thread::spawn(move || input_loop(line_tx));
644+
645+
// broadcast each line we type
646+
println!("> type a message and hit enter to broadcast...");
647+
// listen for lines that we have typed to be sent from `stdin`
648+
while let Some(text) = line_rx.recv().await {
649+
// create a message from the text
650+
let message = Message::new(MessageBody::Message {
651+
from: endpoint.node_id(),
652+
text: text.clone(),
653+
});
654+
// broadcast the encoded message
655+
sender.broadcast(message.to_vec().into()).await?;
656+
// print to ourselves the text that we sent
657+
println!("> sent: {text}");
658+
}
659+
router.shutdown().await?;
660+
661+
Ok(())
662+
}
663+
664+
#[derive(Debug, Serialize, Deserialize)]
665+
struct Message {
666+
body: MessageBody,
667+
nonce: [u8; 16],
668+
}
669+
670+
#[derive(Debug, Serialize, Deserialize)]
671+
enum MessageBody {
672+
AboutMe { from: NodeId, name: String },
673+
Message { from: NodeId, text: String },
674+
}
675+
676+
impl Message {
677+
fn from_bytes(bytes: &[u8]) -> Result<Self> {
678+
serde_json::from_slice(bytes).map_err(Into::into)
679+
}
680+
681+
pub fn new(body: MessageBody) -> Self {
682+
Self {
683+
body,
684+
nonce: rand::random(),
685+
}
686+
}
687+
688+
pub fn to_vec(&self) -> Vec<u8> {
689+
serde_json::to_vec(self).expect("serde_json::to_vec is infallible")
690+
}
691+
}
692+
693+
// Handle incoming events
694+
async fn subscribe_loop(mut receiver: GossipReceiver) -> Result<()> {
695+
// keep track of the mapping between `NodeId`s and names
696+
let mut names = HashMap::new();
697+
// iterate over all events
698+
while let Some(event) = receiver.try_next().await? {
699+
// if the Event is a `GossipEvent::Received`, let's deserialize the message:
700+
if let Event::Gossip(GossipEvent::Received(msg)) = event {
701+
// deserialize the message and match on the
702+
// message type:
703+
match Message::from_bytes(&msg.content)?.body {
704+
MessageBody::AboutMe { from, name } => {
705+
// if it's an `AboutMe` message
706+
// add an entry into the map
707+
// and print the name
708+
names.insert(from, name.clone());
709+
println!("> {} is now known as {}", from.fmt_short(), name);
710+
}
711+
MessageBody::Message { from, text } => {
712+
// if it's a `Message` message,
713+
// get the name from the map
714+
// and print the message
715+
let name = names
716+
.get(&from)
717+
.map_or_else(|| from.fmt_short(), String::to_string);
718+
println!("{}: {}", name, text);
719+
}
720+
}
721+
}
722+
}
723+
Ok(())
724+
}
725+
726+
fn input_loop(line_tx: tokio::sync::mpsc::Sender<String>) -> Result<()> {
727+
let mut buffer = String::new();
728+
let stdin = std::io::stdin(); // We get `Stdin` here.
729+
loop {
730+
stdin.read_line(&mut buffer)?;
731+
line_tx.blocking_send(buffer.clone())?;
732+
buffer.clear();
733+
}
734+
}
735+
736+
// add the `Ticket` code to the bottom of the main file
737+
#[derive(Debug, Serialize, Deserialize)]
738+
struct Ticket {
739+
topic: TopicId,
740+
nodes: Vec<NodeAddr>,
741+
}
742+
743+
impl Ticket {
744+
/// Deserialize from a slice of bytes to a Ticket.
745+
fn from_bytes(bytes: &[u8]) -> Result<Self> {
746+
serde_json::from_slice(bytes).map_err(Into::into)
747+
}
748+
749+
/// Serialize from a `Ticket` to a `Vec` of bytes.
750+
pub fn to_bytes(&self) -> Vec<u8> {
751+
serde_json::to_vec(self).expect("serde_json::to_vec is infallible")
752+
}
753+
}
754+
755+
// The `Display` trait allows us to use the `to_string`
756+
// method on `Ticket`.
757+
impl fmt::Display for Ticket {
758+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
759+
let mut text = data_encoding::BASE32_NOPAD.encode(&self.to_bytes()[..]);
760+
text.make_ascii_lowercase();
761+
write!(f, "{}", text)
762+
}
763+
}
764+
765+
// The `FromStr` trait allows us to turn a `str` into
766+
// a `Ticket`
767+
impl FromStr for Ticket {
768+
type Err = anyhow::Error;
769+
fn from_str(s: &str) -> Result<Self, Self::Err> {
770+
let bytes = data_encoding::BASE32_NOPAD.decode(s.to_ascii_uppercase().as_bytes())?;
771+
Self::from_bytes(&bytes)
772+
}
773+
}
774+
```
775+
531776
## Running the Application
532777

533778
```bash

src/app/docs/tour/2-relays/page.mdx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,17 @@ Keep in mind, connections are end-2-end encrypted, which means relays can’t re
1313
Coming back to our program, let’s add support for relays:
1414

1515

16-
```
16+
```rust
17+
#[tokio::main]
18+
async fn main() -> anyhow::Result<()> {
19+
let builder = iroh::Endpoint::builder()
20+
.relay_mode(iroh::RelayMode::Default);
21+
22+
let endpoint = builder.bind().await?;
23+
println!("node id: {:?}", endpoint.node_id());
24+
25+
Ok(())
26+
}
1727
```
1828

1929
Here we've set the relay mode to `Default`, but this hasn't actually changed anything. Our prior code had `relay_mode` implicitly set to `Default`, and this works because iroh comes with a set of free-to-use public relays by default, run by the number 0 team. You’re more than welcome to run your own relays, use the number 0 hosted solution [n0des.iroh.computer](https://n0des.iroh.computer), run your own, or, ideally all of the above! The code for relay servers is in the main iroh repo, and we release compiled binaries for relays on each release of iroh.

src/components/Code.jsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
useRef,
99
useState,
1010
} from 'react';
11-
import {Tab} from '@headlessui/react';
11+
import {Tab, TabList, TabPanel, TabPanels} from '@headlessui/react';
1212
import clsx from 'clsx';
1313
import {create} from 'zustand';
1414

@@ -150,7 +150,7 @@ function CodeGroupHeader({title, children, selectedIndex}) {
150150
</h3>
151151
)}
152152
{hasTabs && (
153-
<Tab.List className="-mb-px flex gap-4 text-xs font-medium">
153+
<TabList className="-mb-px flex gap-4 text-xs font-medium">
154154
{Children.map(children, (child, childIndex) => (
155155
<Tab
156156
className={clsx(
@@ -163,7 +163,7 @@ function CodeGroupHeader({title, children, selectedIndex}) {
163163
{getPanelTitle(child.props)}
164164
</Tab>
165165
))}
166-
</Tab.List>
166+
</TabList>
167167
)}
168168
</div>
169169
);
@@ -174,17 +174,17 @@ function CodeGroupPanels({children, ...props}) {
174174

175175
if (hasTabs) {
176176
return (
177-
<Tab.Panels>
177+
<TabPanels>
178178
{Children.map(children, (child) => (
179-
<Tab.Panel>
179+
<TabPanel>
180180
<CodePanel {...props}>{child}</CodePanel>
181-
</Tab.Panel>
181+
</TabPanel>
182182
))}
183-
</Tab.Panels>
183+
</TabPanels>
184184
);
185185
}
186186

187-
return <CodePanel {...props}>{children}</CodePanel>;
187+
return <>{Children.map(children, child => <CodePanel {...props}>{child}</CodePanel>)}</>
188188
}
189189

190190
function usePreventLayoutShift() {

typography.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,8 @@ module.exports = ({ theme }) => ({
222222
},
223223
figcaption: {
224224
color: 'var(--tw-prose-captions)',
225-
fontSize: theme('fontSize.xs')[0],
226-
...theme('fontSize.xs')[1],
225+
fontSize: theme('fontSize.sm')[0],
226+
...theme('fontSize.sm')[1],
227227
marginTop: theme('spacing.2'),
228228
},
229229

0 commit comments

Comments
 (0)