mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
tabs wip
This commit is contained in:
parent
63ab82a076
commit
0d11cadc18
@ -149,27 +149,4 @@
|
||||
.chrome-tabs.chrome-tabs-is-sorting .chrome-tab:not(.chrome-tab-is-dragging),
|
||||
.chrome-tabs:not(.chrome-tabs-is-sorting) .chrome-tab.chrome-tab-was-just-dragged {
|
||||
transition: transform 120ms ease-in-out;
|
||||
}
|
||||
.chrome-tabs .chrome-tabs-bottom-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 4px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
z-index: 10;
|
||||
}
|
||||
.chrome-tabs-optional-shadow-below-bottom-bar {
|
||||
position: relative;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='1' height='1' viewBox='0 0 1 1'><rect x='0' y='0' width='1' height='1' fill='rgba(0, 0, 0, .17)'></rect></svg>");
|
||||
background-size: 1px 1px;
|
||||
background-repeat: repeat-x;
|
||||
background-position: 0% 0%;
|
||||
}
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
|
||||
.chrome-tabs-optional-shadow-below-bottom-bar {
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='2' height='2' viewBox='0 0 2 2'><rect x='0' y='0' width='2' height='1' fill='rgba(0, 0, 0, .27)'></rect></svg>");
|
||||
}
|
||||
}
|
||||
}
|
@ -41,12 +41,7 @@
|
||||
|
||||
const tabTemplate = `
|
||||
<div class="chrome-tab">
|
||||
<div class="chrome-tab-dividers"></div>
|
||||
<div class="chrome-tab-background">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"><defs><symbol id="chrome-tab-geometry-left" viewBox="0 0 214 36"><path d="M17 0h197v36H0v-2c4.5 0 9-3.5 9-8V8c0-4.5 3.5-8 8-8z"/></symbol><symbol id="chrome-tab-geometry-right" viewBox="0 0 214 36"><use xlink:href="#chrome-tab-geometry-left"/></symbol><clipPath id="crop"><rect class="mask" width="100%" height="100%" x="0"/></clipPath></defs><svg width="52%" height="100%"><use xlink:href="#chrome-tab-geometry-left" width="214" height="36" class="chrome-tab-geometry"/></svg><g transform="scale(-1, 1)"><svg width="52%" height="100%" x="-100%" y="0"><use xlink:href="#chrome-tab-geometry-right" width="214" height="36" class="chrome-tab-geometry"/></svg></g></svg>
|
||||
</div>
|
||||
<div class="chrome-tab-content">
|
||||
<div class="chrome-tab-favicon"></div>
|
||||
<div class="chrome-tab-title"></div>
|
||||
<div class="chrome-tab-drag-handle"></div>
|
||||
<div class="chrome-tab-close"></div>
|
||||
@ -211,6 +206,8 @@
|
||||
this.cleanUpPreviouslyDraggedTabs()
|
||||
this.layoutTabs()
|
||||
this.setupDraggabilly()
|
||||
|
||||
return tabEl
|
||||
}
|
||||
|
||||
setTabCloseEventListener(tabEl) {
|
||||
@ -251,15 +248,6 @@
|
||||
updateTab(tabEl, tabProperties) {
|
||||
tabEl.querySelector('.chrome-tab-title').textContent = tabProperties.title
|
||||
|
||||
const faviconEl = tabEl.querySelector('.chrome-tab-favicon')
|
||||
if (tabProperties.favicon) {
|
||||
faviconEl.style.backgroundImage = `url('${ tabProperties.favicon }')`
|
||||
faviconEl.removeAttribute('hidden', '')
|
||||
} else {
|
||||
faviconEl.setAttribute('hidden', '')
|
||||
faviconEl.removeAttribute('style')
|
||||
}
|
||||
|
||||
if (tabProperties.id) {
|
||||
tabEl.setAttribute('data-tab-id', tabProperties.id)
|
||||
}
|
||||
|
@ -157,13 +157,3 @@ noteTypeService.init();
|
||||
linkService.init();
|
||||
|
||||
noteAutocompleteService.init();
|
||||
|
||||
$(document).ready(() => {
|
||||
const el = $('.chrome-tabs')[0];
|
||||
const chromeTabs = new ChromeTabs();
|
||||
chromeTabs.init(el);
|
||||
|
||||
el.addEventListener('activeTabChange', ({detail}) => console.log('Active tab changed', detail.tabEl));
|
||||
el.addEventListener('tabAdd', ({detail}) => console.log('Tab added', detail.tabEl));
|
||||
el.addEventListener('tabRemove', ({detail}) => console.log('Tab removed', detail.tabEl));
|
||||
});
|
@ -36,6 +36,9 @@ async function getAttributes() {
|
||||
}
|
||||
|
||||
async function showAttributes() {
|
||||
// FIXME tabs
|
||||
return;
|
||||
|
||||
$promotedAttributesContainer.empty();
|
||||
$attributeList.hide();
|
||||
$attributeListInner.empty();
|
||||
|
@ -106,9 +106,9 @@ function init() {
|
||||
// of opening the link in new window/tab
|
||||
$(document).on('click', "a[data-action='note']", goToLink);
|
||||
$(document).on('click', 'div.popover-content a, div.ui-tooltip-content a', goToLink);
|
||||
$(document).on('dblclick', '#note-detail-text a', goToLink);
|
||||
$(document).on('click', '#note-detail-render a', goToLink);
|
||||
$(document).on('click', '#note-detail-text.ck-read-only a', goToLink);
|
||||
$(document).on('dblclick', '.note-detail-text a', goToLink);
|
||||
$(document).on('click', '.note-detail-render a', goToLink);
|
||||
$(document).on('click', '.note-detail-text.ck-read-only a', goToLink);
|
||||
$(document).on('click', 'span.ck-button__label', e => {
|
||||
// this is a link preview dialog from CKEditor link editing
|
||||
// for some reason clicked element is span
|
||||
|
@ -13,7 +13,15 @@ import noteDetailSearch from "./note_detail_search.js";
|
||||
import noteDetailRender from "./note_detail_render.js";
|
||||
import noteDetailRelationMap from "./note_detail_relation_map.js";
|
||||
|
||||
const $noteTabsContainer = $("#note-tab-container");
|
||||
const $noteTabContentsContainer = $("#note-tab-container");
|
||||
|
||||
const el = $('.chrome-tabs')[0];
|
||||
const chromeTabs = new ChromeTabs();
|
||||
chromeTabs.init(el);
|
||||
|
||||
el.addEventListener('activeTabChange', ({detail}) => console.log('Active tab changed', detail.tabEl));
|
||||
el.addEventListener('tabAdd', ({detail}) => console.log('Tab added', detail.tabEl));
|
||||
el.addEventListener('tabRemove', ({detail}) => console.log('Tab removed', detail.tabEl));
|
||||
|
||||
const componentClasses = {
|
||||
'code': noteDetailCode,
|
||||
@ -26,17 +34,17 @@ const componentClasses = {
|
||||
};
|
||||
|
||||
class NoteContext {
|
||||
constructor(noteId) {
|
||||
constructor(note) {
|
||||
/** @type {NoteFull} */
|
||||
this.note = null;
|
||||
this.noteId = noteId;
|
||||
this.$noteTab = $noteTabsContainer.find(`[data-note-id="${noteId}"]`);
|
||||
this.$noteTitle = this.$noteTab.find(".note-title");
|
||||
this.$noteDetailComponents = this.$noteTab.find(".note-detail-component");
|
||||
this.$protectButton = this.$noteTab.find(".protect-button");
|
||||
this.$unprotectButton = this.$noteTab.find(".unprotect-button");
|
||||
this.$childrenOverview = this.$noteTab.find(".children-overview");
|
||||
this.$scriptArea = this.$noteTab.find(".note-detail-script-area");
|
||||
this.note = note;
|
||||
this.noteId = note.noteId;
|
||||
this.$noteTabContent = $noteTabContentsContainer.find(`[data-note-id="${this.noteId}"]`);
|
||||
this.$noteTitle = this.$noteTabContent.find(".note-title");
|
||||
this.$noteDetailComponents = this.$noteTabContent.find(".note-detail-component");
|
||||
this.$protectButton = this.$noteTabContent.find(".protect-button");
|
||||
this.$unprotectButton = this.$noteTabContent.find(".unprotect-button");
|
||||
this.$childrenOverview = this.$noteTabContent.find(".children-overview");
|
||||
this.$scriptArea = this.$noteTabContent.find(".note-detail-script-area");
|
||||
this.isNoteChanged = false;
|
||||
this.components = {};
|
||||
|
||||
@ -47,6 +55,19 @@ class NoteContext {
|
||||
|
||||
treeService.setNoteTitle(this.noteId, title);
|
||||
});
|
||||
|
||||
this.tab = chromeTabs.addTab({
|
||||
title: note.title,
|
||||
favicon: false
|
||||
});
|
||||
}
|
||||
|
||||
setNote(note) {
|
||||
this.noteId = note.noteId;
|
||||
this.note = note;
|
||||
this.$noteTabContent.attr('data-note-id', note.noteId);
|
||||
|
||||
chromeTabs.updateTab(this.tab, {title: note.title});
|
||||
}
|
||||
|
||||
getComponent(type) {
|
||||
@ -135,20 +156,20 @@ class NoteContext {
|
||||
}
|
||||
|
||||
updateNoteView() {
|
||||
this.$noteTab.toggleClass("protected", this.note.isProtected);
|
||||
this.$noteTabContent.toggleClass("protected", this.note.isProtected);
|
||||
this.$protectButton.toggleClass("active", this.note.isProtected);
|
||||
this.$protectButton.prop("disabled", this.note.isProtected);
|
||||
this.$unprotectButton.toggleClass("active", !this.note.isProtected);
|
||||
this.$unprotectButton.prop("disabled", !this.note.isProtected || !protectedSessionHolder.isProtectedSessionAvailable());
|
||||
|
||||
for (const clazz of Array.from(this.$noteTab[0].classList)) { // create copy to safely iterate over while removing classes
|
||||
for (const clazz of Array.from(this.$noteTabContent[0].classList)) { // create copy to safely iterate over while removing classes
|
||||
if (clazz.startsWith("type-") || clazz.startsWith("mime-")) {
|
||||
this.$noteTab.removeClass(clazz);
|
||||
this.$noteTabContent.removeClass(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
this.$noteTab.addClass(utils.getNoteTypeClass(this.note.type));
|
||||
this.$noteTab.addClass(utils.getMimeTypeClass(this.note.mime));
|
||||
this.$noteTabContent.addClass(utils.getNoteTypeClass(this.note.type));
|
||||
this.$noteTabContent.addClass(utils.getMimeTypeClass(this.note.mime));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ import attributeService from "./attributes.js";
|
||||
import utils from "./utils.js";
|
||||
import importDialog from "../dialogs/import.js";
|
||||
|
||||
const $noteTabsContainer = $("#note-tab-container");
|
||||
const $noteTabContentsContainer = $("#note-tab-container");
|
||||
const $savedIndicator = $("#saved-indicator");
|
||||
|
||||
let noteChangeDisabled = false;
|
||||
@ -43,18 +43,11 @@ async function reload() {
|
||||
await loadNoteDetail(getActiveNoteId());
|
||||
}
|
||||
|
||||
async function openInTab(noteId) {
|
||||
await loadNoteDetail(noteId, true);
|
||||
}
|
||||
|
||||
async function switchToNote(noteId) {
|
||||
if (Object.keys(noteContexts).length === 0) {
|
||||
const tabContent = $("#note-tab-content-template").clone();
|
||||
|
||||
tabContent.removeAttr('id');
|
||||
tabContent.attr('data-note-id', noteId);
|
||||
|
||||
$noteTabsContainer.append(tabContent);
|
||||
|
||||
noteContexts[noteId] = new NoteContext(noteId);
|
||||
}
|
||||
|
||||
//if (getActiveNoteId() !== noteId) {
|
||||
await saveNotesIfChanged();
|
||||
|
||||
@ -71,7 +64,7 @@ function onNoteChange(func) {
|
||||
}
|
||||
|
||||
async function saveNotesIfChanged() {
|
||||
for (const ctx of Object.values(noteContexts)) {
|
||||
for (const ctx of noteContexts) {
|
||||
await ctx.saveNoteIfChanged();
|
||||
}
|
||||
|
||||
@ -93,13 +86,15 @@ async function handleProtectedSession() {
|
||||
return newSessionCreated;
|
||||
}
|
||||
|
||||
/** @type {Object.<string, NoteContext>} */
|
||||
const noteContexts = {};
|
||||
/** @type {NoteContext[]} */
|
||||
const noteContexts = [];
|
||||
|
||||
/** @returns {NoteContext} */
|
||||
function getContext(noteId) {
|
||||
if (noteId in noteContexts) {
|
||||
return noteContexts[noteId];
|
||||
const noteContext = noteContexts.find(nc => nc.noteId === noteId);
|
||||
|
||||
if (noteContext) {
|
||||
return noteContext;
|
||||
}
|
||||
else {
|
||||
throw new Error(`Can't find note context for ${noteId}`);
|
||||
@ -108,21 +103,36 @@ function getContext(noteId) {
|
||||
|
||||
/** @returns {NoteContext} */
|
||||
function getActiveContext() {
|
||||
const currentTreeNode = treeService.getActiveNode();
|
||||
|
||||
return getContext(currentTreeNode.data.noteId);
|
||||
}
|
||||
|
||||
function showTab(noteId) {
|
||||
for (const ctx of Object.values(noteContexts)) {
|
||||
ctx.$noteTab.toggle(ctx.noteId === noteId);
|
||||
for (const ctx of noteContexts) {
|
||||
if (ctx.$noteTabContent.is(":visible")) {
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadNoteDetail(noteId) {
|
||||
const ctx = getContext(noteId);
|
||||
function showTab(noteId) {
|
||||
for (const ctx of noteContexts) {
|
||||
ctx.$noteTabContent.toggle(ctx.noteId === noteId);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadNoteDetail(noteId, newTab = false) {
|
||||
const loadedNote = await loadNote(noteId);
|
||||
|
||||
if (noteContexts.length === 0 || newTab) {
|
||||
const tabContent = $("#note-tab-content-template").clone();
|
||||
|
||||
tabContent.removeAttr('id');
|
||||
tabContent.attr('data-note-id', noteId);
|
||||
|
||||
$noteTabContentsContainer.append(tabContent);
|
||||
|
||||
noteContexts.push(new NoteContext(loadedNote));
|
||||
}
|
||||
|
||||
const ctx = getActiveContext();
|
||||
ctx.setNote(loadedNote);
|
||||
|
||||
// we will try to render the new note only if it's still the active one in the tree
|
||||
// this is useful when user quickly switches notes (by e.g. holding down arrow) so that we don't
|
||||
// try to render all those loaded notes one after each other. This only guarantees that correct note
|
||||
@ -247,11 +257,11 @@ messagingService.subscribeToSyncMessages(syncData => {
|
||||
}
|
||||
});
|
||||
|
||||
$noteTabsContainer.on("dragover", e => e.preventDefault());
|
||||
$noteTabContentsContainer.on("dragover", e => e.preventDefault());
|
||||
|
||||
$noteTabsContainer.on("dragleave", e => e.preventDefault());
|
||||
$noteTabContentsContainer.on("dragleave", e => e.preventDefault());
|
||||
|
||||
$noteTabsContainer.on("drop", e => {
|
||||
$noteTabContentsContainer.on("drop", e => {
|
||||
importDialog.uploadFiles(getActiveNoteId(), e.originalEvent.dataTransfer.files, {
|
||||
safeImport: true,
|
||||
shrinkImages: true,
|
||||
@ -269,6 +279,7 @@ setInterval(saveNotesIfChanged, 3000);
|
||||
|
||||
export default {
|
||||
reload,
|
||||
openInTab,
|
||||
switchToNote,
|
||||
loadNote,
|
||||
getActiveNote,
|
||||
|
@ -3,65 +3,74 @@ import server from "./server.js";
|
||||
import protectedSessionHolder from "./protected_session_holder.js";
|
||||
import noteDetailService from "./note_detail.js";
|
||||
|
||||
const $component = $('#note-detail-file');
|
||||
class NoteDetailFile {
|
||||
/**
|
||||
* @param {NoteContext} ctx
|
||||
*/
|
||||
constructor(ctx) {
|
||||
this.$component = ctx.$noteTabContent.find('.note-detail-file');
|
||||
this.$fileNoteId = ctx.$noteTabContent.find(".file-note-id");
|
||||
this.$fileName = ctx.$noteTabContent.find(".file-filename");
|
||||
this.$fileType = ctx.$noteTabContent.find(".file-filetype");
|
||||
this.$fileSize = ctx.$noteTabContent.find(".file-filesize");
|
||||
this.$previewRow = ctx.$noteTabContent.find(".file-preview-row");
|
||||
this.$previewContent = ctx.$noteTabContent.find(".file-preview-content");
|
||||
this.$downloadButton = ctx.$noteTabContent.find(".file-download");
|
||||
this.$openButton = ctx.$noteTabContent.find(".file-open");
|
||||
|
||||
const $fileNoteId = $("#file-note-id");
|
||||
const $fileName = $("#file-filename");
|
||||
const $fileType = $("#file-filetype");
|
||||
const $fileSize = $("#file-filesize");
|
||||
const $previewRow = $("#file-preview-row");
|
||||
const $previewContent = $("#file-preview-content");
|
||||
const $downloadButton = $("#file-download");
|
||||
const $openButton = $("#file-open");
|
||||
this.$downloadButton.click(() => utils.download(this.getFileUrl()));
|
||||
|
||||
async function show() {
|
||||
const activeNote = noteDetailService.getActiveNote();
|
||||
this.$openButton.click(() => {
|
||||
if (utils.isElectron()) {
|
||||
const open = require("open");
|
||||
|
||||
const attributes = await server.get('notes/' + activeNote.noteId + '/attributes');
|
||||
const attributeMap = utils.toObject(attributes, l => [l.name, l.value]);
|
||||
|
||||
$component.show();
|
||||
|
||||
$fileNoteId.text(activeNote.noteId);
|
||||
$fileName.text(attributeMap.originalFileName || "?");
|
||||
$fileSize.text((attributeMap.fileSize || "?") + " bytes");
|
||||
$fileType.text(activeNote.mime);
|
||||
|
||||
if (activeNote.content) {
|
||||
$previewRow.show();
|
||||
$previewContent.text(activeNote.content);
|
||||
}
|
||||
else {
|
||||
$previewRow.hide();
|
||||
open(this.getFileUrl());
|
||||
}
|
||||
else {
|
||||
window.location.href = this.getFileUrl();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// open doesn't work for protected notes since it works through browser which isn't in protected session
|
||||
$openButton.toggle(!activeNote.isProtected);
|
||||
async show() {
|
||||
const activeNote = noteDetailService.getActiveNote();
|
||||
|
||||
const attributes = await server.get('notes/' + activeNote.noteId + '/attributes');
|
||||
const attributeMap = utils.toObject(attributes, l => [l.name, l.value]);
|
||||
|
||||
this.$component.show();
|
||||
|
||||
this.$fileNoteId.text(activeNote.noteId);
|
||||
this.$fileName.text(attributeMap.originalFileName || "?");
|
||||
this.$fileSize.text((attributeMap.fileSize || "?") + " bytes");
|
||||
this.$fileType.text(activeNote.mime);
|
||||
|
||||
if (activeNote.content) {
|
||||
this.$previewRow.show();
|
||||
this.$previewContent.text(activeNote.content);
|
||||
}
|
||||
else {
|
||||
this.$previewRow.hide();
|
||||
}
|
||||
|
||||
// open doesn't work for protected notes since it works through browser which isn't in protected session
|
||||
this.$openButton.toggle(!activeNote.isProtected);
|
||||
}
|
||||
|
||||
getFileUrl() {
|
||||
// electron needs absolute URL so we extract current host, port, protocol
|
||||
return utils.getHost() + "/api/notes/" + noteDetailService.getActiveNoteId() + "/download";
|
||||
}
|
||||
|
||||
getContent() {}
|
||||
|
||||
focus() {}
|
||||
|
||||
onNoteChange() {}
|
||||
|
||||
cleanup() {}
|
||||
|
||||
scrollToTop() {}
|
||||
}
|
||||
|
||||
$downloadButton.click(() => utils.download(getFileUrl()));
|
||||
|
||||
$openButton.click(() => {
|
||||
if (utils.isElectron()) {
|
||||
const open = require("open");
|
||||
|
||||
open(getFileUrl());
|
||||
}
|
||||
else {
|
||||
window.location.href = getFileUrl();
|
||||
}
|
||||
});
|
||||
|
||||
function getFileUrl() {
|
||||
// electron needs absolute URL so we extract current host, port, protocol
|
||||
return utils.getHost() + "/api/notes/" + noteDetailService.getActiveNoteId() + "/download";
|
||||
}
|
||||
|
||||
export default {
|
||||
show,
|
||||
getContent: () => null,
|
||||
focus: () => null,
|
||||
onNoteChange: () => null,
|
||||
cleanup: () => null,
|
||||
scrollToTop: () => null
|
||||
}
|
||||
export default NoteDetailFile;
|
@ -1,75 +1,85 @@
|
||||
import utils from "./utils.js";
|
||||
import protectedSessionHolder from "./protected_session_holder.js";
|
||||
import noteDetailService from "./note_detail.js";
|
||||
import infoService from "./info.js";
|
||||
import server from "./server.js";
|
||||
|
||||
const $component = $('#note-detail-image');
|
||||
const $imageWrapper = $('#note-detail-image-wrapper');
|
||||
const $imageView = $('#note-detail-image-view');
|
||||
class NoteDetailImage {
|
||||
/**
|
||||
* @param {NoteContext} ctx
|
||||
*/
|
||||
constructor(ctx) {
|
||||
this.$component = ctx.$noteTabContent.find('.note-detail-image');
|
||||
this.$imageWrapper = ctx.$noteTabContent.find('.note-detail-image-wrapper');
|
||||
this.$imageView = ctx.$noteTabContent.find('.note-detail-image-view');
|
||||
this.$copyToClipboardButton = ctx.$noteTabContent.find(".image-copy-to-clipboard");
|
||||
this.$fileName = ctx.$noteTabContent.find(".image-filename");
|
||||
this.$fileType = ctx.$noteTabContent.find(".image-filetype");
|
||||
this.$fileSize = ctx.$noteTabContent.find(".image-filesize");
|
||||
|
||||
const $imageDownloadButton = $("#image-download");
|
||||
const $copyToClipboardButton = $("#image-copy-to-clipboard");
|
||||
const $fileName = $("#image-filename");
|
||||
const $fileType = $("#image-filetype");
|
||||
const $fileSize = $("#image-filesize");
|
||||
this.$imageDownloadButton = ctx.$noteTabContent.find(".image-download");
|
||||
this.$imageDownloadButton.click(() => utils.download(this.getFileUrl()));
|
||||
|
||||
async function show() {
|
||||
const activeNote = noteDetailService.getActiveNote();
|
||||
this.$copyToClipboardButton.click(() => {
|
||||
this.$imageWrapper.attr('contenteditable','true');
|
||||
|
||||
const attributes = await server.get('notes/' + activeNote.noteId + '/attributes');
|
||||
const attributeMap = utils.toObject(attributes, l => [l.name, l.value]);
|
||||
try {
|
||||
this.selectImage(this.$imageWrapper.get(0));
|
||||
|
||||
$component.show();
|
||||
const success = document.execCommand('copy');
|
||||
|
||||
$fileName.text(attributeMap.originalFileName || "?");
|
||||
$fileSize.text((attributeMap.fileSize || "?") + " bytes");
|
||||
$fileType.text(activeNote.mime);
|
||||
|
||||
$imageView.prop("src", `api/images/${activeNote.noteId}/${activeNote.title}`);
|
||||
}
|
||||
|
||||
$imageDownloadButton.click(() => utils.download(getFileUrl()));
|
||||
|
||||
function selectImage(element) {
|
||||
const selection = window.getSelection();
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(element);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
|
||||
$copyToClipboardButton.click(() => {
|
||||
$imageWrapper.attr('contenteditable','true');
|
||||
|
||||
try {
|
||||
selectImage($imageWrapper.get(0));
|
||||
|
||||
const success = document.execCommand('copy');
|
||||
|
||||
if (success) {
|
||||
infoService.showMessage("Image copied to the clipboard");
|
||||
}
|
||||
else {
|
||||
infoService.showAndLogError("Could not copy the image to clipboard.");
|
||||
}
|
||||
if (success) {
|
||||
infoService.showMessage("Image copied to the clipboard");
|
||||
}
|
||||
else {
|
||||
infoService.showAndLogError("Could not copy the image to clipboard.");
|
||||
}
|
||||
}
|
||||
finally {
|
||||
window.getSelection().removeAllRanges();
|
||||
this.$imageWrapper.removeAttr('contenteditable');
|
||||
}
|
||||
});
|
||||
}
|
||||
finally {
|
||||
window.getSelection().removeAllRanges();
|
||||
$imageWrapper.removeAttr('contenteditable');
|
||||
}
|
||||
});
|
||||
|
||||
function getFileUrl() {
|
||||
// electron needs absolute URL so we extract current host, port, protocol
|
||||
return utils.getHost() + "/api/notes/" + noteDetailService.getActiveNoteId() + "/download";
|
||||
async show() {
|
||||
const activeNote = noteDetailService.getActiveNote();
|
||||
|
||||
const attributes = await server.get('notes/' + activeNote.noteId + '/attributes');
|
||||
const attributeMap = utils.toObject(attributes, l => [l.name, l.value]);
|
||||
|
||||
this.$component.show();
|
||||
|
||||
this.$fileName.text(attributeMap.originalFileName || "?");
|
||||
this.$fileSize.text((attributeMap.fileSize || "?") + " bytes");
|
||||
this.$fileType.text(activeNote.mime);
|
||||
|
||||
this.$imageView.prop("src", `api/images/${activeNote.noteId}/${activeNote.title}`);
|
||||
}
|
||||
|
||||
selectImage(element) {
|
||||
const selection = window.getSelection();
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(element);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
|
||||
getFileUrl() {
|
||||
// electron needs absolute URL so we extract current host, port, protocol
|
||||
return utils.getHost() + "/api/notes/" + noteDetailService.getActiveNoteId() + "/download";
|
||||
}
|
||||
|
||||
getContent() {}
|
||||
|
||||
focus() {}
|
||||
|
||||
onNoteChange() {}
|
||||
|
||||
cleanup() {}
|
||||
|
||||
scrollToTop() {
|
||||
this.$component.scrollTop(0);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
show,
|
||||
getContent: () => null,
|
||||
focus: () => null,
|
||||
onNoteChange: () => null,
|
||||
cleanup: () => null,
|
||||
scrollToTop: () => $component.scrollTop(0)
|
||||
}
|
||||
export default NoteDetailImage
|
@ -9,7 +9,7 @@ class NoteDetailText {
|
||||
*/
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx;
|
||||
this.$component = ctx.$noteTab.find('.note-detail-text');
|
||||
this.$component = ctx.$noteTabContent.find('.note-detail-text');
|
||||
this.textEditor = null;
|
||||
|
||||
this.$component.on("dblclick", "img", e => {
|
||||
@ -53,7 +53,7 @@ class NoteDetailText {
|
||||
|
||||
this.$component.show();
|
||||
|
||||
this.textEditor.setData(this.ctx.note.content);
|
||||
// this.textEditor.setData(this.ctx.note.content);
|
||||
}
|
||||
|
||||
getContent() {
|
||||
|
@ -485,7 +485,7 @@ function initFancyTree(tree) {
|
||||
});
|
||||
|
||||
$tree.on('contextmenu', '.fancytree-node', function(e) {
|
||||
treeContextMenuService.getContextMenuItems(e).then(contextMenuItems => {
|
||||
treeContextMenuService.getContextMenuItems(e).then(([node, contextMenuItems]) => {
|
||||
contextMenuWidget.initContextMenu(e, contextMenuItems, treeContextMenuService.selectContextMenuItem);
|
||||
});
|
||||
|
||||
|
@ -11,6 +11,7 @@ import infoService from "./info.js";
|
||||
import treeCache from "./tree_cache.js";
|
||||
import syncService from "./sync.js";
|
||||
import hoistedNoteService from './hoisted_note.js';
|
||||
import noteDetailService from './note_detail.js';
|
||||
|
||||
let clipboardIds = [];
|
||||
let clipboardMode = null;
|
||||
@ -103,6 +104,7 @@ async function getTopLevelItems(event) {
|
||||
const insertChildNoteEnabled = note.type !== 'search';
|
||||
|
||||
return [
|
||||
{ title: "Open in new tab", cmd: "openInTab", uiIcon: "empty" },
|
||||
{ title: "Insert note after <kbd>Ctrl+O</kbd>", cmd: "insertNoteAfter", uiIcon: "plus",
|
||||
items: insertNoteAfterEnabled ? getNoteTypeItems("insertNoteAfter") : null,
|
||||
enabled: insertNoteAfterEnabled },
|
||||
@ -143,9 +145,7 @@ async function getTopLevelItems(event) {
|
||||
async function getContextMenuItems(event) {
|
||||
const items = await getTopLevelItems(event);
|
||||
|
||||
// Activate node on right-click
|
||||
const node = $.ui.fancytree.getNode(event);
|
||||
node.setActive();
|
||||
|
||||
// right click resets selection to just this node
|
||||
// this is important when e.g. you right click on a note while having different note active
|
||||
@ -153,14 +153,17 @@ async function getContextMenuItems(event) {
|
||||
node.setSelected(true);
|
||||
treeService.clearSelectedNodes();
|
||||
|
||||
return items;
|
||||
return [node, items];
|
||||
}
|
||||
|
||||
async function selectContextMenuItem(event, cmd) {
|
||||
// context menu is always triggered on current node
|
||||
const node = treeService.getActiveNode();
|
||||
|
||||
if (cmd.startsWith("insertNoteAfter")) {
|
||||
if (cmd === 'openInTab') {
|
||||
noteDetailService.openInTab(node.data.noteId);
|
||||
}
|
||||
else if (cmd.startsWith("insertNoteAfter")) {
|
||||
const parentNoteId = node.data.parentNoteId;
|
||||
const isProtected = await treeUtils.getParentProtectedStatus(node);
|
||||
const type = cmd.split("_")[1];
|
||||
|
@ -31,7 +31,6 @@ body {
|
||||
#note-tab-container {
|
||||
grid-area: tab-container;
|
||||
min-height: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#search-box {
|
||||
@ -148,7 +147,7 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
|
||||
border: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
#note-info-table td, #note-info-table th {
|
||||
.note-info-table td, .note-info-table th {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ html, body {
|
||||
padding-left: 35px;
|
||||
}
|
||||
|
||||
#note-title-row {
|
||||
.note-title-row {
|
||||
display: flex;
|
||||
padding-left: 15px;
|
||||
flex-shrink: 0;
|
||||
|
@ -48,7 +48,7 @@ button.close {
|
||||
color: var(--main-text-color) !important;
|
||||
}
|
||||
|
||||
#note-title {
|
||||
.note-title {
|
||||
margin-left: 15px;
|
||||
margin-right: 10px;
|
||||
font-size: 150%;
|
||||
@ -95,7 +95,7 @@ ul.fancytree-container {
|
||||
content: "\e9ba";
|
||||
}
|
||||
|
||||
#note-title[readonly] {
|
||||
.note-title[readonly] {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
@ -103,13 +103,17 @@ ul.fancytree-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#note-tab-content-template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.note-tab-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#note-detail-component-wrapper {
|
||||
.note-detail-component-wrapper {
|
||||
flex-grow: 100;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
@ -125,14 +129,14 @@ ul.fancytree-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#note-detail-text h1 { font-size: 2.0em; }
|
||||
#note-detail-text h2 { font-size: 1.8em; }
|
||||
#note-detail-text h3 { font-size: 1.6em; }
|
||||
#note-detail-text h4 { font-size: 1.4em; }
|
||||
#note-detail-text h5 { font-size: 1.2em; }
|
||||
#note-detail-text h6 { font-size: 1.1em; }
|
||||
.note-detail-text h1 { font-size: 2.0em; }
|
||||
.note-detail-text h2 { font-size: 1.8em; }
|
||||
.note-detail-text h3 { font-size: 1.6em; }
|
||||
.note-detail-text h4 { font-size: 1.4em; }
|
||||
.note-detail-text h5 { font-size: 1.2em; }
|
||||
.note-detail-text h6 { font-size: 1.1em; }
|
||||
|
||||
#note-detail-text {
|
||||
.note-detail-text {
|
||||
border: 0 !important;
|
||||
box-shadow: none !important;
|
||||
/* This is because with empty content height of editor is 0 and it's impossible to click into it */
|
||||
@ -142,7 +146,7 @@ ul.fancytree-container {
|
||||
font-family: var(--detail-text-font-family);
|
||||
}
|
||||
|
||||
#note-detail-text p:first-child, #note-detail-text::before {
|
||||
.note-detail-text p:first-child, .note-detail-text::before {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@ -352,12 +356,12 @@ div.ui-tooltip {
|
||||
color: #aaa !important;
|
||||
}
|
||||
|
||||
#note-detail-code {
|
||||
.note-detail-code {
|
||||
min-height: 200px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#note-detail-render {
|
||||
.note-detail-render {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
@ -376,7 +380,7 @@ div.ui-tooltip {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
#note-type-dropdown {
|
||||
.note-type-dropdown {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
@ -403,7 +407,7 @@ div.ui-tooltip {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#file-table th, #file-table td {
|
||||
.file-table th, .file-table td {
|
||||
padding: 10px;
|
||||
font-size: larger;
|
||||
}
|
||||
@ -464,7 +468,7 @@ div.ui-tooltip {
|
||||
background-color: var(--button-disabled-background-color) !important;
|
||||
}
|
||||
|
||||
#note-path-list a.current {
|
||||
.note-path-list a.current {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@ -473,12 +477,12 @@ button.icon-button {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#note-actions {
|
||||
.note-actions {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#note-actions .dropdown-menu {
|
||||
.note-actions .dropdown-menu {
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
@ -495,7 +499,7 @@ button.icon-button {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#note-detail-promoted-attributes {
|
||||
.note-detail-promoted-attributes {
|
||||
margin: auto;
|
||||
/* setting the display to block since "table" doesn't support scrolling */
|
||||
display: block;
|
||||
@ -505,15 +509,15 @@ button.icon-button {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#note-detail-promoted-attributes td, #note-detail-promoted-attributes th {
|
||||
.note-detail-promoted-attributes td, .note-detail-promoted-attributes th {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#note-detail-image {
|
||||
.note-detail-image {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#note-detail-image-view {
|
||||
.note-detail-image-view {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@ -521,7 +525,7 @@ pre:not(.CodeMirror-line) {
|
||||
color: var(--main-text-color) !important;
|
||||
}
|
||||
|
||||
#file-preview-content {
|
||||
.file-preview-content {
|
||||
background-color: var(--accented-background-color);
|
||||
padding: 15px;
|
||||
max-width: 600px;
|
||||
@ -580,7 +584,7 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#note-detail-render-help {
|
||||
.note-detail-render-help {
|
||||
margin: 50px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
<th>File size:</th>
|
||||
<td class="file-filesize"></td>
|
||||
</tr>
|
||||
<tr id="file-preview-row">
|
||||
<tr class="file-preview-row">
|
||||
<th>Preview:</th>
|
||||
<td>
|
||||
<pre class="file-preview-content"></pre>
|
||||
|
@ -1,20 +1,5 @@
|
||||
<div class="chrome-tabs">
|
||||
<div class="chrome-tabs-content">
|
||||
<div class="chrome-tab">
|
||||
<div class="chrome-tab-content">
|
||||
<div class="chrome-tab-title">Google</div>
|
||||
<div class="chrome-tab-drag-handle"></div>
|
||||
<div class="chrome-tab-close"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chrome-tab" active>
|
||||
<div class="chrome-tab-content">
|
||||
<div class="chrome-tab-title">Facebook</div>
|
||||
<div class="chrome-tab-drag-handle"></div>
|
||||
<div class="chrome-tab-close"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chrome-tabs-content"></div>
|
||||
</div>
|
||||
|
||||
<div id="note-tab-container">
|
||||
|
Loading…
x
Reference in New Issue
Block a user