Finish bundled-template migration; simplify frontmatter; de-duplicate toolbar/menu
Completes the move off the external cookiecutter tool: drops src/cookiecutter.rs and the cookiecutter_bin/Template-path setting now that scaffolding always runs through the built-in MiniJinja engine, and rewrites default_project_template() to walk up from the executable without panicking when the bundled template can't be found. Rounds out the snowflake template with the remaining 06-Revisions, 07-Final, 08-Covers and 09-Archive folders. Simplifies the scene/chapter YAML frontmatter (drops type/word_count_min/ word_count_max in favor of status + a flat characters list) and updates the new-file template, header parsing, outline, file list and Story Bible editor to match. Separately, de-duplicates the toolbar and menu bar: settings that only existed as always-visible toolbar controls (draft marker, zero-pad, spell-check toggle, dictionary picker) move into their Settings windows, toolbar buttons that exactly duplicated a menu item are dropped, and toolbar-only actions with no menu path (open project folder, git sync/init) gain one under File. Also fixes two ambiguous menu labels: the Story Bible editor now reads "Story Bible editor…" next to "Story Bible…" in Tools instead of sharing a label with Edit, and Settings' "New project…" is now "New-project settings…" so it doesn't read like File's actual create action. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+45
-128
@@ -6,8 +6,14 @@ use super::*;
|
||||
impl App {
|
||||
// ---- UI ----------------------------------------------------------------
|
||||
|
||||
/// The toolbar — the Workspace, Export, Grammar and Spelling rows — and the
|
||||
/// status bar along the bottom, which is rendered here too.
|
||||
/// The toolbar — the Workspace, Export and Grammar rows — and the status
|
||||
/// bar along the bottom, which is rendered here too.
|
||||
///
|
||||
/// Anything with a menu equivalent (opening the workspace, syncing git,
|
||||
/// toggling a panel, LanguageTool settings, …) lives only in the menu bar;
|
||||
/// this panel keeps just the controls that need a live value at hand —
|
||||
/// the workspace/export paths and the grammar-check trigger — so hiding it
|
||||
/// (View ▸ Toolbar, or Ctrl+D) never costs you a feature, only convenience.
|
||||
///
|
||||
/// The toolbar collapses through `show_animated`, which reserves no height
|
||||
/// when hidden and slides rather than snapping. Note that it guards only the
|
||||
@@ -22,9 +28,6 @@ impl App {
|
||||
egui::TextEdit::singleline(&mut self.workspace_input)
|
||||
.desired_width(300.0),
|
||||
);
|
||||
if ui.button("📂").on_hover_text("Browse for workspace folder").clicked() {
|
||||
self.browse_workspace();
|
||||
}
|
||||
if ui.button("Open").clicked()
|
||||
|| (resp.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)))
|
||||
{
|
||||
@@ -33,23 +36,6 @@ impl App {
|
||||
self.config.save();
|
||||
self.open_workspace();
|
||||
}
|
||||
ui.separator();
|
||||
if self.is_repo {
|
||||
let hover = match &self.repo_root {
|
||||
Some(root) if root.as_path() != self.workspace() => {
|
||||
format!("Commit & push to the repository at {}", root.display())
|
||||
}
|
||||
_ => "Commit & push this workspace's repository".to_string(),
|
||||
};
|
||||
if ui.button("⟳ Sync (git)").on_hover_text(hover).clicked() {
|
||||
self.git_sync();
|
||||
}
|
||||
} else if ui.button("Init git").clicked() {
|
||||
self.git_init();
|
||||
}
|
||||
if ui.button("Log").clicked() {
|
||||
self.show_log = !self.show_log;
|
||||
}
|
||||
});
|
||||
ui.add_space(2.0);
|
||||
ui.horizontal(|ui| {
|
||||
@@ -78,48 +64,6 @@ impl App {
|
||||
{
|
||||
self.export_manuscript();
|
||||
}
|
||||
// Resolved first: the hover text borrows the path, and the click
|
||||
// handler needs `self` mutably.
|
||||
let project = self.project_root().display().to_string();
|
||||
if ui
|
||||
.button("📁")
|
||||
.on_hover_text(format!("Open the project folder {project}"))
|
||||
.clicked()
|
||||
{
|
||||
self.open_project_folder();
|
||||
}
|
||||
ui.separator();
|
||||
if ui
|
||||
.checkbox(&mut self.config.show_preview, "Preview")
|
||||
.changed()
|
||||
{
|
||||
self.config.save();
|
||||
}
|
||||
ui.separator();
|
||||
ui.label("Draft marker:")
|
||||
.on_hover_text(
|
||||
"Text above this line in each file (its header) is dropped on export; \
|
||||
# Title: / # Slug: lines become the chapter heading and caption. \
|
||||
Leave blank to export files whole.",
|
||||
);
|
||||
let marker_resp = ui.add(
|
||||
egui::TextEdit::singleline(&mut self.config.draft_marker)
|
||||
.desired_width(140.0),
|
||||
);
|
||||
if marker_resp.lost_focus() {
|
||||
self.config.save();
|
||||
}
|
||||
ui.separator();
|
||||
if ui
|
||||
.checkbox(&mut self.config.zero_pad_index, "Zero-pad #")
|
||||
.on_hover_text(
|
||||
"When a chapter title defaults to its number, pad it with \
|
||||
leading zeros (e.g. 03. of 12).",
|
||||
)
|
||||
.changed()
|
||||
{
|
||||
self.config.save();
|
||||
}
|
||||
});
|
||||
ui.add_space(2.0);
|
||||
ui.horizontal(|ui| {
|
||||
@@ -143,58 +87,10 @@ impl App {
|
||||
{
|
||||
self.clear_lt();
|
||||
}
|
||||
if ui
|
||||
.button("⚙")
|
||||
.on_hover_text("LanguageTool settings")
|
||||
.clicked()
|
||||
{
|
||||
self.show_settings = true;
|
||||
}
|
||||
if !self.lt_status.is_empty() {
|
||||
ui.label(egui::RichText::new(&self.lt_status).weak());
|
||||
}
|
||||
});
|
||||
ui.add_space(2.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Spelling:").on_hover_text(
|
||||
"Offline spell check (Hunspell dictionaries). Underlines misspellings \
|
||||
as you type when LanguageTool results aren't current; right-click a \
|
||||
word for suggestions.",
|
||||
);
|
||||
if ui
|
||||
.checkbox(&mut self.config.spell_check, "Check as I type")
|
||||
.on_hover_text("Underline misspellings live using the dictionary below")
|
||||
.changed()
|
||||
{
|
||||
self.config.save();
|
||||
if self.config.spell_check {
|
||||
// Re-check the current buffer straight away.
|
||||
self.spell_dirty = true;
|
||||
self.spell_last_edit = None;
|
||||
}
|
||||
}
|
||||
ui.label("Dictionary:");
|
||||
let current = self.config.spell_language.clone();
|
||||
let mut pick: Option<String> = None;
|
||||
egui::ComboBox::from_id_salt("spell_dict")
|
||||
.selected_text(current.clone())
|
||||
.show_ui(ui, |ui| {
|
||||
for d in &self.spell_dicts {
|
||||
if ui
|
||||
.selectable_label(current == d.id, &d.label)
|
||||
.clicked()
|
||||
{
|
||||
pick = Some(d.id.clone());
|
||||
}
|
||||
}
|
||||
});
|
||||
if let Some(id) = pick {
|
||||
self.set_spell_language(&id);
|
||||
}
|
||||
if !self.spell_status.is_empty() {
|
||||
ui.label(egui::RichText::new(&self.spell_status).weak());
|
||||
}
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
});
|
||||
|
||||
@@ -271,7 +167,8 @@ impl App {
|
||||
if ui
|
||||
.button("✨ New project…")
|
||||
.on_hover_text(
|
||||
"Scaffold a project from the cookiecutter template and open its drafting folder",
|
||||
"Scaffold a project from the bundled template and open \
|
||||
its drafting folder",
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
@@ -283,6 +180,26 @@ impl App {
|
||||
ui.close_menu();
|
||||
self.browse_workspace();
|
||||
}
|
||||
if ui.button("📁 Open project folder").clicked() {
|
||||
ui.close_menu();
|
||||
self.open_project_folder();
|
||||
}
|
||||
if self.is_repo {
|
||||
let hover = match &self.repo_root {
|
||||
Some(root) if root.as_path() != self.workspace() => {
|
||||
format!("Commit & push to the repository at {}", root.display())
|
||||
}
|
||||
_ => "Commit & push this workspace's repository".to_string(),
|
||||
};
|
||||
if ui.button("⟳ Sync (git)").on_hover_text(hover).clicked() {
|
||||
ui.close_menu();
|
||||
self.git_sync();
|
||||
}
|
||||
} else if ui.button("Init git").clicked() {
|
||||
ui.close_menu();
|
||||
self.git_init();
|
||||
}
|
||||
ui.separator();
|
||||
if ui.button(self.export_label()).clicked() {
|
||||
ui.close_menu();
|
||||
self.export_manuscript();
|
||||
@@ -321,10 +238,6 @@ impl App {
|
||||
ui.close_menu();
|
||||
self.open_find(true);
|
||||
}
|
||||
if ui.button("📖 Story Bible…").clicked() {
|
||||
ui.close_menu();
|
||||
self.open_story_bible_editor();
|
||||
}
|
||||
if ui.button("📸 Snapshots…").clicked() {
|
||||
ui.close_menu();
|
||||
self.show_snapshots = true;
|
||||
@@ -343,6 +256,10 @@ impl App {
|
||||
ui.close_menu();
|
||||
self.show_story_bible = true;
|
||||
}
|
||||
if ui.button("📖 Story Bible editor…").clicked() {
|
||||
ui.close_menu();
|
||||
self.open_story_bible_editor();
|
||||
}
|
||||
ui.separator();
|
||||
let busy = self.beats_rx.is_some();
|
||||
if ui
|
||||
@@ -360,7 +277,7 @@ impl App {
|
||||
if ui
|
||||
.checkbox(&mut self.config.show_toolbar, "Toolbar")
|
||||
.on_hover_text(
|
||||
"The Workspace, Export, Grammar and Spelling rows \
|
||||
"The Workspace, Export and Grammar rows \
|
||||
under the menu bar",
|
||||
)
|
||||
.clicked()
|
||||
@@ -432,7 +349,7 @@ impl App {
|
||||
ui.close_menu();
|
||||
self.show_template_settings = true;
|
||||
}
|
||||
if ui.button("New project…").clicked() {
|
||||
if ui.button("New-project settings…").clicked() {
|
||||
ui.close_menu();
|
||||
self.show_project_settings = true;
|
||||
}
|
||||
@@ -444,7 +361,7 @@ impl App {
|
||||
ui.close_menu();
|
||||
self.show_manuscript_settings = true;
|
||||
}
|
||||
if ui.button("Word list…").clicked() {
|
||||
if ui.button("Spelling…").clicked() {
|
||||
ui.close_menu();
|
||||
self.show_wordlist = true;
|
||||
}
|
||||
@@ -459,9 +376,9 @@ impl App {
|
||||
// a control that hides the toolbar cannot live inside it.
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
let (icon, hint) = if self.config.show_toolbar {
|
||||
("⬆", "Hide the toolbar (Workspace, Export, Grammar, Spelling)")
|
||||
("⬆", "Hide the toolbar (Workspace, Export, Grammar)")
|
||||
} else {
|
||||
("⬇", "Show the toolbar (Workspace, Export, Grammar, Spelling)")
|
||||
("⬇", "Show the toolbar (Workspace, Export, Grammar)")
|
||||
};
|
||||
if ui.button(icon).on_hover_text(hint).clicked() {
|
||||
self.config.show_toolbar = !self.config.show_toolbar;
|
||||
@@ -666,11 +583,11 @@ impl App {
|
||||
|
||||
pub(super) fn header_toggle(&mut self, ui: &mut egui::Ui) {
|
||||
let collapsed = self.header_stash.is_some();
|
||||
// Nothing to say about a file with no marker to fold at.
|
||||
if !collapsed
|
||||
&& crate::preprocess::split_at_marker(&self.buffer, &self.config.draft_marker)
|
||||
.is_none()
|
||||
{
|
||||
// Nothing to say about a file with no header to fold away. Both forms
|
||||
// count: asking only about the draft marker meant a frontmatter file
|
||||
// never offered the toggle, because it has no marker to find — the fold
|
||||
// itself has handled frontmatter all along.
|
||||
if !collapsed && !self.header_is_foldable() {
|
||||
return;
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
@@ -678,7 +595,7 @@ impl App {
|
||||
let n = self.hidden_field_count();
|
||||
(
|
||||
format!("▸ header hidden ({n} field{})", if n == 1 { "" } else { "s" }),
|
||||
"Show the fields above the draft marker",
|
||||
"Show this file's fields again",
|
||||
)
|
||||
} else {
|
||||
(
|
||||
|
||||
Reference in New Issue
Block a user