//! The bundled page: its folder and its input. use std::path::{Path, PathBuf}; use anyhow::{Result, bail}; use ely_web_content::WebView; use gpui::App; use serde_json::{Value, json}; use crate::view::Heading; /// The app's Resources, else this build's `engines/content`. pub fn content() -> Result { let bundled = std::env::current_exe()? .parent() .and_then(|dir| Some(dir.parent()?.join("Resources/content"))); let built = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../engines/content/ely-mark"); for dir in bundled.into_iter().chain([built]) { if dir.join("index.html").is_file() { log::info!("mark: page from {}", dir.display()); return Ok(dir); } } bail!("ElyMark's page is not built; run scripts/build-mark-content.sh") } /// How the page shows a document (MARK-02). #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Mode { Visual, Source, Preview, } impl Mode { pub const ALL: [Mode; 3] = [Mode::Visual, Mode::Source, Mode::Preview]; pub fn name(self) -> &'static str { match self { Mode::Visual => "visual", Mode::Source => "source", Mode::Preview => "preview", } } pub fn parse(name: &str) -> Option { Mode::ALL.into_iter().find(|mode| mode.name() == name) } } /// Shows `markdown` as `mode` after load `load`, maybe placed. pub fn open( view: &WebView, markdown: &str, (mode, load, place): (Mode, u64, Option), folder: &Path, cx: &App, ) { let text = |key| ely_ui::i18n::text(cx, key, &[]).to_string(); let texts = json!({ "searchLanguage": text("mark.search_language"), "noResult": text("mark.no_result"), "copy": text("mark.copy"), "preview": text("mark.preview"), "loading": text("mark.loading"), "edit": text("mark.edit"), "hide": text("mark.hide"), }); // Relative links and images resolve in the document's folder. let base = format!("{}/", ely_web_content::file_url(folder)); view.run_js(&format!( "ely.open({}, '{}', {texts}, {load}, {}, {})", json!(markdown), mode.name(), json!(base), json!(place) )); } /// The disk's text in place; reports start a new generation. pub fn refresh(view: &WebView, markdown: &str, load: u64) { view.run_js(&format!("ely.refresh({}, {load})", json!(markdown))); } /// The same text in another mode; pending edits go first. pub fn switch(view: &WebView, mode: Mode) { view.run_js(&format!("ely.mode('{}')", mode.name())); } /// The folder for relative links and images. pub fn folder(view: &WebView, folder: &Path) { let base = format!("{}/", ely_web_content::file_url(folder)); view.run_js(&format!("ely.base({})", json!(base))); } /// Calls `ely.(argument)` in the page. pub fn call(view: &WebView, name: &str, argument: &serde_json::Value) { view.run_js(&format!("ely.{name}({argument})")); } /// Headings from the page; anything malformed is None. pub fn headings(items: &Value) -> Option> { let heading = |item: &Value| { let level = item["level"] .as_u64() .filter(|level| (1..=6).contains(level))?; let title = item["title"].as_str()?.to_owned(); Some(Heading { level: level as usize, title, }) }; items.as_array()?.iter().map(heading).collect() }