feat: completed base feature set for the app
This commit is contained in:
@@ -52,7 +52,44 @@ fn render_macro(invocation: &str, context: &MacroRenderContext) -> Option<String
|
||||
"vimeo" => Some(render_vimeo(&args)),
|
||||
"photo_archive" => Some(render_photo_archive(&args, context)),
|
||||
"tag_cloud" => Some(render_tag_cloud(&args, context)),
|
||||
_ => None,
|
||||
_ => render_script_macro(name, &args, context),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_script_macro(
|
||||
name: &str,
|
||||
args: &HashMap<String, JsonValue>,
|
||||
context: &MacroRenderContext,
|
||||
) -> Option<String> {
|
||||
let definition = context.roots.get("macro_scripts")?.get(name)?;
|
||||
let source = definition.get("source")?.as_str()?;
|
||||
let entrypoint = definition
|
||||
.get("entrypoint")
|
||||
.and_then(JsonValue::as_str)
|
||||
.unwrap_or("render");
|
||||
let env = serde_json::json!({
|
||||
"isPreview": context.roots.get("is_preview").and_then(JsonValue::as_bool).unwrap_or(false),
|
||||
"mainLanguage": context.roots.get("main_language").and_then(JsonValue::as_str).unwrap_or("en"),
|
||||
"language": context.roots.get("language").and_then(JsonValue::as_str).unwrap_or("en"),
|
||||
"languagePrefix": context.roots.get("language_prefix").and_then(JsonValue::as_str).unwrap_or(""),
|
||||
"hook": "markdown",
|
||||
"source": { "kind": if context.post_id.is_some() { "post" } else { "page" } },
|
||||
"translations": context.roots.get("translations").cloned().unwrap_or(JsonValue::Array(Vec::new())),
|
||||
});
|
||||
let params = serde_json::to_value(args).ok()?;
|
||||
match crate::scripting::execute_many(
|
||||
source,
|
||||
entrypoint,
|
||||
&[params, env],
|
||||
crate::scripting::ExecutionKind::Macro,
|
||||
&crate::scripting::ExecutionControl::default(),
|
||||
) {
|
||||
Ok(result) => Some(match result.value {
|
||||
JsonValue::Null => String::new(),
|
||||
JsonValue::String(value) => value,
|
||||
value => value.to_string(),
|
||||
}),
|
||||
Err(_) => Some(String::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,4 +536,27 @@ mod tests {
|
||||
markdown
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_macro_receives_named_params_and_environment() {
|
||||
let mut roots = serde_json::Map::new();
|
||||
roots.insert(
|
||||
"macro_scripts".into(),
|
||||
serde_json::json!({
|
||||
"notice": {
|
||||
"source": "function render(params, env) return '<aside>' .. params.text .. ':' .. env.language .. '</aside>' end",
|
||||
"entrypoint": "render"
|
||||
}
|
||||
}),
|
||||
);
|
||||
roots.insert("language".into(), serde_json::Value::String("de".into()));
|
||||
let rendered = expand_builtin_macros(
|
||||
"[[notice text=Hallo]]",
|
||||
&MacroRenderContext {
|
||||
roots,
|
||||
post_id: Some("post-1".into()),
|
||||
},
|
||||
);
|
||||
assert_eq!(rendered, "<aside>Hallo:de</aside>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,19 @@ impl Filter for MarkdownFilter {
|
||||
fn collect_macro_roots(runtime: &dyn Runtime) -> JsonMap<String, JsonValue> {
|
||||
let mut roots = JsonMap::new();
|
||||
|
||||
for key in ["post", "post_tags", "tag_color_by_name", "project", "Tags"] {
|
||||
for key in [
|
||||
"post",
|
||||
"post_tags",
|
||||
"tag_color_by_name",
|
||||
"project",
|
||||
"Tags",
|
||||
"macro_scripts",
|
||||
"language",
|
||||
"language_prefix",
|
||||
"main_language",
|
||||
"is_preview",
|
||||
"translations",
|
||||
] {
|
||||
if let Some(value) = runtime.try_get(&[ScalarCow::new(key)]) {
|
||||
roots.insert(key.to_string(), liquid_value_to_json(value.as_view()));
|
||||
}
|
||||
|
||||
@@ -492,6 +492,7 @@ mod tests {
|
||||
main_language: Some("en".into()),
|
||||
default_author: None,
|
||||
max_posts_per_page: 50,
|
||||
image_import_concurrency: 4,
|
||||
blogmark_category: None,
|
||||
pico_theme: None,
|
||||
semantic_similarity_enabled: false,
|
||||
|
||||
@@ -11,13 +11,14 @@ use serde_json::{Value, json};
|
||||
use crate::db::queries;
|
||||
use crate::engine::menu::{self, MenuItemKind};
|
||||
use crate::model::{
|
||||
CategorySettings, Media, Post, ProjectMetadata, Tag, Template, TemplateKind, TemplateStatus,
|
||||
CategorySettings, Media, Post, ProjectMetadata, ScriptKind, Tag, Template, TemplateKind,
|
||||
TemplateStatus,
|
||||
};
|
||||
use crate::render::{
|
||||
RenderCategorySettings, RenderTemplateLookup, build_canonical_post_path,
|
||||
render_liquid_template, resolve_post_template,
|
||||
};
|
||||
use crate::util::frontmatter::{read_template_file, read_translation_file};
|
||||
use crate::util::frontmatter::{read_script_file, read_template_file, read_translation_file};
|
||||
use crate::util::slugify;
|
||||
|
||||
const STARTER_SINGLE_POST_TEMPLATE: &str =
|
||||
@@ -69,6 +70,7 @@ struct TemplateBundle {
|
||||
default_list_template: String,
|
||||
not_found_template: String,
|
||||
partials: HashMap<String, String>,
|
||||
macro_scripts: HashMap<String, Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -97,6 +99,24 @@ pub fn build_site_render_artifacts(
|
||||
project_id: &str,
|
||||
metadata: &ProjectMetadata,
|
||||
published_posts: &[(Post, String)],
|
||||
) -> Result<SiteRenderArtifacts, Box<dyn Error + Send + Sync>> {
|
||||
build_site_render_artifacts_with_mode(
|
||||
conn,
|
||||
data_dir,
|
||||
project_id,
|
||||
metadata,
|
||||
published_posts,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_site_render_artifacts_with_mode(
|
||||
conn: &Connection,
|
||||
data_dir: &Path,
|
||||
project_id: &str,
|
||||
metadata: &ProjectMetadata,
|
||||
published_posts: &[(Post, String)],
|
||||
is_preview: bool,
|
||||
) -> Result<SiteRenderArtifacts, Box<dyn Error + Send + Sync>> {
|
||||
let bundle = load_template_bundle(conn, data_dir, project_id)?;
|
||||
let main_language = main_language(metadata).to_string();
|
||||
@@ -136,6 +156,7 @@ pub fn build_site_render_artifacts(
|
||||
&menu_items,
|
||||
&post_data_json_by_id,
|
||||
&bundle,
|
||||
is_preview,
|
||||
)
|
||||
.map(|html| SitePage {
|
||||
language: language.clone(),
|
||||
@@ -173,6 +194,7 @@ pub fn build_site_render_artifacts(
|
||||
&menu_items,
|
||||
&post_data_json_by_id,
|
||||
&bundle,
|
||||
is_preview,
|
||||
)?;
|
||||
let canonical_path = build_canonical_post_path(&record.post, &language, &main_language);
|
||||
let relative_path = format!("{}/index.html", canonical_path.trim_start_matches('/'));
|
||||
@@ -202,8 +224,14 @@ pub fn build_preview_response(
|
||||
published_posts: &[(Post, String)],
|
||||
requested_path: &str,
|
||||
) -> Result<PreviewRenderResult, Box<dyn Error + Send + Sync>> {
|
||||
let artifacts =
|
||||
build_site_render_artifacts(conn, data_dir, project_id, metadata, published_posts)?;
|
||||
let artifacts = build_site_render_artifacts_with_mode(
|
||||
conn,
|
||||
data_dir,
|
||||
project_id,
|
||||
metadata,
|
||||
published_posts,
|
||||
true,
|
||||
)?;
|
||||
let normalized = normalize_request_path(requested_path);
|
||||
if let Some(page) = artifacts
|
||||
.pages
|
||||
@@ -239,6 +267,30 @@ fn load_template_bundle(
|
||||
let mut partials = starter_partials();
|
||||
let mut default_list_template = STARTER_POST_LIST_TEMPLATE.to_string();
|
||||
let mut not_found_template = STARTER_NOT_FOUND_TEMPLATE.to_string();
|
||||
let mut macro_scripts = HashMap::new();
|
||||
|
||||
for script in queries::script::list_scripts_by_project(conn, project_id).unwrap_or_default() {
|
||||
if script.kind != ScriptKind::Macro
|
||||
|| !script.enabled
|
||||
|| script.entrypoint.trim().is_empty()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let source = if let Some(content) = script.content {
|
||||
content
|
||||
} else if script.file_path.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
fs::read_to_string(data_dir.join(&script.file_path))
|
||||
.ok()
|
||||
.and_then(|raw| read_script_file(&raw).ok().map(|(_, body)| body))
|
||||
.unwrap_or_default()
|
||||
};
|
||||
macro_scripts.insert(
|
||||
script.slug,
|
||||
json!({ "source": source, "entrypoint": script.entrypoint }),
|
||||
);
|
||||
}
|
||||
|
||||
for template in templates {
|
||||
if !template.enabled {
|
||||
@@ -315,6 +367,7 @@ fn load_template_bundle(
|
||||
default_list_template,
|
||||
not_found_template,
|
||||
partials,
|
||||
macro_scripts,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -557,6 +610,7 @@ fn render_list_route(
|
||||
menu_items: &[Value],
|
||||
post_data_json_by_id: &HashMap<String, Value>,
|
||||
bundle: &TemplateBundle,
|
||||
is_preview: bool,
|
||||
) -> Result<String, Box<dyn Error + Send + Sync>> {
|
||||
let main_language = main_language(metadata);
|
||||
let canonical_map = canonical_post_path_by_slug(posts, language, main_language);
|
||||
@@ -569,6 +623,9 @@ fn render_list_route(
|
||||
let context = json!({
|
||||
"language": language,
|
||||
"language_prefix": language_prefix(language, main_language),
|
||||
"main_language": main_language,
|
||||
"is_preview": is_preview,
|
||||
"macro_scripts": bundle.macro_scripts,
|
||||
"html_theme_attribute": serde_json::Value::Null,
|
||||
"page_title": route.page_title,
|
||||
"pico_stylesheet_href": pico_stylesheet_href(metadata),
|
||||
@@ -626,6 +683,7 @@ fn render_post_route(
|
||||
menu_items: &[Value],
|
||||
post_data_json_by_id: &HashMap<String, Value>,
|
||||
bundle: &TemplateBundle,
|
||||
is_preview: bool,
|
||||
) -> Result<String, Box<dyn Error + Send + Sync>> {
|
||||
let render_categories = category_settings
|
||||
.iter()
|
||||
@@ -683,6 +741,9 @@ fn render_post_route(
|
||||
let context = json!({
|
||||
"language": language,
|
||||
"language_prefix": language_prefix(language, main_language),
|
||||
"main_language": main_language,
|
||||
"is_preview": is_preview,
|
||||
"macro_scripts": bundle.macro_scripts,
|
||||
"page_title": record.post.title,
|
||||
"pico_stylesheet_href": pico_stylesheet_href(metadata),
|
||||
"html_theme_attribute": serde_json::Value::Null,
|
||||
|
||||
Reference in New Issue
Block a user