mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
fix: improve mobile keyboard/navbar CSS scoping and platform detection (#2157)
* fix: improve mobile keyboard/navbar CSS scoping and platform detection * fix: clear drawer keyboard class when view moves out of drawer Track the last drawer element so that when the Copilot view is moved out of a drawer while the soft keyboard is open, the copilot-keyboard-open class is properly removed from the old drawer, restoring its header/tab UI.
This commit is contained in:
parent
aa71040702
commit
c76235a326
3 changed files with 102 additions and 5 deletions
|
|
@ -5,7 +5,7 @@ import { AppContext, EventTargetContext } from "@/context";
|
|||
import CopilotPlugin from "@/main";
|
||||
import { FileParserManager } from "@/tools/FileParserManager";
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import { ItemView, WorkspaceLeaf } from "obsidian";
|
||||
import { ItemView, Platform, WorkspaceLeaf } from "obsidian";
|
||||
import * as React from "react";
|
||||
import { createRoot, Root } from "react-dom/client";
|
||||
|
||||
|
|
@ -17,6 +17,8 @@ export default class CopilotView extends ItemView {
|
|||
private fileParserManager: FileParserManager;
|
||||
private root: Root | null = null;
|
||||
private handleSaveAsNote: (() => Promise<void>) | null = null;
|
||||
private keyboardObserver: MutationObserver | null = null;
|
||||
private lastDrawerEl: HTMLElement | null = null;
|
||||
eventTarget: EventTarget;
|
||||
|
||||
constructor(
|
||||
|
|
@ -58,6 +60,52 @@ export default class CopilotView extends ItemView {
|
|||
};
|
||||
|
||||
this.renderView(handleSaveAsNote, updateUserMessageHistory);
|
||||
this.setupMobileKeyboardObserver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Observe --keyboard-height on <html> style to toggle a class on the
|
||||
* parent .workspace-drawer when the soft keyboard is open.
|
||||
* CSS uses this class to hide drawer header elements on mobile.
|
||||
*
|
||||
* Reason: The drawer lookup is inside the callback (not at setup time) because
|
||||
* the view can be moved from editor tab to drawer without triggering onOpen again.
|
||||
*/
|
||||
private setupMobileKeyboardObserver(): void {
|
||||
if (!Platform.isMobile) return;
|
||||
|
||||
// Reason: Disconnect any existing observer defensively in case onOpen runs more than once
|
||||
this.keyboardObserver?.disconnect();
|
||||
|
||||
const syncKeyboardClass = () => {
|
||||
const drawer = this.containerEl.closest(".workspace-drawer") as HTMLElement | null;
|
||||
|
||||
// Reason: If the view moved out of its previous drawer, clear the class on the old one
|
||||
// so drawer chrome (header/tab options) is restored.
|
||||
if (this.lastDrawerEl && this.lastDrawerEl !== drawer) {
|
||||
this.lastDrawerEl.classList.remove("copilot-keyboard-open");
|
||||
}
|
||||
this.lastDrawerEl = drawer;
|
||||
|
||||
if (!drawer) return;
|
||||
|
||||
// Reason: Check if this view itself is inside the active tab content, rather than
|
||||
// querying by data-type which is more brittle across Obsidian versions.
|
||||
const isCopilotActive = !!this.containerEl.closest(".workspace-drawer-active-tab-content");
|
||||
const kbHeight = parseFloat(
|
||||
document.documentElement.style.getPropertyValue("--keyboard-height") || "0"
|
||||
);
|
||||
drawer.classList.toggle("copilot-keyboard-open", isCopilotActive && kbHeight > 0);
|
||||
};
|
||||
|
||||
this.keyboardObserver = new MutationObserver(syncKeyboardClass);
|
||||
this.keyboardObserver.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["style"],
|
||||
});
|
||||
|
||||
// Reason: Sync initial state in case keyboard is already open when view opens
|
||||
syncKeyboardClass();
|
||||
}
|
||||
|
||||
private renderView(
|
||||
|
|
@ -104,6 +152,14 @@ export default class CopilotView extends ItemView {
|
|||
}
|
||||
|
||||
async onClose(): Promise<void> {
|
||||
this.keyboardObserver?.disconnect();
|
||||
this.keyboardObserver = null;
|
||||
// Reason: Clean up the class on the tracked drawer element when the view is closed.
|
||||
// Use lastDrawerEl instead of querying closest(), because the view may have already
|
||||
// been detached from the drawer DOM by the time onClose fires.
|
||||
this.lastDrawerEl?.classList.remove("copilot-keyboard-open");
|
||||
this.lastDrawerEl = null;
|
||||
|
||||
if (this.root) {
|
||||
this.root.unmount();
|
||||
this.root = null;
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@ import { getSettings, updateSetting } from "@/settings/model";
|
|||
import { Change, diffArrays } from "diff";
|
||||
import { Check, X as XIcon } from "lucide-react";
|
||||
import { App, ItemView, Notice, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import React, { useRef, memo, useMemo } from "react";
|
||||
import React, { memo, useMemo, useRef, useState } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { Button } from "../ui/button";
|
||||
import { SettingSwitch } from "../ui/setting-switch";
|
||||
import { useState } from "react";
|
||||
import { getChangeBlocks } from "@/composerUtils";
|
||||
import { ApplyViewResult } from "@/types";
|
||||
import { ensureFolderExists } from "@/utils";
|
||||
|
|
|
|||
|
|
@ -887,10 +887,10 @@ If your plugin does not need CSS, delete this file.
|
|||
/* use 'important' to prevent ob default css style from being third-party theme overridden */
|
||||
|
||||
.workspace-leaf-content[data-type="copilot-chat-view"] .view-content {
|
||||
padding-bottom: max(var(--safe-area-inset-bottom), var(--size-4-8)) !important;
|
||||
padding-bottom: max(calc(var(--safe-area-inset-bottom, 0px) + var(--size-4-1)), var(--size-4-8)) !important;
|
||||
}
|
||||
|
||||
/* open plugin in Editor mode for mobile */
|
||||
/* Editor mode for mobile - legacy (no bottom navbar): minimal padding */
|
||||
body.is-mobile
|
||||
.workspace-tab-container
|
||||
.workspace-leaf-content[data-type="copilot-chat-view"]
|
||||
|
|
@ -898,6 +898,48 @@ body.is-mobile
|
|||
padding-bottom: var(--size-4-1) !important;
|
||||
}
|
||||
|
||||
/* Editor mode for mobile - new version (with bottom navbar visible): reserve navbar space */
|
||||
@supports selector(body:has(*)) {
|
||||
body.is-mobile:has(.mobile-navbar):not(.is-hidden-nav)
|
||||
.workspace-tab-container
|
||||
.workspace-leaf-content[data-type="copilot-chat-view"]
|
||||
.view-content {
|
||||
padding-bottom: calc(
|
||||
var(--navbar-height, 50px) + max(var(--safe-area-inset-bottom, 0px), var(--size-4-3)) + var(--size-4-1)
|
||||
) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ApplyView for mobile - new version (with bottom navbar visible): reserve navbar space */
|
||||
@supports selector(body:has(*)) {
|
||||
body.is-mobile:has(.mobile-navbar):not(.is-hidden-nav)
|
||||
.workspace-tab-container
|
||||
.workspace-leaf-content[data-type="obsidian-copilot-apply-view"]
|
||||
.view-content {
|
||||
padding-bottom: calc(
|
||||
var(--navbar-height, 50px) + max(var(--safe-area-inset-bottom, 0px), var(--size-4-3)) +
|
||||
var(--size-4-1) + var(--size-4-4)
|
||||
) !important;
|
||||
}
|
||||
|
||||
/* Lift the fixed Accept/Reject button bar above the navbar */
|
||||
body.is-mobile:has(.mobile-navbar):not(.is-hidden-nav)
|
||||
.workspace-tab-container
|
||||
.workspace-leaf-content[data-type="obsidian-copilot-apply-view"]
|
||||
.tw-fixed.tw-bottom-4 {
|
||||
bottom: calc(
|
||||
var(--navbar-height, 50px) + max(var(--safe-area-inset-bottom, 0px), var(--size-4-3)) +
|
||||
var(--size-4-1)
|
||||
) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide drawer header and tab-options when keyboard is open in Copilot's drawer */
|
||||
body.is-mobile .workspace-drawer.copilot-keyboard-open .workspace-drawer-header,
|
||||
body.is-mobile .workspace-drawer.copilot-keyboard-open .workspace-drawer-tab-options {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Collapsible sources styling */
|
||||
.copilot-sources {
|
||||
margin-top: var(--size-4-1);
|
||||
|
|
|
|||
Loading…
Reference in a new issue