Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["crates/stet-core", "crates/stet-app", "crates/stet-substack"]
members = ["crates/stet-core", "crates/stet-app", "crates/stet-substack", "crates/stet-youtube"]

[workspace.package]
version = "0.3.0"
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,8 +491,11 @@ takes you to it. Nothing is ever sent to readers from here: publishing stays a b
you press on Substack.

1. **Signing in.** Substack has no publishing API, so Stet uses the one its own editor does, with your session cookie. The first time, the menu asks for it: in your browser, on substack.com, DevTools → Application → Cookies → `substack.sid`. It is kept in the macOS login keychain and nowhere else, never shown, and sent only to Substack over HTTPS (to a custom domain only once Substack says the domain is yours).
2. **What carries over.** Headings, emphasis, links, lists, quotes, code, pictures with captions, footnotes. A table goes up as preformatted text, and a local video as a link, because Substack's editor has neither; the menu says so before you send.
3. **It may break.** This is Substack's private interface, undocumented and theirs to change.
2. **What carries over.** Headings, emphasis, links, lists, quotes, code, pictures with captions, footnotes. A table goes up as preformatted text, because Substack's editor has none; the menu says so before you send.
3. **Videos.** A YouTube address becomes an embed. A local video is either uploaded to YouTube and embedded, or left as a link: the menu's **Videos** line picks which. Each video is uploaded once. Stet remembers the file by its contents (in `youtube-uploads.json` beside its settings), so publishing the same post again, or another post with the same video, reuses the upload, and only uploads again if that video was deleted from YouTube.
4. **It may break.** This is Substack's private interface, undocumented and theirs to change.

**Connecting YouTube.** `:youtube` (File → Connect YouTube…) signs in with your own Google OAuth client. Make one first: in Google Cloud, enable the *YouTube Data API v3*, then Credentials → Create OAuth client ID → *Desktop app*. Paste its ID and secret, and **Sign in with Google** finishes in your browser. The client and the sign-in are kept in the macOS login keychain. Once you're signed in, the same page shows the channel and who sees uploads (Unlisted by default, which is enough for an embed). Google's rules apply: a project that has not passed YouTube's API audit can only upload private videos, and while its consent screen is in *Testing* the sign-in lapses after seven days.

Pages like this one are how Stet asks for settings. They sit in the command menu's panel and work the way it does: ↑ and ↓ move, typing fills in the selected line, ← and → change a choice, Enter acts, Esc closes.

Expand Down
5 changes: 4 additions & 1 deletion crates/stet-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,18 @@ serde_json = "1"
open = "5"
ureq = { version = "3", optional = true }
stet-substack = { path = "../stet-substack", optional = true }
stet-youtube = { path = "../stet-youtube", optional = true }

[target.'cfg(target_os = "macos")'.dependencies]
muda = { version = "0.21", default-features = false }
objc2 = "0.5"
objc2-foundation = { version = "0.2", features = ["NSArray", "NSString", "NSURL"] }

[features]
default = ["remote-images", "substack"]
default = ["remote-images", "substack", "youtube"]
# Publish a document to Substack as a draft.
substack = ["dep:stet-substack"]
# Sign in to YouTube, and upload a published document's videos there.
youtube = ["dep:stet-youtube"]
# Fetch images referenced by http(s) URLs.
remote-images = ["dep:ureq"]
17 changes: 15 additions & 2 deletions crates/stet-app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,9 @@ impl Running {
editor,
view,
#[cfg(feature = "substack")]
publish: crate::publish::Publisher::new(wake),
publish: crate::publish::Publisher::new(wake.clone()),
#[cfg(feature = "youtube")]
youtube: crate::youtube::Connector::new(wake),
},
gpu: Gpu::new(device, queue, format),
surface,
Expand Down Expand Up @@ -439,6 +441,8 @@ impl Running {
self.gpu.free_images(&stale);
#[cfg(feature = "substack")]
self.session.publish.poll(&mut self.session.editor);
#[cfg(feature = "youtube")]
self.session.youtube.poll(&mut self.session.editor);
if !self.drawn {
crate::timing("first redraw requested");
}
Expand Down Expand Up @@ -543,7 +547,8 @@ impl Running {
prose_font: editor.config.prose_font.first().cloned(),
mono_font: editor.config.mono_font.first().cloned(),
window: self.window.fullscreen().is_none().then_some((size.width, size.height)),
substack: platform::State::load().substack,
// Kept by the sheets that set them, not by the window.
..platform::State::load()
}
.save();
}
Expand Down Expand Up @@ -1207,11 +1212,19 @@ fn headless_session(args: &Args) -> Session {
publish.offline = true;
publish
};
#[cfg(feature = "youtube")]
let youtube = {
let mut youtube = crate::youtube::Connector::new(no_wake());
youtube.offline = true;
youtube
};
Session {
editor,
view,
#[cfg(feature = "substack")]
publish,
#[cfg(feature = "youtube")]
youtube,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/stet-app/src/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ const MENUS: &[(&str, &[(&str, &str)])] = &[
("Save", "<D-s>"),
("Save As…", "<D-S-s>"),
("Publish to Substack…", ":publish"),
("Connect YouTube…", ":youtube"),
("", ""),
("Close Tab", "<D-w>"),
],
Expand Down
2 changes: 2 additions & 0 deletions crates/stet-app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ mod retouch;
mod session;
mod text;
mod view;
#[cfg(feature = "youtube")]
mod youtube;

use std::path::PathBuf;
use std::sync::Arc;
Expand Down
4 changes: 4 additions & 0 deletions crates/stet-app/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ pub struct State {
pub window: Option<(f32, f32)>,
/// The Substack publication last published to.
pub substack: Option<String>,
/// Who sees uploaded videos: `unlisted`, `public` or `private`.
pub youtube_privacy: Option<String>,
/// What publishing did with local videos last: `youtube` or `link`.
pub videos: Option<String>,
}

impl State {
Expand Down
Loading
Loading