start of mobile layout

This commit is contained in:
zadam 2020-03-01 11:04:42 +01:00
parent f64b343d8d
commit 4f744564f2
6 changed files with 199 additions and 233 deletions

View File

@ -1,151 +1,22 @@
import treeService from "./services/tree.js";
import treeCache from "./services/tree_cache.js";
import treeBuilder from "./services/tree_builder.js";
import contextMenuWidget from "./services/context_menu.js";
import branchService from "./services/branches.js";
import utils from "./services/utils.js";
import glob from './services/glob.js';
import macInit from './services/mac_init.js';
import options from "./services/options.js";
import noteContentRenderer from "./services/note_content_renderer.js";
import appContext from "./services/app_context.js";
import noteCreateService from "./services/note_create.js";
import glob from "./services/glob.js";
import FlexContainer from "./widgets/flex_container.js";
import EmptyTypeWidget from "./widgets/type_widgets/empty.js";
import TextTypeWidget from "./widgets/type_widgets/text.js";
import CodeTypeWidget from "./widgets/type_widgets/code.js";
import FileTypeWidget from "./widgets/type_widgets/file.js";
import ImageTypeWidget from "./widgets/type_widgets/image.js";
import SearchTypeWidget from "./widgets/type_widgets/search.js";
import RenderTypeWidget from "./widgets/type_widgets/render.js";
import RelationMapTypeWidget from "./widgets/type_widgets/relation_map.js";
import ProtectedSessionTypeWidget from "./widgets/type_widgets/protected_session.js";
import BookTypeWidget from "./widgets/type_widgets/book.js";
import MobileLayout from "./widgets/mobile_layout.js";
const $leftPane = $("#left-pane");
const $tree = $("#tree");
const $detail = $("#detail");
macInit.init();
function togglePanes() {
if (!$leftPane.is(":visible") || !$detail.is(":visible")) {
$detail.toggleClass("d-none");
$leftPane.toggleClass("d-none");
}
}
function showDetailPane() {
if (!$detail.is(":visible")) {
$detail.removeClass("d-none");
$leftPane.addClass("d-none");
}
}
$detail.on("click", ".close-detail-button",() => {
// no page is opened
document.location.hash = '-';
togglePanes();
});
async function showTree() {
const treeData = await treeBuilder.prepareTree();
$tree.fancytree({
autoScroll: true,
extensions: ["dnd5", "clones"],
source: treeData,
scrollParent: $tree,
minExpandLevel: 2, // root can't be collapsed
click: (event, data) => {
if (data.targetType !== 'expander' && data.node.isActive()) {
// this is important for single column mobile view, otherwise it's not possible to see again previously displayed note
$tree.fancytree('getTree').reactivate(true);
return false;
}
},
activate: async (event, data) => {
const node = data.node;
treeService.clearSelectedNodes();
showDetailPane();
const notePath = await treeService.getNotePath(node);
},
expand: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, true),
collapse: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, false),
init: (event, data) => treeService.treeInitialized(), // don't collapse to short form
dnd5: dragAndDropSetup,
lazyLoad: function(event, data) {
const noteId = data.node.data.noteId;
data.result = treeCache.getNote(noteId).then(note => treeBuilder.prepareBranch(note));
},
clones: {
highlightActiveClones: true
},
// this is done to automatically lazy load all expanded search notes after tree load
loadChildren: (event, data) => {
data.node.visit((subNode) => {
// Load all lazy/unloaded child nodes
// (which will trigger `loadChildren` recursively)
if (subNode.isUndefined() && subNode.isExpanded()) {
subNode.load();
}
});
}
});
treeService.setTree($.ui.fancytree.getTree("#tree"));
}
$detail.on("click", ".note-menu-button", async e => {
// FIXME
const node = appContext.getMainNoteTree().getActiveNode();
const branch = treeCache.getBranch(node.data.branchId);
const note = await treeCache.getNote(node.data.noteId);
const parentNote = await treeCache.getNote(branch.parentNoteId);
const isNotRoot = note.noteId !== 'root';
const items = [
{ title: "Insert note after", cmd: "insertNoteAfter", uiIcon: "plus",
enabled: isNotRoot && parentNote.type !== 'search' },
{ title: "Insert child note", cmd: "insertChildNote", uiIcon: "plus",
enabled: note.type !== 'search' },
{ title: "Delete this note", cmd: "delete", uiIcon: "trash",
enabled: isNotRoot && parentNote.type !== 'search' }
];
contextMenuWidget.initContextMenu(e, {
getContextMenuItems: () => items,
selectContextMenuItem: async (event, cmd) => {
if (cmd === "insertNoteAfter") {
const parentNoteId = node.data.parentNoteId;
const isProtected = await treeService.getParentProtectedStatus(node);
noteCreateService.createNote(parentNoteId, {
isProtected: isProtected,
target: 'after',
targetBranchId: node.data.branchId
});
}
else if (cmd === "insertChildNote") {
noteCreateService.createNote(node.data.noteId);
}
else if (cmd === "delete") {
if (await branchService.deleteNotes([node])) {
// move to the tree
togglePanes();
}
}
else {
throw new Error("Unrecognized command " + cmd);
}
}
});
});
$("#switch-to-desktop-button").on('click', () => {
utils.setCookie('trilium-device', 'desktop');
utils.reloadApp();
});
$("#log-out-button").on('click', () => {
$("#logout-form").trigger('submit');
});
// this is done so that startNotePath is not used
if (!document.location.hash) {
document.location.hash = '-';
}
showTree();
appContext.setLayout(new MobileLayout());
appContext.start();

View File

@ -0,0 +1,151 @@
import treeService from "./services/tree.js";
import treeCache from "./services/tree_cache.js";
import treeBuilder from "./services/tree_builder.js";
import contextMenuWidget from "./services/context_menu.js";
import branchService from "./services/branches.js";
import utils from "./services/utils.js";
import appContext from "./services/app_context.js";
import noteCreateService from "./services/note_create.js";
import glob from "./services/glob.js";
const $leftPane = $("#left-pane");
const $tree = $("#tree");
const $detail = $("#detail");
function togglePanes() {
if (!$leftPane.is(":visible") || !$detail.is(":visible")) {
$detail.toggleClass("d-none");
$leftPane.toggleClass("d-none");
}
}
function showDetailPane() {
if (!$detail.is(":visible")) {
$detail.removeClass("d-none");
$leftPane.addClass("d-none");
}
}
$detail.on("click", ".close-detail-button",() => {
// no page is opened
document.location.hash = '-';
togglePanes();
});
async function showTree() {
const treeData = await treeBuilder.prepareTree();
$tree.fancytree({
autoScroll: true,
extensions: ["dnd5", "clones"],
source: treeData,
scrollParent: $tree,
minExpandLevel: 2, // root can't be collapsed
click: (event, data) => {
if (data.targetType !== 'expander' && data.node.isActive()) {
// this is important for single column mobile view, otherwise it's not possible to see again previously displayed note
$tree.fancytree('getTree').reactivate(true);
return false;
}
},
activate: async (event, data) => {
const node = data.node;
treeService.clearSelectedNodes();
showDetailPane();
const notePath = await treeService.getNotePath(node);
},
expand: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, true),
collapse: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, false),
init: (event, data) => treeService.treeInitialized(), // don't collapse to short form
dnd5: dragAndDropSetup,
lazyLoad: function(event, data) {
const noteId = data.node.data.noteId;
data.result = treeCache.getNote(noteId).then(note => treeBuilder.prepareBranch(note));
},
clones: {
highlightActiveClones: true
},
// this is done to automatically lazy load all expanded search notes after tree load
loadChildren: (event, data) => {
data.node.visit((subNode) => {
// Load all lazy/unloaded child nodes
// (which will trigger `loadChildren` recursively)
if (subNode.isUndefined() && subNode.isExpanded()) {
subNode.load();
}
});
}
});
treeService.setTree($.ui.fancytree.getTree("#tree"));
}
$detail.on("click", ".note-menu-button", async e => {
// FIXME
const node = appContext.getMainNoteTree().getActiveNode();
const branch = treeCache.getBranch(node.data.branchId);
const note = await treeCache.getNote(node.data.noteId);
const parentNote = await treeCache.getNote(branch.parentNoteId);
const isNotRoot = note.noteId !== 'root';
const items = [
{ title: "Insert note after", cmd: "insertNoteAfter", uiIcon: "plus",
enabled: isNotRoot && parentNote.type !== 'search' },
{ title: "Insert child note", cmd: "insertChildNote", uiIcon: "plus",
enabled: note.type !== 'search' },
{ title: "Delete this note", cmd: "delete", uiIcon: "trash",
enabled: isNotRoot && parentNote.type !== 'search' }
];
contextMenuWidget.initContextMenu(e, {
getContextMenuItems: () => items,
selectContextMenuItem: async (event, cmd) => {
if (cmd === "insertNoteAfter") {
const parentNoteId = node.data.parentNoteId;
const isProtected = await treeService.getParentProtectedStatus(node);
noteCreateService.createNote(parentNoteId, {
isProtected: isProtected,
target: 'after',
targetBranchId: node.data.branchId
});
}
else if (cmd === "insertChildNote") {
noteCreateService.createNote(node.data.noteId);
}
else if (cmd === "delete") {
if (await branchService.deleteNotes([node])) {
// move to the tree
togglePanes();
}
}
else {
throw new Error("Unrecognized command " + cmd);
}
}
});
});
$("#switch-to-desktop-button").on('click', () => {
utils.setCookie('trilium-device', 'desktop');
utils.reloadApp();
});
$("#log-out-button").on('click', () => {
$("#logout-form").trigger('submit');
});
// this is done so that startNotePath is not used
if (!document.location.hash) {
document.location.hash = '-';
}
showTree();

View File

@ -12,10 +12,12 @@ export default class Entrypoints extends Component {
constructor() {
super();
// hot keys are active also inside inputs and content editables
jQuery.hotkeys.options.filterInputAcceptingElements = false;
jQuery.hotkeys.options.filterContentEditable = false;
jQuery.hotkeys.options.filterTextInputs = false;
if (jQuery.hotkeys) {
// hot keys are active also inside inputs and content editables
jQuery.hotkeys.options.filterInputAcceptingElements = false;
jQuery.hotkeys.options.filterContentEditable = false;
jQuery.hotkeys.options.filterTextInputs = false;
}
$(document).on('click', "a[data-action='note-revision']", async event => {
const linkEl = $(event.target);

View File

@ -0,0 +1,19 @@
import FlexContainer from "./flex_container.js";
import NoteTitleWidget from "./note_title.js";
import NoteDetailWidget from "./note_detail.js";
import NoteTreeWidget from "./note_tree.js";
export default class MobileLayout {
getRootWidget(appContext) {
return new FlexContainer('row')
.setParent(appContext)
.id('root-widget')
.css('height', '100vh')
.child(new FlexContainer('column')
// .child(/* buttons */)
.child(new NoteTreeWidget()))
.child(new FlexContainer('column')
.child(new NoteTitleWidget())
.child(new NoteDetailWidget()));
}
}

View File

@ -66,7 +66,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
this.$widget.fancytree({
autoScroll: true,
keyboard: false, // we takover keyboard handling in the hotkeys plugin
extensions: ["hotkeys", "dnd5", "clones"],
extensions: utils.isMobile() ? ["clones"] : ["hotkeys", "dnd5", "clones"],
source: treeData,
scrollParent: this.$widget,
minExpandLevel: 2, // root can't be collapsed

View File

@ -7,87 +7,12 @@
<title>Trilium Notes</title>
<link rel="apple-touch-icon" sizes="180x180" href="images/app-icons/ios/apple-touch-icon.png">
</head>
<body class="mobile">
<body class="mobile" style="display: none;">
<noscript>Trilium requires JavaScript to be enabled.</noscript>
<div id="toast-container" class="d-flex flex-column justify-content-center align-items-center"></div>
<div class="row" id="container-row" style="display: none;">
<div id="left-pane" class="d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-5 col-md-4 col-lg-4 col-xl-4">
<div id="global-buttons">
<a id="create-top-level-note-button" title="Create new top level note" class="icon-action bx bx-folder-plus"></a>
<a id="collapse-tree-button" title="Collapse note tree" class="icon-action bx bx-layer-minus"></a>
<a id="scroll-to-active-note-button" title="Scroll to active note" class="icon-action bx bx-crosshair"></a>
<div class="dropdown">
<a id="global-actions-button" title="Global actions" class="icon-action bx bx-cog dropdown-toggle" data-toggle="dropdown"></a>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" id="switch-to-desktop-button"><span class="bx bx-laptop"></span> Switch to desktop version</a>
<a class="dropdown-item" id="log-out-button"><span class="bx bx-log-out"></span> Logout</a>
</div>
</div>
</div>
<div id="tree"></div>
</div>
<div id="detail" class="d-none d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-7 col-md-8 col-lg-8">
<div class="note-tab-row">
<div class="note-tab-row-content"></div>
</div>
<div id="note-tab-container">
<div class="note-tab-content note-tab-content-template">
<div class="note-detail-content" style="width: 100%">
<div class="note-title-row">
<button type="button" class="note-menu-button action-button bx bx-menu"></button>
<input autocomplete="off" value="" class="form-control note-title" tabindex="1">
<button type="button" class="close-detail-button action-button d-sm-none d-md-none d-lg-none d-xl-none" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="note-detail-printable-wrapper">
<div class="note-detail-text note-detail-printable" tabindex="10000">
<div class="note-detail-text-editor"></div>
</div>
<div class="note-detail-code note-detail-printable">
<div class="note-detail-code-editor"></div>
</div>
<% include details/search.ejs %>
<% include details/render.ejs %>
<% include details/image.ejs %>
<% include details/relation_map.ejs %>
<% include details/protected_session_password.ejs %>
<% include details/book.ejs %>
</div>
</div>
</div>
</div>
</div>
<% include dialogs/protected_session_password.ejs %>
<% include dialogs/confirm.ejs %>
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container"></div>
<form action="logout" id="logout-form" method="POST" style="display: none;">
<input type="hidden" name="_csrf" value="<%= csrfToken %>"/>
</form>
</div>
<script type="text/javascript">
window.baseApiUrl = 'api/';
@ -121,10 +46,8 @@
<link rel="stylesheet" type="text/css" href="libraries/boxicons/css/boxicons.min.css">
<script type="text/javascript">
// we hide container initally because otherwise it is rendered first without CSS and then flickers into
// final form which is pretty ugly.
$("#container-row").show();
<script>
$("body").show();
</script>
</body>
</html>