...
This commit is contained in:
18
packages/marts/Cargo.toml
Normal file
18
packages/marts/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "marts"
|
||||
description = "marts = mize_parts - parts collection of the Mize framework"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.59", optional = true }
|
||||
deno_core = { version = "0.385.0", optional = true }
|
||||
mize = { path = "../mize/", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["target-os"]
|
||||
target-os = [ "clap", "mize/target-os" ]
|
||||
target-wasm = [ "mize/target-wasm" ]
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
170
packages/marts/c2vi/obsidian-canvas-patch.ts
Normal file
170
packages/marts/c2vi/obsidian-canvas-patch.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
export function c2vi_obsidian_canvas_patch(mize: any) {
|
||||
const plugin = mize.get_part_native("obsidian");
|
||||
|
||||
window.pc = patchCanvas;
|
||||
window.upc = () => {
|
||||
unpatchCanvas(plugin, plugin.app.workspace.activeLeaf);
|
||||
};
|
||||
|
||||
console.log("hereeeeeeeeeeeeeeeee");
|
||||
// canvas patching
|
||||
plugin.registerEvent(
|
||||
plugin.app.workspace.on("file-open", (file: any) => {
|
||||
if (file?.extension === "canvas") {
|
||||
maybePathCanvas(plugin, plugin.app.workspace.activeLeaf);
|
||||
}
|
||||
}),
|
||||
);
|
||||
plugin.registerEvent(
|
||||
plugin.app.workspace.on("active-leaf-change", (file: any) => {
|
||||
if (file?.extension === "canvas") {
|
||||
maybePathCanvas(plugin, plugin.app.workspace.activeLeaf);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const maybePathCanvas = (plugin: PPCObsidianPlugin, leaf: any | null) => {
|
||||
console.log("maybePathCanvas called");
|
||||
const file = plugin.app.workspace.getActiveFile();
|
||||
|
||||
if (file?.extension !== "canvas") return;
|
||||
console.log("patching canvas.....");
|
||||
|
||||
const canvas = plugin.app.workspace.activeLeaf.view.canvas;
|
||||
if ("isPatched" in canvas) return;
|
||||
|
||||
patchCanvas(plugin, leaf, canvas);
|
||||
};
|
||||
|
||||
function patchCanvas(plugin: PPCObsidianPlugin, leaf: any, canvas: any) {
|
||||
canvas.isPatched = true;
|
||||
const canvasEl = canvas.canvasEl;
|
||||
const canvasWrapperEl = canvasEl.parentElement;
|
||||
const overlay = createDiv();
|
||||
overlay.setAttr("class", "c2vi-canvas-overlay");
|
||||
const canvas_card_menue = canvasWrapperEl.childNodes[1];
|
||||
canvasWrapperEl.insertBefore(overlay, canvas_card_menue);
|
||||
Object.assign(overlay.style, {
|
||||
position: "absolute",
|
||||
inset: "0",
|
||||
//zIndex: "1337",
|
||||
});
|
||||
overlay.addEventListener("contextmenu", (e) => {
|
||||
if (e.button === 2) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
});
|
||||
overlay.addEventListener("mouseout", (e) => {
|
||||
const menu = document.getElementsByClassName("menu")[0];
|
||||
if (!menu) return;
|
||||
menu.addEventListener("pointerup", (e) => {
|
||||
console.log("pointerup on menu", e);
|
||||
e.target.click();
|
||||
/*
|
||||
menu.dispatchEvent(
|
||||
new MouseEvent("mousedown", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
button: e.button,
|
||||
buttons: e.buttons,
|
||||
view: window,
|
||||
}),
|
||||
);
|
||||
*/
|
||||
});
|
||||
});
|
||||
|
||||
setupEventPropagation("pointerdown", PointerEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("pointerup", PointerEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("mousedown", MouseEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("mouseup", MouseEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("click", MouseEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("dragstart", DragEvent, overlay, canvasWrapperEl);
|
||||
setupEventPropagation("dragstop", DragEvent, overlay, canvasWrapperEl);
|
||||
|
||||
window.a = canvasWrapperEl;
|
||||
window.b = overlay;
|
||||
|
||||
const mouseEvents = [
|
||||
"mousedown",
|
||||
"mouseup",
|
||||
"click",
|
||||
"dblclick",
|
||||
//"mousemove",
|
||||
"mouseover",
|
||||
"mouseout",
|
||||
"wheel",
|
||||
];
|
||||
|
||||
mouseEvents.forEach((eventType) => {
|
||||
canvasWrapperEl.addEventListener(
|
||||
eventType,
|
||||
(event) => {
|
||||
console.log(`Mouse Event Triggered: ${event.type}`, event);
|
||||
},
|
||||
{ passive: true },
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function unpatchCanvas(plugin: PPCObsidianPlugin, leaf: any) {
|
||||
const canvas = plugin.app.workspace.activeLeaf.view.canvas;
|
||||
if (!canvas.isPatched) return;
|
||||
canvas.isPatched = false;
|
||||
const canvasEl = canvas.canvasEl;
|
||||
const canvasWrapperEl = canvasEl.parentElement;
|
||||
|
||||
const targets = canvasEl.querySelectorAll(":scope > .c2vi-canvas-overlay");
|
||||
|
||||
targets.forEach((child) => child.remove());
|
||||
}
|
||||
|
||||
function setupEventPropagation(type: string, evClass, overlay: any, target) {
|
||||
overlay.addEventListener(type, (originalEvent) => {
|
||||
originalEvent.stopPropagation();
|
||||
originalEvent.stopImmediatePropagation();
|
||||
originalEvent.preventDefault();
|
||||
const newEvent = new evClass(type, originalEvent);
|
||||
if (type == "pointerdown" && originalEvent.ctrlKey == true) {
|
||||
dispatchContextMenue(
|
||||
target,
|
||||
originalEvent.clientX,
|
||||
originalEvent.clientY,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (type === "pointerdown") {
|
||||
window.downEv = newEvent;
|
||||
}
|
||||
if (type === "pointerup") {
|
||||
window.upEv = newEvent;
|
||||
}
|
||||
newEvent.isTrusted = true;
|
||||
console.log("original Event of", type, originalEvent);
|
||||
console.log("new Event of", type, newEvent);
|
||||
target.dispatchEvent(newEvent);
|
||||
});
|
||||
}
|
||||
|
||||
function dispatchContextMenue(target, x, y) {
|
||||
const newEvent = new PointerEvent("contextmenu", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
view: window,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
pointerType: "mouse",
|
||||
button: 2,
|
||||
buttons: 2,
|
||||
pointerId: 1,
|
||||
isPrimary: true,
|
||||
});
|
||||
target.dispatchEvent(newEvent);
|
||||
}
|
||||
60
packages/marts/cli/mod.rs
Normal file
60
packages/marts/cli/mod.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use clap::{ArgMatches, Command};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use mize::{mize_part, Mize, MizePart, MizeResult};
|
||||
|
||||
#[mize_part("cli")]
|
||||
#[derive(Default)]
|
||||
pub struct CliPart {
|
||||
mize: Mize,
|
||||
cmd: Option<Command>,
|
||||
actions: HashMap<String, fn(&ArgMatches) -> MizeResult<()>>,
|
||||
}
|
||||
|
||||
pub fn cli(mize: &mut Mize) -> MizeResult<()> {
|
||||
let cli_part = CliPart {
|
||||
mize: mize.clone(),
|
||||
cmd: Some(Command::new("marts-cli")),
|
||||
actions: HashMap::new(),
|
||||
};
|
||||
|
||||
mize.register_part(Box::new(cli_part))
|
||||
}
|
||||
|
||||
impl MizePart for CliPart {
|
||||
fn init(&mut self, _mize: &mut Mize) -> MizeResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn run(&mut self, _mize: &mut Mize) -> MizeResult<()> {
|
||||
let matches = self.cmd.take().unwrap().get_matches();
|
||||
let sub_cmd = matches.subcommand_name().unwrap();
|
||||
let action = self.actions.get(sub_cmd).unwrap();
|
||||
action(matches.subcommand_matches(sub_cmd).unwrap())?;
|
||||
Ok(())
|
||||
}
|
||||
fn opts(&self, mize: &mut Mize) {
|
||||
mize.new_opt("cli.name");
|
||||
}
|
||||
}
|
||||
|
||||
impl CliPart {
|
||||
pub fn subcommand(
|
||||
&mut self,
|
||||
subcmd: Command,
|
||||
action: fn(sub_matches: &ArgMatches) -> MizeResult<()>,
|
||||
) -> &mut Command {
|
||||
let name = subcmd.get_name().to_string();
|
||||
let cmd = self.cmd.take().unwrap().subcommand(subcmd);
|
||||
self.actions.insert(name, action);
|
||||
self.cmd = Some(cmd);
|
||||
self.cmd.as_mut().unwrap()
|
||||
}
|
||||
pub fn with_cmd(
|
||||
&mut self,
|
||||
func: fn(mize: Mize, cmd: Command) -> MizeResult<Command>,
|
||||
) -> MizeResult<()> {
|
||||
let cmd = func(self.mize.clone(), self.cmd.take().unwrap())?;
|
||||
self.cmd = Some(cmd);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
7
packages/marts/deno.json
Normal file
7
packages/marts/deno.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "@ppc/marts",
|
||||
"imports": {},
|
||||
"exports": {
|
||||
".": "./index.ts"
|
||||
}
|
||||
}
|
||||
2
packages/marts/index.ts
Normal file
2
packages/marts/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { task_obsidian_otask_parse } from "./task/obsidian-otask-parse";
|
||||
export { c2vi_obsidian_canvas_patch } from "./c2vi/obsidian-canvas-patch";
|
||||
155
packages/marts/js/mod.rs
Normal file
155
packages/marts/js/mod.rs
Normal file
@@ -0,0 +1,155 @@
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::FsModuleLoader;
|
||||
use deno_core::{extension, op2, JsRuntime, ModuleSpecifier, PollEventLoopOptions, RuntimeOptions};
|
||||
use mize::async_trait;
|
||||
use mize::instance::MizePartCreate;
|
||||
use mize::{mize_part, Mize, MizePart, MizeResult};
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[mize_part("js")]
|
||||
pub struct JsPart {
|
||||
mize: Mize,
|
||||
js_runtime: JsRuntime,
|
||||
}
|
||||
|
||||
// Define your custom ops for the "mize" object inside deno
|
||||
#[op2(fast)]
|
||||
fn op_mize_get_part(#[string] name: &str) {
|
||||
println!("js wants the part {}", name);
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[string]
|
||||
fn op_mize_get_config(#[string] key: &str) -> String {
|
||||
format!("config_value_for_{}", key)
|
||||
}
|
||||
|
||||
// Create an extension with your custom ops
|
||||
extension!(mize_ext, ops = [op_mize_get_part, op_mize_get_config]);
|
||||
|
||||
impl MizePart for JsPart {
|
||||
fn init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
println!("js part init");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn js(mize: &mut Mize) -> Box<dyn MizePart> {
|
||||
let mut js_runtime = JsRuntime::new(RuntimeOptions {
|
||||
module_loader: Some(Rc::new(FsModuleLoader)),
|
||||
extensions: vec![mize_ext::init_ops()],
|
||||
..Default::default()
|
||||
});
|
||||
let bootstrap_code = r#"
|
||||
globalThis.mize = {
|
||||
get_part: (name) => Deno.core.ops.op_mize_get_part(name),
|
||||
get_config: (key) => Deno.core.ops.op_mize_get_config(key),
|
||||
version: "1.0.0",
|
||||
// Add more methods as needed
|
||||
};
|
||||
"#;
|
||||
|
||||
js_runtime.execute_script("[bootstrap]", bootstrap_code)?;
|
||||
JsPart { mize, js_runtime }
|
||||
}
|
||||
|
||||
impl JsPart {
|
||||
pub fn part_from_js_file(
|
||||
&mut self,
|
||||
name: &'static str,
|
||||
path: &'static str,
|
||||
) -> Box<dyn MizePart + Send + Sync> {
|
||||
Box::new(PartFromJsFileAdapter {
|
||||
mize: self.mize.clone(),
|
||||
name,
|
||||
js_file: path,
|
||||
})
|
||||
}
|
||||
pub fn runtime(&mut self) -> &mut JsRuntime {
|
||||
&mut self.js_runtime
|
||||
}
|
||||
}
|
||||
|
||||
#[mize_part]
|
||||
struct PartFromJsFileAdapter {
|
||||
mize: Mize,
|
||||
name: &'static str,
|
||||
js_file: &'static str,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl MizePart for PartFromJsFileAdapter {
|
||||
fn deps(&self) -> &'static [&'static str] {
|
||||
&["js"]
|
||||
}
|
||||
fn name(&self) -> &'static str {
|
||||
self.name
|
||||
}
|
||||
async fn async_init(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
let js = mize.get_part_native::<JsPart>("js")?;
|
||||
let path = self.js_file;
|
||||
let js_code = format!(
|
||||
r#"
|
||||
import {{ opts, deps, create }} from "{path}";
|
||||
|
||||
const isPromise = (value) => {
|
||||
return !!value && (typeof value === 'object' || typeof value === 'function') && typeof value.then === 'function';
|
||||
};
|
||||
|
||||
// Call the async function
|
||||
let create_result = create(mize);
|
||||
if (isPromise(create_result)) {
|
||||
create_result = await create_result;
|
||||
}
|
||||
const result = {
|
||||
opts: opts(mize),
|
||||
deps: deps(mize),
|
||||
create: create_result,
|
||||
};
|
||||
|
||||
result;
|
||||
"#
|
||||
);
|
||||
|
||||
// Execute the script
|
||||
let result = js.runtime().execute_script("[main]", js_code)?;
|
||||
|
||||
// Resolve the promise and run event loop
|
||||
let resolved_value = js.runtime().resolve_value(result).await?;
|
||||
|
||||
// Run the event loop to completion
|
||||
js.runtime()
|
||||
.run_event_loop(PollEventLoopOptions::default())
|
||||
.await?;
|
||||
|
||||
println!("Execution of part {} completed successfully", self.name());
|
||||
Ok(())
|
||||
}
|
||||
async fn async_run(&mut self, mize: &mut Mize) -> MizeResult<()> {
|
||||
let js = mize.get_part_native::<JsPart>("js")?;
|
||||
let path = self.js_file;
|
||||
let js_code = format!(
|
||||
r#"
|
||||
import {{ run }} from "{path}";
|
||||
|
||||
const result = run()
|
||||
|
||||
result;
|
||||
"#
|
||||
);
|
||||
// Execute the script
|
||||
let result = js.runtime().execute_script("[main]", js_code)?;
|
||||
|
||||
// Resolve the promise and run event loop
|
||||
let resolved_value = js.runtime.resolve_value(result).await?;
|
||||
|
||||
// Run the event loop to completion
|
||||
js.runtime()
|
||||
.run_event_loop(PollEventLoopOptions::default())
|
||||
.await?;
|
||||
|
||||
println!("Running of part {} completed successfully", self.name());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
6
packages/marts/lib.rs
Normal file
6
packages/marts/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
#[cfg(feature = "target-os")]
|
||||
mod cli;
|
||||
#[cfg(feature = "target-os")]
|
||||
pub use cli::*;
|
||||
|
||||
//mod js;
|
||||
27
packages/marts/package.json
Normal file
27
packages/marts/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "marts",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"packageManager": "pnpm@10.15.1",
|
||||
"dependencies": {
|
||||
"@flowershow/remark-wiki-link": "^3.4.0",
|
||||
"builtin-modules": "^5.0.0",
|
||||
"esbuild": "^0.27.3",
|
||||
"mdast": "^3.0.0",
|
||||
"obsidian": "^1.12.2",
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"typescript": "^5.9.3",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit": "^5.1.0"
|
||||
}
|
||||
}
|
||||
522
packages/marts/task/obsidian-otask-parse.ts
Normal file
522
packages/marts/task/obsidian-otask-parse.ts
Normal file
@@ -0,0 +1,522 @@
|
||||
import { id, i, init, InstaQLEntity } from "@instantdb/react";
|
||||
import remarkParse from "remark-parse";
|
||||
import remarkStringify from "remark-stringify";
|
||||
import { unified } from "unified";
|
||||
import { visit } from "unist-util-visit";
|
||||
import { Root, Content, Heading, List, ListItem, Text } from "mdast";
|
||||
import remarkFrontmatter from "remark-frontmatter";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import wikiLinkPlugin from "@flowershow/remark-wiki-link";
|
||||
|
||||
const OTASK_PROP_NAMES = ["short", "outcome", "priority"];
|
||||
const ACTION_PROP_NAMES = [];
|
||||
|
||||
const REMARK = unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkFrontmatter)
|
||||
.use(remarkGfm)
|
||||
.use(remarkStringify, { bullet: "-" });
|
||||
|
||||
export function task_obsidian_otask_parse(mize: any) {
|
||||
const obsidian_plugin = mize.get_part_native("obsidian");
|
||||
|
||||
obsidian_plugin.addCommand({
|
||||
id: "full-parse",
|
||||
name: "Parse all otask files into the db",
|
||||
callback: async () => {
|
||||
await full_parse();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function full_parse() {
|
||||
console.log("full parse.......................... yay");
|
||||
let otasks = [];
|
||||
OTASK_PARSE_QUEUE = [];
|
||||
OTASK_LIST = [];
|
||||
|
||||
const root_file = app.vault.getAbstractFileByPath("task/my-projects.md");
|
||||
|
||||
const content = await app.vault.read(root_file);
|
||||
const list = await getListUnderHeading(content, "root");
|
||||
|
||||
for (const entry of list) {
|
||||
queue_otasks_from_list_item(entry, []);
|
||||
}
|
||||
|
||||
// go through the queue of otaskParseTasks
|
||||
// using a queue instead of recursive calls, to make it more debuggable...
|
||||
do {
|
||||
const task = OTASK_PARSE_QUEUE.shift();
|
||||
dbg("task", task);
|
||||
if (task.file) {
|
||||
const otask = await otasks_from_file(
|
||||
task.file,
|
||||
task.otask_path,
|
||||
task.priority,
|
||||
);
|
||||
dbg("otask", otask);
|
||||
if (otask) {
|
||||
OTASK_LIST.push(otask);
|
||||
}
|
||||
} else {
|
||||
const otask = await otasks_from_list_item(
|
||||
task.listItem,
|
||||
task.otask_path,
|
||||
task.priority,
|
||||
);
|
||||
dbg("otask", otask);
|
||||
if (otask) {
|
||||
OTASK_LIST.push(otask);
|
||||
}
|
||||
}
|
||||
} while (OTASK_PARSE_QUEUE.length > 0);
|
||||
|
||||
console.log("otasks::", OTASK_LIST);
|
||||
|
||||
render_sub0_file(OTASK_LIST);
|
||||
}
|
||||
|
||||
let OTASK_PARSE_QUEUE = [];
|
||||
let OTASK_LIST = [];
|
||||
let DBG = false;
|
||||
|
||||
function dbg() {
|
||||
if (DBG) {
|
||||
console.log(arguments);
|
||||
}
|
||||
}
|
||||
|
||||
function queue_otasks_from_list_item(listItem, otask_path, priority = 0) {
|
||||
OTASK_PARSE_QUEUE.push({
|
||||
listItem,
|
||||
otask_path,
|
||||
priority,
|
||||
});
|
||||
}
|
||||
|
||||
function queue_otasks_from_file(file, otask_path, priority = 0) {
|
||||
OTASK_PARSE_QUEUE.push({
|
||||
file,
|
||||
otask_path,
|
||||
priority,
|
||||
});
|
||||
}
|
||||
|
||||
// parsing helper functions
|
||||
|
||||
async function render_sub0_file(otasks) {
|
||||
const sub0_file = app.vault.getAbstractFileByPath("private/sub0-tasks.md");
|
||||
if (!(sub0_file instanceof TFile)) {
|
||||
console.error(
|
||||
"SystemC2: sub0_file not found:",
|
||||
"private/sub0-tasks.md",
|
||||
sub0_file,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
//render sub0
|
||||
const sub0_otasks = otasks.filter(
|
||||
(otask) => otask.priority == 0 && !otask.is_file,
|
||||
);
|
||||
console.log("sub0 otasks...", sub0_otasks);
|
||||
|
||||
let rendered_otasks_string = "";
|
||||
|
||||
for (const otask of sub0_otasks) {
|
||||
const path_part = otask.path.length > 0 ? otask.path.join(": ") + ": " : "";
|
||||
|
||||
let content_part = otask.content ? otask.content + "\n" : "";
|
||||
content_part = content_part
|
||||
.split("\n")
|
||||
.map((line) => "\t" + line)
|
||||
.join("\n"); // add a \t in front of every otask.content line
|
||||
|
||||
rendered_otasks_string += `- ${path_part}${otask.longName}\n${content_part}\n`;
|
||||
}
|
||||
//console.log("newContent...", rendered_otasks_string)
|
||||
|
||||
await replaceHeading(sub0_file, "sub0", rendered_otasks_string);
|
||||
|
||||
//render sub-1
|
||||
}
|
||||
|
||||
async function replaceHeading(
|
||||
file: TFile,
|
||||
heading: string,
|
||||
newContent: string,
|
||||
) {
|
||||
const data = await this.app.vault.read(file);
|
||||
|
||||
// Parse the Markdown into an AST
|
||||
const tree = REMARK.parse(data) as Root;
|
||||
|
||||
// Split new content into AST nodes
|
||||
const newContentAst = REMARK.parse(newContent) as Root;
|
||||
|
||||
const children = tree.children;
|
||||
const newChildren: Content[] = [];
|
||||
let insideTarget = false;
|
||||
let targetLevel = 0;
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const node = children[i];
|
||||
|
||||
if (node.type === "heading") {
|
||||
const headingNode = node as Heading;
|
||||
const text = headingNode.children
|
||||
.filter((c) => c.type === "text")
|
||||
.map((c: any) => c.value)
|
||||
.join("");
|
||||
|
||||
// If we encounter our target heading
|
||||
if (text.trim() === heading) {
|
||||
insideTarget = true;
|
||||
targetLevel = headingNode.depth;
|
||||
newChildren.push(node); // Keep the heading itself
|
||||
// Insert new content right after
|
||||
newChildren.push(...newContentAst.children);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we’re currently inside target section and see a heading of same or higher level
|
||||
if (insideTarget && headingNode.depth <= targetLevel) {
|
||||
insideTarget = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we’re not inside the target section, keep the node
|
||||
if (!insideTarget) {
|
||||
newChildren.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
const newTree: Root = { ...tree, children: newChildren };
|
||||
|
||||
// Convert AST back to Markdown
|
||||
const newMarkdown = await REMARK.stringify(newTree);
|
||||
|
||||
await this.app.vault.modify(file, newMarkdown);
|
||||
}
|
||||
|
||||
async function otasks_from_list_item(listItem, otask_path, priority = 0) {
|
||||
// the main otask this listItem is about
|
||||
let otask = {
|
||||
description: [],
|
||||
actions: [],
|
||||
path: [],
|
||||
priority,
|
||||
};
|
||||
dbg("SystemC2: otasks_from_list_item", listItem, otask_path);
|
||||
|
||||
if (!listItem.children[0].type === "paragraph") {
|
||||
dbg(
|
||||
"listItem[0] of a otask item is not a paragraph... the otask:",
|
||||
listItem,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
let longName = nodeToString(listItem.children[0]);
|
||||
|
||||
dbg("longName:", longName);
|
||||
|
||||
// check for [[anotherotask]], call otasks_from_file, return those
|
||||
const match = longName.match(/\[\[([^\]]+)\]\]/);
|
||||
if (match) {
|
||||
const file = app.metadataCache.getFirstLinkpathDest(match[1], "");
|
||||
if (file) {
|
||||
const metadata = app.metadataCache.getFileCache(file);
|
||||
|
||||
if (metadata?.frontmatter?.tags?.includes("t/otask")) {
|
||||
queue_otasks_from_file(file, otask_path);
|
||||
return null;
|
||||
} else {
|
||||
dbg(
|
||||
"SystemC2: listItem with Link to '",
|
||||
match[1],
|
||||
"', which is not an otask",
|
||||
longName,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check for [x] and remove from name
|
||||
if (longName.startsWith("[ ") || longName.startsWith("[x")) {
|
||||
longName = longName.slice(4);
|
||||
}
|
||||
|
||||
//check for "ot:" and remove from name
|
||||
if (longName.startsWith("ot:")) {
|
||||
longName = longName.slice(4);
|
||||
}
|
||||
|
||||
otask.longName = longName;
|
||||
otask.short = longName;
|
||||
otask.path = otask_path;
|
||||
let new_otask_path = [...otask_path, otask.short];
|
||||
|
||||
// go through sub list items
|
||||
// - if it's a prop for the otask... handle that
|
||||
// - if it's an "ac:" handle that
|
||||
// - if it's "ot: " handle as another otask -> recursive call
|
||||
// - if it's "[ ]" handle as another otask -> recursive call
|
||||
// - if it's a link to a otask file... handle as another otask -> call otasks_from_file()
|
||||
// - else add it as description item list to otask
|
||||
if (!listItem.children || listItem.children.length < 2) {
|
||||
return otask;
|
||||
}
|
||||
|
||||
// add content as text to otask
|
||||
if (otask.longName == "hosting updates") {
|
||||
console.log("listItem... ", listItem.children[1]);
|
||||
let hii = REMARK.stringify(listItem.children[1]);
|
||||
console.log("text...", hii);
|
||||
}
|
||||
let content_text = REMARK.stringify(listItem.children[1]);
|
||||
otask.content = content_text;
|
||||
|
||||
for (const subListItem of listItem.children[1].children) {
|
||||
let text = nodeToString(subListItem.children[0]);
|
||||
|
||||
// check prop
|
||||
if (check_otask_prop_from_list_item(subListItem, otask)) {
|
||||
new_otask_path = [...otask_path, otask.short]; // have to redo it here, in case short changed from aprop
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
|
||||
// remove "[ ]"
|
||||
if (text.startsWith("[ ") || text.startsWith("[x")) {
|
||||
text = text.slice(4);
|
||||
}
|
||||
|
||||
// check ac
|
||||
if (text.startsWith("ac:")) {
|
||||
otask.actions = [
|
||||
...otask.actions,
|
||||
action_from_list_item(subListItem, new_otask_path, otask.priority),
|
||||
];
|
||||
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
|
||||
// check ot
|
||||
if (text.startsWith("ot:")) {
|
||||
queue_otasks_from_list_item(subListItem, new_otask_path, otask.priority);
|
||||
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
|
||||
// check [ ]
|
||||
if (text.startsWith("[ ] ") || text.startsWith("[x] ")) {
|
||||
queue_otasks_from_list_item(subListItem, new_otask_path, otask.priority);
|
||||
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
|
||||
// check link to otask file
|
||||
const match = text.match(/\[\[([^\]]+)\]\]/);
|
||||
if (match) {
|
||||
const file = app.metadataCache.getFirstLinkpathDest(match[1], "");
|
||||
if (file) {
|
||||
const metadata = app.metadataCache.getFileCache(file);
|
||||
|
||||
if (metadata?.frontmatter?.tags?.includes("t/otask")) {
|
||||
queue_otasks_from_file(file, new_otask_path, otask.priority);
|
||||
continue;
|
||||
} else {
|
||||
dbg(
|
||||
"SystemC2: listItem with Link to '",
|
||||
match[1],
|
||||
"', which is not an otask",
|
||||
longName,
|
||||
);
|
||||
}
|
||||
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
}
|
||||
|
||||
// add as description
|
||||
const desc = REMARK.stringify(subListItem);
|
||||
otask.description.push(desc);
|
||||
}
|
||||
|
||||
return otask;
|
||||
}
|
||||
|
||||
async function otasks_from_file(file: TFile, otask_path, priority = 0) {
|
||||
dbg("SystemC2: otasks_from_file", file, otask_path);
|
||||
|
||||
// the main otask this file is about
|
||||
let otask = {
|
||||
description: [],
|
||||
actions: [],
|
||||
path: [],
|
||||
priority,
|
||||
is_file: true,
|
||||
};
|
||||
|
||||
otask.longName = file.basename;
|
||||
otask.short = otask.longName;
|
||||
otask.path = otask_path;
|
||||
let new_otask_path = [...otask_path, otask.short];
|
||||
|
||||
const content = await app.vault.read(file);
|
||||
|
||||
// parse listItems at the top... for props
|
||||
const topListItems = getListItemsBeforeFirstHeading(content);
|
||||
for (const topListItem of topListItems) {
|
||||
const text = nodeToString(topListItem);
|
||||
|
||||
// check for prop
|
||||
if (check_otask_prop_from_list_item(topListItem, otask)) {
|
||||
new_otask_path = [...otask_path, otask.short];
|
||||
continue; // don't do any other processing on this sub-listItem
|
||||
}
|
||||
}
|
||||
|
||||
// parse all the sub sections
|
||||
const SUB_COUNT = 10;
|
||||
for (let sub_number = 0; sub_number < SUB_COUNT; sub_number++) {
|
||||
let listItems = await getListUnderHeading(content, "sub" + sub_number);
|
||||
|
||||
// "sub" without a number should have priority 5
|
||||
if (sub_number == 5) {
|
||||
listItems = listItems.concat(await getListUnderHeading(content, "sub"));
|
||||
}
|
||||
|
||||
//dbg(`sub section ${sub_number} has items:`, listItems)
|
||||
|
||||
for (const listItem of listItems) {
|
||||
// check if listItem is an action...
|
||||
let text = nodeToString(listItem.children[0]);
|
||||
if (
|
||||
text.startsWith("ac:") ||
|
||||
text.startsWith("[ ] ac:") ||
|
||||
text.startsWith("[x] ac:")
|
||||
) {
|
||||
const action = action_from_list_item(listItem, otask_path);
|
||||
otask.actions.push(action);
|
||||
}
|
||||
|
||||
queue_otasks_from_list_item(
|
||||
listItem,
|
||||
new_otask_path,
|
||||
otask.priority + sub_number,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return otask;
|
||||
}
|
||||
|
||||
function action_from_list_item(listItem, otask_path) {
|
||||
dbg("SystemC2: action_from_list_item", listItem, otask_path);
|
||||
let action = {};
|
||||
let text = nodeToString(listItem.children[0]).slice(4);
|
||||
|
||||
if (text.startsWith("[")) {
|
||||
text = text.slice(4);
|
||||
}
|
||||
|
||||
action.name = text;
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function check_otask_prop_from_list_item(listItem, otask) {
|
||||
let text = nodeToString(listItem.children[0]);
|
||||
|
||||
for (const prop_name of OTASK_PROP_NAMES) {
|
||||
if (text.startsWith(prop_name + ":")) {
|
||||
const value = text.slice(prop_name.length + 2);
|
||||
|
||||
if (prop_name == "priority") {
|
||||
// parse priority as int
|
||||
otask[prop_name] = parseInt(value);
|
||||
} else if (prop_name == "outcome") {
|
||||
// add sub items to it
|
||||
let fullText = REMARK.stringify(listItem).slice(prop_name.length + 4);
|
||||
otask[prop_name] = fullText;
|
||||
} else {
|
||||
otask[prop_name] = value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract plain text from a node
|
||||
function nodeToString(node: any): string {
|
||||
let result = "";
|
||||
visit(node, "text", (textNode: Text) => {
|
||||
result += textNode.value;
|
||||
});
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
async function getListUnderHeading(
|
||||
markdown: string,
|
||||
headingText: string,
|
||||
): string[] {
|
||||
const tree = REMARK.parse(markdown);
|
||||
//dbg("tree", tree)
|
||||
|
||||
let capture = false;
|
||||
let items: string[] = [];
|
||||
|
||||
for (let i = 0; i < tree.children.length; i++) {
|
||||
const node: Content = tree.children[i];
|
||||
|
||||
// Detect the target heading
|
||||
if (
|
||||
node.type === "heading" &&
|
||||
nodeToString(node).toLowerCase() === headingText.toLowerCase()
|
||||
) {
|
||||
capture = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Stop if another heading of the same or higher level comes
|
||||
if (capture && node.type === "heading") {
|
||||
break;
|
||||
}
|
||||
|
||||
// Collect list items if capturing
|
||||
if (capture && node.type === "list") {
|
||||
for (const li of (node as List).children) {
|
||||
if (li.type === "listItem") {
|
||||
items.push(li);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function getListItemsBeforeFirstHeading(markdown: string) {
|
||||
const tree = REMARK.parse(markdown);
|
||||
|
||||
let items = [];
|
||||
|
||||
for (const node of tree.children) {
|
||||
if (node.type === "heading") {
|
||||
// stop at first heading
|
||||
break;
|
||||
}
|
||||
if (node.type === "list") {
|
||||
for (const li of (node as List).children) {
|
||||
if (li.type === "listItem") {
|
||||
items.push(li);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
Reference in New Issue
Block a user