chore: cleaned up some specs around extensions and small changes to get code in line
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}");
|
||||
|
||||
Reference in New Issue
Block a user