chore: cleaned up some specs around extensions and small changes to get code in line

This commit is contained in:
2026-07-18 22:52:54 +02:00
parent 5c1b333cce
commit a2e2ab037d
21 changed files with 549 additions and 27 deletions

View File

@@ -15,6 +15,7 @@ const MAX_TITLE_LENGTH: usize = 200;
const MAX_URL_LENGTH: usize = 2_048;
const MAX_TOASTS_TOTAL: usize = 20;
const MAX_TOAST_LENGTH: usize = 300;
const BLOGMARK_SCHEME: &str = "ruds";
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BlogmarkCandidate {
@@ -36,7 +37,7 @@ pub struct BlogmarkImportResult {
pub fn parse_deep_link(raw: &str) -> EngineResult<BlogmarkCandidate> {
let parsed =
Url::parse(raw).map_err(|_| EngineError::Validation("invalid blogmark URL".into()))?;
if parsed.scheme() != "bds2" {
if parsed.scheme() != BLOGMARK_SCHEME {
return Err(EngineError::Validation(
"unsupported blogmark scheme".into(),
));
@@ -66,6 +67,14 @@ pub fn parse_deep_link(raw: &str) -> EngineResult<BlogmarkCandidate> {
})
}
pub fn bookmarklet(project_id: &str) -> String {
let project_id =
url::form_urlencoded::byte_serialize(project_id.as_bytes()).collect::<String>();
format!(
"javascript:(()=>{{const t=encodeURIComponent(document.title||'');const u=encodeURIComponent(location.href||'');location.href='ruds://new-post?title='+t+'&url='+u+'&project_id={project_id}';}})();"
)
}
pub fn receive_deep_link(
conn: &Connection,
data_dir: &Path,
@@ -281,7 +290,7 @@ mod tests {
#[test]
fn parses_and_hardens_blogmark_links() {
let candidate = parse_deep_link(
"bds2://new-post?title=%00Hello&url=https%3A%2F%2Fuser%3Apass%40example.com%2Fa%23frag&tags=one%2Ctwo",
"ruds://new-post?title=%00Hello&url=https%3A%2F%2Fuser%3Apass%40example.com%2Fa%23frag&tags=one%2Ctwo",
)
.unwrap();
assert_eq!(candidate.title, "Hello");
@@ -292,13 +301,22 @@ mod tests {
#[test]
fn rejects_other_schemes_and_actions() {
assert!(parse_deep_link("bds://new-post?title=x").is_err());
assert!(parse_deep_link("bds2://other?title=x").is_err());
assert!(parse_deep_link("bds2://new-post?title=x").is_err());
assert!(parse_deep_link("ruds://other?title=x").is_err());
}
#[test]
fn bookmarklet_targets_only_ruds_and_the_selected_project() {
let value = bookmarklet("project & seven");
assert!(value.contains("ruds://new-post?"));
assert!(value.contains("project_id=project+%26+seven"));
assert!(!value.contains("bds2://"));
}
#[test]
fn invalid_source_url_never_reaches_candidate() {
let candidate =
parse_deep_link("bds2://new-post?title=x&url=javascript%3Aalert%281%29").unwrap();
parse_deep_link("ruds://new-post?title=x&url=javascript%3Aalert%281%29").unwrap();
assert!(candidate.url.is_none());
}
@@ -328,7 +346,7 @@ mod tests {
db.conn(),
directory.path(),
&project.id,
"bds2://new-post?title=Example&url=https%3A%2F%2Fexample.com",
"ruds://new-post?title=Example&url=https%3A%2F%2Fexample.com",
)
.unwrap();

View File

@@ -36,9 +36,10 @@ winresource = "0.1"
[package.metadata.packager]
product-name = "Blogging Desktop Server"
identifier = "de.rfc1437.bds2"
identifier = "de.rfc1437.ruds"
description = "A desktop application for writing and publishing static blogs."
before-packaging-command = "cargo build --release -p bds-ui"
deep-link-protocols = [{ schemes = ["ruds"] }]
icons = [
"assets/app-icons/bds.icns",
"assets/app-icons/bds.ico",

View File

@@ -4427,6 +4427,16 @@ impl BdsApp {
SettingsMsg::BlogmarkCategoryChanged(s) => {
state.blogmark_category = s;
}
SettingsMsg::CopyBlogmarkBookmarklet => {
if let Some(project) = &self.active_project {
let bookmarklet = engine::blogmark::bookmarklet(&project.id);
self.notify(
ToastLevel::Success,
&t(self.ui_locale, "settings.blogmarkBookmarkletCopied"),
);
return iced::clipboard::write(bookmarklet);
}
}
SettingsMsg::SaveProject => {
if let (Some(db), Some(data_dir), Some(project)) =
(&self.db, &self.data_dir, self.active_project.as_mut())

View File

@@ -292,6 +292,7 @@ pub enum SettingsMsg {
MaxPostsPerPageChanged(String),
ImageImportConcurrencyChanged(String),
BlogmarkCategoryChanged(String),
CopyBlogmarkBookmarklet,
SaveProject,
// Editor
DefaultModeChanged(String),
@@ -543,6 +544,11 @@ fn section_project<'a>(state: &'a SettingsViewState, locale: UiLocale) -> Elemen
.find(|row| row.name == state.blogmark_category),
|row| Message::Settings(SettingsMsg::BlogmarkCategoryChanged(row.name)),
);
let copy_blogmark_bookmarklet =
button(text(t(locale, "settings.copyBlogmarkBookmarklet")).size(13))
.on_press(Message::Settings(SettingsMsg::CopyBlogmarkBookmarklet))
.style(inputs::secondary_button)
.padding([6, 16]);
let save = button(text(t(locale, "common.save")).size(13))
.on_press(Message::Settings(SettingsMsg::SaveProject))
.style(inputs::primary_button)
@@ -566,6 +572,7 @@ fn section_project<'a>(state: &'a SettingsViewState, locale: UiLocale) -> Elemen
max_posts,
image_import_concurrency,
blogmark_category,
copy_blogmark_bookmarklet,
save,
]
.spacing(8)

View File

@@ -22,6 +22,8 @@ fn desktop_packages_have_native_icons_and_cargo_commands() {
"assets/app-icons/bds.png",
"assets/app-icons/bds.ico",
"assets/app-icons/bds.icns",
"identifier = \"de.rfc1437.ruds\"",
"deep-link-protocols = [{ schemes = [\"ruds\"] }]",
"signing-identity = \"-\"",
] {
assert!(manifest.contains(required), "missing {required}");