Edit: Check my replies for more updated/fixed verisons. Feel free to pick whichever one works best for you.
Save this as a .js file and point CodeScript ToolKit to it as your “Startup script path.” It’ll make the Obsidan app header hide when the softkeyboard is open and show when it’s not.
const capacitorapp = window.Capacitor?.Plugins?.App;
export async function invoke(app) {
if (capacitorapp && capacitorapp.addListener) {
window.Capacitor?.Plugins?.Keyboard?.addListener('keyboardWillShow', () => {
document.querySelectorAll('.view-header').forEach(el => {
el.dataset.originalDisplay = el.style.display || '';
el.style.display = 'none';
});
});
window.Capacitor?.Plugins?.Keyboard?.addListener('keyboardWillHide', () => {
document.querySelectorAll('.view-header').forEach(el => {
el.style.display = el.dataset.originalDisplay || '';
delete el.dataset.originalDisplay;
});
});
}
}
Relevant feature requests:
3 Likes
While making it so the cursor changes colors based on Vim operating mode with a combination of JavaScript and CSS, I realized I can do a similar thing here.
const capacitorapp = window.Capacitor?.Plugins?.App;
export async function invoke(app) {
let keyboardopen = false;
if (capacitorapp && capacitorapp.addListener) {
window.Capacitor?.Plugins?.Keyboard?.addListener('keyboardWillShow', () => {
keyboardopen = true;
if (document.activeElement.className == 'view-header-title') {
if (!document.activeElement.onblur) {
document.activeElement.onblur = function() {
if (keyboardopen) document.body.style.setProperty('--headerview', 'none');
};
}
return;
}
document.body.style.setProperty('--headerview', 'none');
});
window.Capacitor?.Plugins?.Keyboard?.addListener('keyboardWillHide', () => {
keyboardopen = false;
document.body.style.setProperty('--headerview', 'flex');
});
}
}
body {
--headerview: flex;
}
.workspace-leaf-content[data-type="markdown"] .view-header {
display: var(--headerview) !important;
}
This works more consistently than the previous script. I was using the previous one and noticed it got a little buggy when going from tablet mode to phone mode on my foldable. Maybe because it targeted all .view-header
instead of .view.header
that specifically have data-type="markdown"
in their parent .workspace-leaf-content
.
This version also doesn’t hide the app header when the keyboard opens if you tap on the app header to edit the note title.
@CawlinTeffid, not sure if you tried the previous version (I remember you expressing interest). This version in my last reply here should work much better. Plus now I’m actually using it myself, too. Let me know if there’s any improvements you’d like to see.
I think I tried it and went back to the CSS snippet. I’ll give the new version a try. Thanks!
Oh, I took a look at the CSS again. That transition is nice! Here’s how to plug it into the JS I made.
This takes that CSS and then makes it so the part that hides runs when body
gets a class called hide-view-header
applied to it.
body.hide-view-header .workspace-leaf-content[data-type="markdown"] .view-header {
border-bottom: 0;
height: 0;
overflow: hidden;
}
.workspace-leaf-content[data-type="markdown"] .view-header {
transition: height 250ms;
}
The JavaScript changes:
document.body.style.setProperty('--headerview', 'none');
is replaced with document.body.classList.add('hide-view-header');
and
document.body.style.setProperty('--headerview', 'flex');
is replaced with document.body.classList.remove('hide-view-header');
.
const capacitorapp = window.Capacitor?.Plugins?.App;
export async function invoke(app) {
let keyboardopen = false;
if (capacitorapp && capacitorapp.addListener) {
window.Capacitor?.Plugins?.Keyboard?.addListener('keyboardWillShow', () => {
keyboardopen = true;
if (document.activeElement.className == 'view-header-title') {
if (!document.activeElement.onblur) {
document.activeElement.onblur = function() {
if (keyboardopen) document.body.classList.add('hide-view-header');
};
}
return;
}
document.body.classList.add('hide-view-header');
});
window.Capacitor?.Plugins?.Keyboard?.addListener('keyboardWillHide', () => {
keyboardopen = false;
document.body.classList.remove('hide-view-header');
});
}
}
So now this is the best of both worlds; detecting when the keyboard is open more natively without needing to check if the mobile-toolbar
is visible plus a nice CSS transition.
Edited everything fom keyboardDidShow
and keyboardDidHide
to keyboardWillShow
and keyboardWillHide
since Obsidian 1.9.10 made keyboardDidShow
and keyboardDidHide
fire only after the keyboard fully shows.
Updated version that doesn’t change your note position so much when the header hides
In the previous version, when the view-header hid, your editing content moved up to fill that space. I got kinda annoyed by that since I’d tap some text and then it’d move away from where I tapped.
This version now tries to counter that by scrolling the same amount that is hidden/shown. It’s a little more jumpy than the last version due to scrolling, but how fast it animates the scroll and header transition can be changed with the const duration = 250;
at the top. Just change it to however many milliseconds you want.
Also, I made it animate opacity
as well, since, due to overflow: hidden;
, previously the stuff inside the header would pop back in kinda jarringly when the header was shown again.
JS
import { MarkdownView } from 'obsidian';
export async function invoke(app) {
const duration = 250;
document.body.style.setProperty('--headertransduration', `${duration}ms`);
const capacitorapp = window.Capacitor?.Plugins?.App;
let viewHeaderHeight = 0;
let keyboardopen = false;
let headerhidden = false;
function adjustScroll(delta) {
const editor = app.workspace.getActiveViewOfType(MarkdownView)?.editor;
if (!editor) return;
const scrollInfo = editor.getScrollInfo();
const start = scrollInfo.top;
const end = start + delta;
const startTime = performance.now();
function animate(time) {
const elapsed = time - startTime;
const progress = Math.min(elapsed / duration, 1);
const ease = progress < 0.5
? 2 * progress * progress
: -1 + (4 - 2 * progress) * progress;
editor.scrollTo(scrollInfo.left, start + (end - start) * ease);
if (progress < 1) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
}
function hideHeader() {
if (headerhidden) return;
document.body.classList.add('hide-view-header');
adjustScroll(-viewHeaderHeight);
headerhidden = true;
}
function showHeader() {
if (!headerhidden) return;
document.body.classList.remove('hide-view-header');
adjustScroll(viewHeaderHeight);
headerhidden = false;
}
if (capacitorapp?.addListener) {
window.Capacitor?.Plugins?.Keyboard?.addListener('keyboardWillShow', () => {
keyboardopen = true;
if (document.activeElement.className == 'view-header-title') {
if (!document.activeElement.onblur) {
document.activeElement.onblur = function() {
if (keyboardopen) hideHeader();
};
}
return;
}
hideHeader();
});
window.Capacitor?.Plugins?.Keyboard?.addListener('keyboardWillHide', () => {
keyboardopen = false;
showHeader();
});
}
app.workspace.onLayoutReady(() => {
const leafContent = document.querySelector('.workspace-leaf-content:not([data-type="outgoing-link"]):not([data-type="backlink"]):not([data-type="undefined"])');
const viewHeader = leafContent?.querySelector('.view-header');
viewHeaderHeight = viewHeader ? viewHeader.getBoundingClientRect().height : 0;
});
}
CSS
body {
--headertransduration: 0ms;
}
.workspace-leaf-content[data-type="markdown"] .view-header {
transition: height var(--headertransduration), opacity var(--headertransduration);
}
body.hide-view-header .workspace-leaf-content[data-type="markdown"] .view-header {
border-bottom: 0;
height: 0;
opacity: 0;
overflow: hidden;
}
Now only hides the header when focusing on the actual editor content (no longer hides when editing properties, searching, focused on the quickswitcher, etc.)
If going from the editor content to the properties it still stays hidden until the keyboard is closed because I prefer that.
import { MarkdownView } from 'obsidian';
export async function invoke(app) {
const duration = 250;
document.body.style.setProperty('--headertransduration', `${duration}ms`);
const capacitorapp = window.Capacitor?.Plugins?.App;
let viewHeaderHeight = 0;
let keyboardopen = false;
let headerhidden = false;
function adjustScroll(delta) {
const editor = app.workspace.getActiveViewOfType(MarkdownView)?.editor;
if (!editor) return;
const scrollInfo = editor.getScrollInfo();
const start = scrollInfo.top;
const end = start + delta;
const startTime = performance.now();
function animate(time) {
const elapsed = time - startTime;
const progress = Math.min(elapsed / duration, 1);
const ease = progress < 0.5
? 2 * progress * progress
: -1 + (4 - 2 * progress) * progress;
editor.scrollTo(scrollInfo.left, start + (end - start) * ease);
if (progress < 1) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
}
function hideHeader() {
if (headerhidden) return;
document.body.classList.add('hide-view-header');
adjustScroll(-viewHeaderHeight);
headerhidden = true;
}
function showHeader() {
if (!headerhidden) return;
document.body.classList.remove('hide-view-header');
adjustScroll(viewHeaderHeight);
headerhidden = false;
}
if (capacitorapp?.addListener) {
window.Capacitor?.Plugins?.Keyboard?.addListener('keyboardWillShow', () => {
keyboardopen = true;
if (document.activeElement.classList.contains('view-header-title') && !document.activeElement.onblur) {
document.activeElement.onblur = function() {
setTimeout(() => {
if (keyboardopen && document.activeElement.classList.contains('cm-content')) {
hideHeader();
}
}, 100);
};
return;
} else if (document.activeElement.classList.contains('cm-content')) {
hideHeader();
}
});
window.Capacitor?.Plugins?.Keyboard?.addListener('keyboardWillHide', () => {
keyboardopen = false;
showHeader();
});
}
app.workspace.onLayoutReady(() => {
const leafContent = document.querySelector('.workspace-leaf-content:not([data-type="outgoing-link"]):not([data-type="backlink"]):not([data-type="undefined"])');
const viewHeader = leafContent?.querySelector('.view-header');
viewHeaderHeight = viewHeader ? viewHeader.getBoundingClientRect().height : 0;
});
}
Now properly shows the header when the editor is defocused without closing the keyboard
If you create a new file, the keyboard starts focused on the header. Previously, if the header was hidden, you couldn’t see what you were typing. Now, it won’t be hidden since it’ll re-check if it’s still focused on editor content.
With these changes, editing properties would also always make the header show. So, I also made it so that editing properties doesn’t mess with header visibility; it’ll stay visible if visible or hidden if hidden.
export async function invoke(app) {
const duration = 250;
document.body.style.setProperty('--headertransduration', `${duration}ms`);
const capacitorapp = window.Capacitor?.Plugins?.App;
let viewHeaderHeight = 0;
let keyboardopen = false;
let headerhidden = false;
function adjustScroll(delta) {
const editor = app.workspace?.activeLeaf?.view?.editor;
if (!editor || app.workspace?.activeLeaf?.view?.file?.extension != 'md') return;
const scrollInfo = editor.getScrollInfo();
const start = scrollInfo.top;
const end = start + delta;
const startTime = performance.now();
function animate(time) {
const elapsed = time - startTime;
const progress = Math.min(elapsed / duration, 1);
const ease = progress < 0.5
? 2 * progress * progress
: -1 + (4 - 2 * progress) * progress;
editor.scrollTo(scrollInfo.left, start + (end - start) * ease);
if (progress < 1) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
}
function hideHeader() {
if (headerhidden || !document.activeElement.classList.contains('cm-content') || document.activeElement.closest('.metadata-property-value') || document.activeElement.closest('.metadata-property-key')) return;
document.body.classList.add('hide-view-header');
adjustScroll(-viewHeaderHeight);
headerhidden = true;
}
function showHeader() {
if (!headerhidden) return;
document.body.classList.remove('hide-view-header');
adjustScroll(viewHeaderHeight);
headerhidden = false;
}
function elementDefocused() {
setTimeout(() => {
if(!document.activeElement.onblur) document.activeElement.onblur = elementDefocused();
if (keyboardopen) {
if (document.activeElement.classList.contains('cm-content')) {
hideHeader();
} else if (!document.activeElement.closest('.metadata-property-value') && !document.activeElement.closest('.metadata-property-key')) {
showHeader();
}
}
}, 100);
}
if (capacitorapp?.addListener) {
window.Capacitor?.Plugins?.Keyboard?.addListener('keyboardWillShow', () => {
keyboardopen = true;
hideHeader();
if(!document.activeElement.onblur) document.activeElement.onblur = elementDefocused();
});
window.Capacitor?.Plugins?.Keyboard?.addListener('keyboardWillHide', () => {
keyboardopen = false;
showHeader();
});
}
app.workspace.onLayoutReady(() => {
const leafContent = document.querySelector('.workspace-leaf-content:not([data-type="outgoing-link"]):not([data-type="backlink"]):not([data-type="undefined"])');
const viewHeader = leafContent?.querySelector('.view-header');
viewHeaderHeight = viewHeader ? viewHeader.getBoundingClientRect().height : 0;
});
}