0.183.4: mobile edit/split modal overhaul + pop-out fixes

Tabbed editor on phones, top-anchored sheet with the editor filling and the action
bar staying above the keyboard, slimmer controls, copy button above the text,
Pop-out/Advanced labels, full-width pop-out tab (editor no longer collapses), and a
larger composer full-screen button.
This commit is contained in:
GB 2026-07-16 07:28:06 -07:00
parent b6f593e6b2
commit 8850970bb3
6 changed files with 146 additions and 42 deletions

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
{
"id": "stashpad",
"name": "Stashpad",
"version": "0.181.0",
"version": "0.183.4",
"minAppVersion": "1.13.0",
"description": "A chat-style, nested-notes workspace: rapid capture, outliner navigation, fast search, tasks, and per-folder templates, with one-click Open Knowledge Format (OKF) export for LLMs and agents.",
"author": "Human",

View file

@ -1,6 +1,6 @@
{
"name": "stashpad-obsidian",
"version": "0.181.0",
"version": "0.183.4",
"private": true,
"scripts": {
"dev": "node esbuild.config.mjs",

30
release-notes/0.183.4.md Normal file
View file

@ -0,0 +1,30 @@
# 0.183.4
A focused pass on the mobile edit / split experience.
## Mobile edit & split modal
- **Tabbed editor.** On a phone, the Original / Changes / Your-edit sections now
share one space as tabs (one visible at a time) instead of stacking, so the
modal stays compact.
- **Top-anchored sheet.** The modal anchors to the top of the screen (clear of the
notch / Dynamic Island) with a consistent height, and the editor fills it and
scrolls — the action bar stays put above the keyboard instead of being pushed
off or lifted/distorted when the keyboard opens.
- **Slimmer, cleaner controls.** Compact Edit/Split toggle, tabs, and buttons; the
bottom action bar reads as part of the modal (no stray black box); the pop-out
and Obsidian-editor buttons are labelled **"Pop-out"** and **"Advanced"**.
- **Copy button** for the Original text moved to a small button above the text
(it used to overlay the text's right edge).
## Pop-out tab
- Opening the editor **in its own tab** now uses the **full width** of the tab, the
editor fills the leaf (it no longer collapses to a sliver when the keyboard is
up), the content clears Obsidian's bottom toolbar, and all the action buttons
stay reachable.
## Composer
- The composer's **full-screen button** (open your draft in the roomy in-app
editor) is a bit larger and easier to tap.

View file

@ -652,7 +652,7 @@ export class NoteWorkbench {
if (!this.cb.popOut) return;
const pop = actions.createEl("button", { cls: "stashpad-split-popout-btn" });
setIcon(pop.createSpan({ cls: "stashpad-split-popout-icon" }), "maximize-2");
pop.createSpan({ text: "Open in a tab" });
pop.createSpan({ text: Platform.isMobile ? "Pop-out" : "Open in a tab" });
pop.setAttr("aria-label", "Open in a full tab");
pop.onmousedown = (e) => e.preventDefault();
pop.onclick = () => this.cb.popOut!(this.getState());
@ -663,7 +663,7 @@ export class NoteWorkbench {
if (!this.cb.onOpenExternal) return;
const b = actions.createEl("button", { cls: "stashpad-split-popout-btn" });
setIcon(b.createSpan({ cls: "stashpad-split-popout-icon" }), "pencil");
b.createSpan({ text: "Obsidian editor" });
b.createSpan({ text: Platform.isMobile ? "Advanced" : "Obsidian editor" });
b.setAttr("aria-label", "Open in a full Obsidian editor tab");
b.onmousedown = (e) => e.preventDefault();
b.onclick = () => { this.cb.onOpenExternal!(); this.cb.close(); };
@ -980,10 +980,12 @@ export class NoteWorkbench {
ta.readOnly = false;
this.cursorTextarea = ta;
// Original (read-only) — copy button lives inside the panel (top-right).
// Original (read-only). 0.183.3: copy button in a right-aligned bar ABOVE the
// text (it used to overlay the text's right edge as a full-height strip).
const origPanel = bodyHost.createDiv({ cls: "stashpad-split-tabpanel" });
const origCopyBar = origPanel.createDiv({ cls: "stashpad-split-tab-copybar" });
origCopyBar.appendChild(this.makeCopyButton(() => this.body, "Copy the original text", "stashpad-split-copy-btn stashpad-split-tab-copy"));
origPanel.createDiv({ cls: "stashpad-split-panel-body", text: this.body });
origPanel.appendChild(this.makeCopyButton(() => this.body, "Copy the original text", "stashpad-split-copy-btn stashpad-split-tab-copy"));
// Changes (word diff).
const changesPanel = bodyHost.createDiv({ cls: "stashpad-split-tabpanel" });
@ -1027,26 +1029,20 @@ export class NoteWorkbench {
if (!edited && (active === "orig" || active === "changes")) showTab("edit");
};
// Firm height cap so the editor scrolls instead of pushing the action bar
// past the keyboard (fewer lines than desktop — space is at a premium).
const lineHeight = parseFloat(getComputedStyle(ta).lineHeight) || 22;
const maxLines = this.surface === "edit" ? 8 : 4;
ta.setCssStyles({ maxHeight: `${lineHeight * maxLines + 16}px`, overflowY: "auto" });
const fit = (): void => {
ta.setCssStyles({ height: "auto" });
const needed = Math.min(ta.scrollHeight, lineHeight * maxLines + 16);
ta.setCssStyles({ height: `${Math.max(needed, lineHeight * 2 + 16)}px` });
};
// 0.183.0: the editor FILLS the fixed-height modal (flex, via CSS) and scrolls
// internally — no explicit line cap. With the modal at a fixed height it can't
// grow past the keyboard anyway, so filling looks better than a hard cap that
// leaves an empty gap under a short note.
ta.setCssStyles({ overflowY: "auto" });
syncEdited();
showTab("edit");
requestAnimationFrame(() => {
fit();
ta.focus();
const pos = this.surface === "edit" ? ta.value.length : Math.floor(ta.value.length / 2);
ta.setSelectionRange(pos, pos);
});
ta.addEventListener("input", () => { this.cursorText = ta.value; fit(); syncEdited(); });
ta.addEventListener("input", () => { this.cursorText = ta.value; syncEdited(); });
}
}

View file

@ -1737,8 +1737,8 @@ body.is-phone .modal.stashpad-compact-modal.stashpad-breadcrumb-modal {
position: absolute;
top: 4px;
right: 6px;
width: 22px;
height: 22px;
width: 27px;
height: 27px;
padding: 0;
display: inline-flex;
align-items: center;
@ -1752,7 +1752,7 @@ body.is-phone .modal.stashpad-compact-modal.stashpad-breadcrumb-modal {
opacity: 0.65;
}
.stashpad-composer-fullscreen:hover { background: var(--background-modifier-hover); color: var(--text-normal); opacity: 1; }
.stashpad-composer-fullscreen .svg-icon { width: 14px; height: 14px; }
.stashpad-composer-fullscreen .svg-icon { width: 17px; height: 17px; }
/* Keep typed text clear of the button. */
.stashpad-composer-input { padding-right: 32px; }
@ -2266,6 +2266,33 @@ body.is-phone .modal.stashpad-compact-modal.stashpad-breadcrumb-modal {
max-width: 860px;
margin: 0 auto;
}
/* 0.183.1: the popped-out editor VIEW on mobile. Make it a FLEX column so the
editor fills the leaf it collapsed to <1 line when the keyboard shrank the
viewport because the host wasn't a flex container. Bottom padding clears
Obsidian's mobile bottom toolbar + the home indicator. */
.is-mobile .stashpad-split-view {
display: flex;
flex-direction: column;
/* 0.183.3: SCROLLABLE so no button is ever permanently tucked when the keyboard
shrinks the leaf. 0.183.4: FULL WIDTH the base rule's max-width:860px +
margin:0 auto was centering it (auto side margins), leaving it inset. */
overflow-y: auto;
width: 100%;
max-width: none;
margin: 0;
padding: 10px 8px calc(env(safe-area-inset-bottom, 0px) + 52px);
}
.is-mobile .stashpad-split-view .stashpad-split-tabpanel.is-active .stashpad-split-cursor-ta {
min-height: calc(1.5em * 6);
}
/* Help text is empty on phones anyway — hide it so it doesn't eat vertical space. */
.is-mobile .stashpad-split-help { display: none; }
/* In the scrollable VIEW the editor already fills via flex; margin-top:auto
misbehaves with overflow. 0.183.4: give it a fixed gap so the separator line
isn't jammed against the text box / Case row a touch more in split mode
(text box edge) than edit mode (Case row). */
.is-mobile .stashpad-split-view:not(.stashpad-edit-surface) .stashpad-split-actions { margin-top: 21px; }
.is-mobile .stashpad-split-view.stashpad-edit-surface .stashpad-split-actions { margin-top: 18px; }
/* In a full tab there's room let the read-only previews and the preset list
breathe (they stay short/scrolly inside the cramped modal). */
.stashpad-split-view .stashpad-split-panel-body { max-height: calc(1.5em * 10 + 0.8em); }
@ -2287,14 +2314,28 @@ body.is-phone .modal.stashpad-compact-modal.stashpad-breadcrumb-modal {
end of the flow, so a long note (tall textarea) pushed Save/Cancel off-screen
and with the keyboard up behind it. Now the content scrolls and the
actions stick to the bottom of the visible area, always reachable. */
.is-mobile .stashpad-split-modal {
/* 0.182.0: anchor the sheet to the TOP (was centered) with a notch / Dynamic
Island offset so it has more vertical room and its bottom sits well clear of
the keyboard + the keyboard's autocomplete suggestion strip. */
.is-mobile .modal-container:has(> .stashpad-split-modal) {
align-items: flex-start;
}
/* FLOATING modal only (`:not(.stashpad-split-view)`) the popped-out tab shares
the .stashpad-split-modal class but is a full leaf, so it must NOT inherit the
modal's min-height / margin / max-height (that collapsed it). */
.is-mobile .stashpad-split-modal:not(.stashpad-split-view) {
width: 100vw;
max-width: 100vw;
height: 100%;
max-height: 100%;
height: auto;
/* Consistent, roomier height regardless of content; the editor fills it.
0.183.1: 20px shorter so the action bar stays clear of the keyboard. */
min-height: calc(58vh - 20px);
/* Notch/Dynamic-Island offset, a quarter shorter (0.75× the inset). */
margin-top: calc(env(safe-area-inset-top, 0px) * 0.75 + 8px);
max-height: calc(100% - env(safe-area-inset-top, 0px) - 32px);
display: flex;
flex-direction: column;
border-radius: 0;
border-radius: 0 0 14px 14px;
}
.is-mobile .stashpad-split-modal .modal-content {
flex: 1 1 auto;
@ -2305,18 +2346,17 @@ body.is-phone .modal.stashpad-compact-modal.stashpad-breadcrumb-modal {
padding: 10px 12px;
-webkit-overflow-scrolling: touch;
}
/* Pinned, full-bleed action bar (Save/Cancel/pop-out). Sticks to the bottom of
the scrollport so it stays visible above the keyboard; wraps on narrow width. */
/* 0.183.0: action bar pinned to the BOTTOM of the modal via margin-top:auto
NOT position:sticky. Sticky made iOS lift/distort the row when the keyboard
opened. No env(safe-area-inset-bottom) padding either (the sheet is top-anchored,
not a bottom sheet that padding drew empty space beneath the buttons on iOS). */
.is-mobile .stashpad-split-modal .stashpad-split-actions {
position: sticky;
bottom: 0;
margin: 8px -12px -10px;
padding: 8px 12px calc(8px + env(safe-area-inset-bottom, 0px));
background: var(--background-primary);
margin-top: auto; /* pin to the bottom of the modal/leaf (not sticky) */
padding-top: 10px;
/* 0.183.2: NO opaque background it read as a separate black box floating on
the modal in iOS dark mode. Just a hairline separator. */
border-top: 1px solid var(--background-modifier-border);
flex-wrap: wrap;
gap: 8px;
z-index: 3;
}
/* On a phone the primary Save spans the row; secondary actions wrap under it. */
.is-mobile .stashpad-split-modal .stashpad-split-actions-right {
@ -2353,23 +2393,61 @@ body.is-phone .modal.stashpad-compact-modal.stashpad-breadcrumb-modal {
.stashpad-split-tab.is-hidden { display: none; }
.stashpad-split-tabpanel { position: relative; }
.stashpad-split-tabpanel:not(.is-active) { display: none; }
/* Copy button sits in the Original panel (top-right), scaled small. */
.stashpad-split-tab-copy { position: absolute; top: 4px; right: 4px; z-index: 2; padding: 4px; }
/* 0.183.0: the editor FILLS the fixed-height modal (no empty gap under short
notes). Flex chain: modal-content tabs tabbody active panel textarea,
each stretching; the textarea/preview scrolls internally. */
.is-mobile .stashpad-split-tabs { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.is-mobile .stashpad-split-tabbody { flex: 1 1 auto; min-height: 0; display: flex; }
.is-mobile .stashpad-split-tabpanel.is-active { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.is-mobile .stashpad-split-tabpanel.is-active .stashpad-split-cursor-ta { flex: 1 1 auto; min-height: 0; height: auto; }
.is-mobile .stashpad-split-tabpanel.is-active .stashpad-split-panel-body { flex: 1 1 auto; min-height: 0; max-height: none; }
/* 0.183.3: copy button lives in its own right-aligned bar ABOVE the Original text
(no longer a full-height strip overlaying the text's right edge). */
.stashpad-split-tab-copybar { display: flex; justify-content: flex-end; margin-bottom: 4px; flex: 0 0 auto; }
.stashpad-split-tab-copy {
position: static;
height: auto;
width: auto;
padding: 5px 12px;
gap: 5px;
background: var(--background-secondary);
border: 1px solid var(--background-modifier-border);
}
.stashpad-split-tab-copy svg { width: 1.15em; height: 1.15em; }
/* Slimmer mobile modal buttons the toggle / tabs / mode / action buttons were
too chunky. Tight vertical padding + line-height, no min-height. */
.is-mobile .stashpad-split-surface-btn,
.is-mobile .stashpad-split-tab,
.is-mobile .stashpad-split-mode-btn,
.is-mobile .stashpad-split-cancel-btn,
.is-mobile .stashpad-split-confirm-btn,
.is-mobile .stashpad-split-popout-btn,
.is-mobile .stashpad-split-case-btn {
/* Obsidian mobile forces height: 44px on buttons — override to size to content. */
height: auto;
padding-top: 4px;
padding-bottom: 4px;
min-height: 0;
line-height: 1.25;
}
.stashpad-split-tabpanel .stashpad-split-panel-body {
max-height: calc(1.5em * 7 + 12px);
overflow-y: auto;
padding: 8px 10px;
margin: 0;
}
/* Action bar: keep all buttons on ONE row, compact. Cancel drops its "(Esc)" hint,
and pop-out + Obsidian-editor go icon-only, so Cancel / / / Save fit. */
.is-mobile .stashpad-split-modal .stashpad-split-actions { flex-wrap: nowrap; gap: 6px; margin-top: 6px; }
/* Action bar: compact buttons; Cancel drops its "(Esc)" hint. 0.183.0: pop-out +
Obsidian-editor now SHOW short text labels ("Pop-out" / "Advanced"), so the row
is allowed to wrap. */
.is-mobile .stashpad-split-modal .stashpad-split-actions { flex-wrap: wrap; gap: 6px; }
.is-mobile .stashpad-split-cancel-btn,
.is-mobile .stashpad-split-confirm-btn,
.is-mobile .stashpad-split-popout-btn { padding: 6px 10px; }
.is-mobile .stashpad-split-esc-hint { display: none; }
.is-mobile .stashpad-split-popout-btn span:not([class*="icon"]) { display: none; }
.is-mobile .stashpad-split-actions-right { margin-left: auto; gap: 8px; }
/* Let Save flow inline with the others (wraps to a tidy grid) instead of being
pushed to its own row by margin-left:auto. */
.is-mobile .stashpad-split-actions-right { margin-left: 0; gap: 8px; }
.is-mobile .stashpad-split-confirm-btn { flex: 1 1 auto; justify-content: center; }
/* Tap-friendly line picker — make each line look interactable. */
.stashpad-split-line { cursor: pointer; }
.stashpad-split-line:hover { background: var(--background-modifier-hover); }