mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-09-29 00:11:32 +08:00
refactor(components): split editor toolbar for mobile
This commit is contained in:
parent
c422c3e5b9
commit
fbba76bbb3
@ -22,7 +22,6 @@ import LauncherContainer from "../widgets/containers/launcher_container.js";
|
|||||||
import RootContainer from "../widgets/containers/root_container.js";
|
import RootContainer from "../widgets/containers/root_container.js";
|
||||||
import SharedInfoWidget from "../widgets/shared_info.js";
|
import SharedInfoWidget from "../widgets/shared_info.js";
|
||||||
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
|
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
|
||||||
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
|
|
||||||
import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js";
|
import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js";
|
||||||
import AboutDialog from "../widgets/dialogs/about.js";
|
import AboutDialog from "../widgets/dialogs/about.js";
|
||||||
import HelpDialog from "../widgets/dialogs/help.js";
|
import HelpDialog from "../widgets/dialogs/help.js";
|
||||||
@ -32,6 +31,7 @@ import JumpToNoteDialog from "../widgets/dialogs/jump_to_note.js";
|
|||||||
import RecentChangesDialog from "../widgets/dialogs/recent_changes.js";
|
import RecentChangesDialog from "../widgets/dialogs/recent_changes.js";
|
||||||
import PromptDialog from "../widgets/dialogs/prompt.js";
|
import PromptDialog from "../widgets/dialogs/prompt.js";
|
||||||
import RefreshButton from "../widgets/floating_buttons/refresh_button.js";
|
import RefreshButton from "../widgets/floating_buttons/refresh_button.js";
|
||||||
|
import MobileEditorToolbar from "../widgets/ribbon_widgets/mobile_editor_toolbar.js";
|
||||||
|
|
||||||
const MOBILE_CSS = `
|
const MOBILE_CSS = `
|
||||||
<style>
|
<style>
|
||||||
@ -182,7 +182,7 @@ export default class MobileLayout {
|
|||||||
.child(new TabRowWidget().css("height", "40px"))
|
.child(new TabRowWidget().css("height", "40px"))
|
||||||
.child(new FlexContainer("row").class("horizontal").css("height", "53px").child(new LauncherContainer(true)).child(new GlobalMenuWidget(true)).id("launcher-pane"))
|
.child(new FlexContainer("row").class("horizontal").css("height", "53px").child(new LauncherContainer(true)).child(new GlobalMenuWidget(true)).id("launcher-pane"))
|
||||||
)
|
)
|
||||||
.child(new ClassicEditorToolbar())
|
.child(new MobileEditorToolbar())
|
||||||
.child(new AboutDialog())
|
.child(new AboutDialog())
|
||||||
.child(new HelpDialog())
|
.child(new HelpDialog())
|
||||||
.child(new RecentChangesDialog())
|
.child(new RecentChangesDialog())
|
||||||
|
@ -21,53 +21,6 @@ const TPL = /*html*/`\
|
|||||||
.classic-toolbar-widget .ck.ck-button.ck-disabled {
|
.classic-toolbar-widget .ck.ck-button.ck-disabled {
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.mobile .classic-toolbar-widget {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.mobile .classic-toolbar-widget.visible {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
overflow-x: auto;
|
|
||||||
overscroll-behavior: none;
|
|
||||||
z-index: 500;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.mobile .classic-toolbar-widget.visible::-webkit-scrollbar {
|
|
||||||
height: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 991px) {
|
|
||||||
body.mobile .classic-toolbar-widget.visible {
|
|
||||||
bottom: calc(var(--tab-bar-height) + var(--launcher-pane-height) + var(--mobile-bottom-offset));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 991px) {
|
|
||||||
body.mobile .classic-toolbar-widget.visible {
|
|
||||||
bottom: 0;
|
|
||||||
left: 25%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body.mobile .classic-toolbar-widget.dropdown-active {
|
|
||||||
height: 50vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.mobile .classic-toolbar-widget .ck.ck-toolbar {
|
|
||||||
position: absolute;
|
|
||||||
background-color: var(--main-background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
body.mobile .classic-toolbar-widget .ck.ck-dropdown__panel {
|
|
||||||
bottom: 100% !important;
|
|
||||||
top: unset !important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -82,13 +35,6 @@ const TPL = /*html*/`\
|
|||||||
*/
|
*/
|
||||||
export default class ClassicEditorToolbar extends NoteContextAwareWidget {
|
export default class ClassicEditorToolbar extends NoteContextAwareWidget {
|
||||||
|
|
||||||
private observer: MutationObserver;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.observer = new MutationObserver((e) => this.#onDropdownStateChanged(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
return "classicEditor";
|
return "classicEditor";
|
||||||
}
|
}
|
||||||
@ -100,33 +46,6 @@ export default class ClassicEditorToolbar extends NoteContextAwareWidget {
|
|||||||
doRender() {
|
doRender() {
|
||||||
this.$widget = $(TPL);
|
this.$widget = $(TPL);
|
||||||
this.contentSized();
|
this.contentSized();
|
||||||
|
|
||||||
if (utils.isMobile()) {
|
|
||||||
// The virtual keyboard obscures the editing toolbar so we have to reposition by calculating the height of the keyboard.
|
|
||||||
window.visualViewport?.addEventListener("resize", () => this.#adjustPosition());
|
|
||||||
window.addEventListener("scroll", () => this.#adjustPosition());
|
|
||||||
|
|
||||||
// Observe when a dropdown is expanded to apply a style that allows the dropdown to be visible, since we can't have the element both visible and the toolbar scrollable.
|
|
||||||
this.observer.disconnect();
|
|
||||||
this.observer.observe(this.$widget[0], {
|
|
||||||
attributeFilter: ["aria-expanded"],
|
|
||||||
subtree: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#onDropdownStateChanged(e: MutationRecord[]) {
|
|
||||||
const dropdownActive = e.map((e) => (e.target as any).ariaExpanded === "true").reduce((acc, e) => acc && e);
|
|
||||||
this.$widget[0].classList.toggle("dropdown-active", dropdownActive);
|
|
||||||
}
|
|
||||||
|
|
||||||
#adjustPosition() {
|
|
||||||
let bottom = window.innerHeight - (window.visualViewport?.height || 0);
|
|
||||||
|
|
||||||
// When the keyboard is not visible, align it to the launcher bar instead.
|
|
||||||
bottom = Math.max(bottom, document.getElementById("mobile-bottom-bar")?.offsetHeight || 0);
|
|
||||||
|
|
||||||
this.$widget.css("bottom", `${bottom}px`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTitle() {
|
async getTitle() {
|
||||||
@ -139,7 +58,7 @@ export default class ClassicEditorToolbar extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async #shouldDisplay() {
|
async #shouldDisplay() {
|
||||||
if (utils.isDesktop() && options.get("textNoteEditorType") !== "ckeditor-classic") {
|
if (options.get("textNoteEditorType") !== "ckeditor-classic") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,10 +73,4 @@ export default class ClassicEditorToolbar extends NoteContextAwareWidget {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshWithNote() {
|
|
||||||
if (utils.isMobile()) {
|
|
||||||
this.toggleExt(await this.#shouldDisplay());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
139
src/public/app/widgets/ribbon_widgets/mobile_editor_toolbar.ts
Normal file
139
src/public/app/widgets/ribbon_widgets/mobile_editor_toolbar.ts
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||||
|
|
||||||
|
const TPL = /*html*/`\
|
||||||
|
<div class="classic-toolbar-widget"></div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.classic-toolbar-widget {
|
||||||
|
--ck-color-toolbar-background: transparent;
|
||||||
|
--ck-color-button-default-background: transparent;
|
||||||
|
--ck-color-button-default-disabled-background: transparent;
|
||||||
|
min-height: 39px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.classic-toolbar-widget .ck.ck-toolbar {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.classic-toolbar-widget .ck.ck-button.ck-disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile .classic-toolbar-widget {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile .classic-toolbar-widget.visible {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
overscroll-behavior: none;
|
||||||
|
z-index: 500;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile .classic-toolbar-widget.visible::-webkit-scrollbar {
|
||||||
|
height: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
body.mobile .classic-toolbar-widget.visible {
|
||||||
|
bottom: calc(var(--tab-bar-height) + var(--launcher-pane-height) + var(--mobile-bottom-offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 991px) {
|
||||||
|
body.mobile .classic-toolbar-widget.visible {
|
||||||
|
bottom: 0;
|
||||||
|
left: 25%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile .classic-toolbar-widget.dropdown-active {
|
||||||
|
height: 50vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile .classic-toolbar-widget .ck.ck-toolbar {
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--main-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile .classic-toolbar-widget .ck.ck-dropdown__panel {
|
||||||
|
bottom: 100% !important;
|
||||||
|
top: unset !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the editing toolbar when the CKEditor is in decoupled mode.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This toolbar is only enabled if the user has selected the classic CKEditor.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The ribbon item is active by default for text notes, as long as they are not in read-only mode.
|
||||||
|
*/
|
||||||
|
export default class MobileEditorToolbar extends NoteContextAwareWidget {
|
||||||
|
|
||||||
|
private observer: MutationObserver;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.observer = new MutationObserver((e) => this.#onDropdownStateChanged(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return "classicEditor";
|
||||||
|
}
|
||||||
|
|
||||||
|
doRender() {
|
||||||
|
this.$widget = $(TPL);
|
||||||
|
this.contentSized();
|
||||||
|
|
||||||
|
// The virtual keyboard obscures the editing toolbar so we have to reposition by calculating the height of the keyboard.
|
||||||
|
window.visualViewport?.addEventListener("resize", () => this.#adjustPosition());
|
||||||
|
window.addEventListener("scroll", () => this.#adjustPosition());
|
||||||
|
|
||||||
|
// Observe when a dropdown is expanded to apply a style that allows the dropdown to be visible, since we can't have the element both visible and the toolbar scrollable.
|
||||||
|
this.observer.disconnect();
|
||||||
|
this.observer.observe(this.$widget[0], {
|
||||||
|
attributeFilter: ["aria-expanded"],
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#onDropdownStateChanged(e: MutationRecord[]) {
|
||||||
|
const dropdownActive = e.map((e) => (e.target as any).ariaExpanded === "true").reduce((acc, e) => acc && e);
|
||||||
|
this.$widget[0].classList.toggle("dropdown-active", dropdownActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
#adjustPosition() {
|
||||||
|
let bottom = window.innerHeight - (window.visualViewport?.height || 0);
|
||||||
|
|
||||||
|
// When the keyboard is not visible, align it to the launcher bar instead.
|
||||||
|
bottom = Math.max(bottom, document.getElementById("mobile-bottom-bar")?.offsetHeight || 0);
|
||||||
|
|
||||||
|
this.$widget.css("bottom", `${bottom}px`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async #shouldDisplay() {
|
||||||
|
if (!this.note || this.note.type !== "text") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await this.noteContext?.isReadOnly()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshWithNote() {
|
||||||
|
this.toggleExt(await this.#shouldDisplay());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user