A document can forge both a console message and its source (KaTeX's \errmessage, data: scripts, sourceURL, encoded paths), so the display handler logs a line number and nothing the page wrote. The page's own diagnostics reach the app over elyQuery: a missed reveal sends its line; the visual failure and diagram lines already did. A test calls the real handler and checks the captured log. Round 5 found only the missing test; it is added and mutation-checked, with no sixth review under the five-round cap. M-15 is new: the print page runs a document's scripts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
115 lines
3.4 KiB
Rust
115 lines
3.4 KiB
Rust
//! 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<PathBuf> {
|
|
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> {
|
|
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<crate::view::Place>),
|
|
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.<name>(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<Vec<Heading>> {
|
|
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()
|
|
}
|