mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	tabs wip
This commit is contained in:
		| @@ -150,26 +150,3 @@ | |||||||
| .chrome-tabs:not(.chrome-tabs-is-sorting) .chrome-tab.chrome-tab-was-just-dragged { | .chrome-tabs:not(.chrome-tabs-is-sorting) .chrome-tab.chrome-tab-was-just-dragged { | ||||||
|   transition: transform 120ms ease-in-out; |   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 = ` |   const tabTemplate = ` | ||||||
|     <div class="chrome-tab"> |     <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-content"> | ||||||
|         <div class="chrome-tab-favicon"></div> |  | ||||||
|         <div class="chrome-tab-title"></div> |         <div class="chrome-tab-title"></div> | ||||||
|         <div class="chrome-tab-drag-handle"></div> |         <div class="chrome-tab-drag-handle"></div> | ||||||
|         <div class="chrome-tab-close"></div> |         <div class="chrome-tab-close"></div> | ||||||
| @@ -211,6 +206,8 @@ | |||||||
|       this.cleanUpPreviouslyDraggedTabs() |       this.cleanUpPreviouslyDraggedTabs() | ||||||
|       this.layoutTabs() |       this.layoutTabs() | ||||||
|       this.setupDraggabilly() |       this.setupDraggabilly() | ||||||
|  |  | ||||||
|  |       return tabEl | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     setTabCloseEventListener(tabEl) { |     setTabCloseEventListener(tabEl) { | ||||||
| @@ -251,15 +248,6 @@ | |||||||
|     updateTab(tabEl, tabProperties) { |     updateTab(tabEl, tabProperties) { | ||||||
|       tabEl.querySelector('.chrome-tab-title').textContent = tabProperties.title |       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) { |       if (tabProperties.id) { | ||||||
|         tabEl.setAttribute('data-tab-id', tabProperties.id) |         tabEl.setAttribute('data-tab-id', tabProperties.id) | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -157,13 +157,3 @@ noteTypeService.init(); | |||||||
| linkService.init(); | linkService.init(); | ||||||
|  |  | ||||||
| noteAutocompleteService.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() { | async function showAttributes() { | ||||||
|  |     // FIXME tabs | ||||||
|  |     return; | ||||||
|  |  | ||||||
|     $promotedAttributesContainer.empty(); |     $promotedAttributesContainer.empty(); | ||||||
|     $attributeList.hide(); |     $attributeList.hide(); | ||||||
|     $attributeListInner.empty(); |     $attributeListInner.empty(); | ||||||
|   | |||||||
| @@ -106,9 +106,9 @@ function init() { | |||||||
| // of opening the link in new window/tab | // of opening the link in new window/tab | ||||||
| $(document).on('click', "a[data-action='note']", goToLink); | $(document).on('click', "a[data-action='note']", goToLink); | ||||||
| $(document).on('click', 'div.popover-content a, div.ui-tooltip-content a', goToLink); | $(document).on('click', 'div.popover-content a, div.ui-tooltip-content a', goToLink); | ||||||
| $(document).on('dblclick', '#note-detail-text a', goToLink); | $(document).on('dblclick', '.note-detail-text a', goToLink); | ||||||
| $(document).on('click', '#note-detail-render a', goToLink); | $(document).on('click', '.note-detail-render a', goToLink); | ||||||
| $(document).on('click', '#note-detail-text.ck-read-only a', goToLink); | $(document).on('click', '.note-detail-text.ck-read-only a', goToLink); | ||||||
| $(document).on('click', 'span.ck-button__label', e => { | $(document).on('click', 'span.ck-button__label', e => { | ||||||
|     // this is a link preview dialog from CKEditor link editing |     // this is a link preview dialog from CKEditor link editing | ||||||
|     // for some reason clicked element is span |     // 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 noteDetailRender from "./note_detail_render.js"; | ||||||
| import noteDetailRelationMap from "./note_detail_relation_map.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 = { | const componentClasses = { | ||||||
|     'code': noteDetailCode, |     'code': noteDetailCode, | ||||||
| @@ -26,17 +34,17 @@ const componentClasses = { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| class NoteContext { | class NoteContext { | ||||||
|     constructor(noteId) { |     constructor(note) { | ||||||
|         /** @type {NoteFull} */ |         /** @type {NoteFull} */ | ||||||
|         this.note = null; |         this.note = note; | ||||||
|         this.noteId = noteId; |         this.noteId = note.noteId; | ||||||
|         this.$noteTab = $noteTabsContainer.find(`[data-note-id="${noteId}"]`); |         this.$noteTabContent = $noteTabContentsContainer.find(`[data-note-id="${this.noteId}"]`); | ||||||
|         this.$noteTitle = this.$noteTab.find(".note-title"); |         this.$noteTitle = this.$noteTabContent.find(".note-title"); | ||||||
|         this.$noteDetailComponents = this.$noteTab.find(".note-detail-component"); |         this.$noteDetailComponents = this.$noteTabContent.find(".note-detail-component"); | ||||||
|         this.$protectButton = this.$noteTab.find(".protect-button"); |         this.$protectButton = this.$noteTabContent.find(".protect-button"); | ||||||
|         this.$unprotectButton = this.$noteTab.find(".unprotect-button"); |         this.$unprotectButton = this.$noteTabContent.find(".unprotect-button"); | ||||||
|         this.$childrenOverview = this.$noteTab.find(".children-overview"); |         this.$childrenOverview = this.$noteTabContent.find(".children-overview"); | ||||||
|         this.$scriptArea = this.$noteTab.find(".note-detail-script-area"); |         this.$scriptArea = this.$noteTabContent.find(".note-detail-script-area"); | ||||||
|         this.isNoteChanged = false; |         this.isNoteChanged = false; | ||||||
|         this.components = {}; |         this.components = {}; | ||||||
|  |  | ||||||
| @@ -47,6 +55,19 @@ class NoteContext { | |||||||
|  |  | ||||||
|             treeService.setNoteTitle(this.noteId, title); |             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) { |     getComponent(type) { | ||||||
| @@ -135,20 +156,20 @@ class NoteContext { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     updateNoteView() { |     updateNoteView() { | ||||||
|         this.$noteTab.toggleClass("protected", this.note.isProtected); |         this.$noteTabContent.toggleClass("protected", this.note.isProtected); | ||||||
|         this.$protectButton.toggleClass("active", this.note.isProtected); |         this.$protectButton.toggleClass("active", this.note.isProtected); | ||||||
|         this.$protectButton.prop("disabled", this.note.isProtected); |         this.$protectButton.prop("disabled", this.note.isProtected); | ||||||
|         this.$unprotectButton.toggleClass("active", !this.note.isProtected); |         this.$unprotectButton.toggleClass("active", !this.note.isProtected); | ||||||
|         this.$unprotectButton.prop("disabled", !this.note.isProtected || !protectedSessionHolder.isProtectedSessionAvailable()); |         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-")) { |             if (clazz.startsWith("type-") || clazz.startsWith("mime-")) { | ||||||
|                 this.$noteTab.removeClass(clazz); |                 this.$noteTabContent.removeClass(clazz); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this.$noteTab.addClass(utils.getNoteTypeClass(this.note.type)); |         this.$noteTabContent.addClass(utils.getNoteTypeClass(this.note.type)); | ||||||
|         this.$noteTab.addClass(utils.getMimeTypeClass(this.note.mime)); |         this.$noteTabContent.addClass(utils.getMimeTypeClass(this.note.mime)); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ import attributeService from "./attributes.js"; | |||||||
| import utils from "./utils.js"; | import utils from "./utils.js"; | ||||||
| import importDialog from "../dialogs/import.js"; | import importDialog from "../dialogs/import.js"; | ||||||
|  |  | ||||||
| const $noteTabsContainer = $("#note-tab-container"); | const $noteTabContentsContainer = $("#note-tab-container"); | ||||||
| const $savedIndicator = $("#saved-indicator"); | const $savedIndicator = $("#saved-indicator"); | ||||||
|  |  | ||||||
| let noteChangeDisabled = false; | let noteChangeDisabled = false; | ||||||
| @@ -43,18 +43,11 @@ async function reload() { | |||||||
|     await loadNoteDetail(getActiveNoteId()); |     await loadNoteDetail(getActiveNoteId()); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function switchToNote(noteId) { | async function openInTab(noteId) { | ||||||
|     if (Object.keys(noteContexts).length === 0) { |     await loadNoteDetail(noteId, true); | ||||||
|         const tabContent = $("#note-tab-content-template").clone(); |  | ||||||
|  |  | ||||||
|         tabContent.removeAttr('id'); |  | ||||||
|         tabContent.attr('data-note-id', noteId); |  | ||||||
|  |  | ||||||
|         $noteTabsContainer.append(tabContent); |  | ||||||
|  |  | ||||||
|         noteContexts[noteId] = new NoteContext(noteId); |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async function switchToNote(noteId) { | ||||||
|     //if (getActiveNoteId() !== noteId) { |     //if (getActiveNoteId() !== noteId) { | ||||||
|         await saveNotesIfChanged(); |         await saveNotesIfChanged(); | ||||||
|  |  | ||||||
| @@ -71,7 +64,7 @@ function onNoteChange(func) { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function saveNotesIfChanged() { | async function saveNotesIfChanged() { | ||||||
|     for (const ctx of Object.values(noteContexts)) { |     for (const ctx of noteContexts) { | ||||||
|         await ctx.saveNoteIfChanged(); |         await ctx.saveNoteIfChanged(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -93,13 +86,15 @@ async function handleProtectedSession() { | |||||||
|     return newSessionCreated; |     return newSessionCreated; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** @type {Object.<string, NoteContext>} */ | /** @type {NoteContext[]} */ | ||||||
| const noteContexts = {}; | const noteContexts = []; | ||||||
|  |  | ||||||
| /** @returns {NoteContext} */ | /** @returns {NoteContext} */ | ||||||
| function getContext(noteId) { | function getContext(noteId) { | ||||||
|     if (noteId in noteContexts) { |     const noteContext = noteContexts.find(nc => nc.noteId === noteId); | ||||||
|         return noteContexts[noteId]; |  | ||||||
|  |     if (noteContext) { | ||||||
|  |         return noteContext; | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|         throw new Error(`Can't find note context for ${noteId}`); |         throw new Error(`Can't find note context for ${noteId}`); | ||||||
| @@ -108,21 +103,36 @@ function getContext(noteId) { | |||||||
|  |  | ||||||
| /** @returns {NoteContext} */ | /** @returns {NoteContext} */ | ||||||
| function getActiveContext() { | function getActiveContext() { | ||||||
|     const currentTreeNode = treeService.getActiveNode(); |     for (const ctx of noteContexts) { | ||||||
|  |         if (ctx.$noteTabContent.is(":visible")) { | ||||||
|     return getContext(currentTreeNode.data.noteId); |             return ctx; | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function showTab(noteId) { | function showTab(noteId) { | ||||||
|     for (const ctx of Object.values(noteContexts)) { |     for (const ctx of noteContexts) { | ||||||
|         ctx.$noteTab.toggle(ctx.noteId === noteId); |         ctx.$noteTabContent.toggle(ctx.noteId === noteId); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function loadNoteDetail(noteId) { | async function loadNoteDetail(noteId, newTab = false) { | ||||||
|     const ctx = getContext(noteId); |  | ||||||
|     const loadedNote = await loadNote(noteId); |     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 |     // 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 |     // 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 |     // 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, { |     importDialog.uploadFiles(getActiveNoteId(), e.originalEvent.dataTransfer.files, { | ||||||
|         safeImport: true, |         safeImport: true, | ||||||
|         shrinkImages: true, |         shrinkImages: true, | ||||||
| @@ -269,6 +279,7 @@ setInterval(saveNotesIfChanged, 3000); | |||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     reload, |     reload, | ||||||
|  |     openInTab, | ||||||
|     switchToNote, |     switchToNote, | ||||||
|     loadNote, |     loadNote, | ||||||
|     getActiveNote, |     getActiveNote, | ||||||
|   | |||||||
| @@ -3,65 +3,74 @@ import server from "./server.js"; | |||||||
| import protectedSessionHolder from "./protected_session_holder.js"; | import protectedSessionHolder from "./protected_session_holder.js"; | ||||||
| import noteDetailService from "./note_detail.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"); |         this.$downloadButton.click(() => utils.download(this.getFileUrl())); | ||||||
| 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"); |  | ||||||
|  |  | ||||||
| async function show() { |         this.$openButton.click(() => { | ||||||
|  |             if (utils.isElectron()) { | ||||||
|  |                 const open = require("open"); | ||||||
|  |  | ||||||
|  |                 open(this.getFileUrl()); | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 window.location.href = this.getFileUrl(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async show() { | ||||||
|         const activeNote = noteDetailService.getActiveNote(); |         const activeNote = noteDetailService.getActiveNote(); | ||||||
|  |  | ||||||
|         const attributes = await server.get('notes/' + activeNote.noteId + '/attributes'); |         const attributes = await server.get('notes/' + activeNote.noteId + '/attributes'); | ||||||
|         const attributeMap = utils.toObject(attributes, l => [l.name, l.value]); |         const attributeMap = utils.toObject(attributes, l => [l.name, l.value]); | ||||||
|  |  | ||||||
|     $component.show(); |         this.$component.show(); | ||||||
|  |  | ||||||
|     $fileNoteId.text(activeNote.noteId); |         this.$fileNoteId.text(activeNote.noteId); | ||||||
|     $fileName.text(attributeMap.originalFileName || "?"); |         this.$fileName.text(attributeMap.originalFileName || "?"); | ||||||
|     $fileSize.text((attributeMap.fileSize || "?") + " bytes"); |         this.$fileSize.text((attributeMap.fileSize || "?") + " bytes"); | ||||||
|     $fileType.text(activeNote.mime); |         this.$fileType.text(activeNote.mime); | ||||||
|  |  | ||||||
|         if (activeNote.content) { |         if (activeNote.content) { | ||||||
|         $previewRow.show(); |             this.$previewRow.show(); | ||||||
|         $previewContent.text(activeNote.content); |             this.$previewContent.text(activeNote.content); | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|         $previewRow.hide(); |             this.$previewRow.hide(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // open doesn't work for protected notes since it works through browser which isn't in protected session |         // open doesn't work for protected notes since it works through browser which isn't in protected session | ||||||
|     $openButton.toggle(!activeNote.isProtected); |         this.$openButton.toggle(!activeNote.isProtected); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| $downloadButton.click(() => utils.download(getFileUrl())); |     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 |         // electron needs absolute URL so we extract current host, port, protocol | ||||||
|         return utils.getHost() + "/api/notes/" + noteDetailService.getActiveNoteId() + "/download"; |         return utils.getHost() + "/api/notes/" + noteDetailService.getActiveNoteId() + "/download"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| export default { |     getContent() {} | ||||||
|     show, |  | ||||||
|     getContent: () => null, |     focus() {} | ||||||
|     focus: () => null, |  | ||||||
|     onNoteChange: () => null, |     onNoteChange() {} | ||||||
|     cleanup: () => null, |  | ||||||
|     scrollToTop: () => null |     cleanup() {} | ||||||
|  |  | ||||||
|  |     scrollToTop() {} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export default NoteDetailFile; | ||||||
| @@ -1,49 +1,29 @@ | |||||||
| import utils from "./utils.js"; | import utils from "./utils.js"; | ||||||
| import protectedSessionHolder from "./protected_session_holder.js"; |  | ||||||
| import noteDetailService from "./note_detail.js"; | import noteDetailService from "./note_detail.js"; | ||||||
| import infoService from "./info.js"; | import infoService from "./info.js"; | ||||||
| import server from "./server.js"; | import server from "./server.js"; | ||||||
|  |  | ||||||
| const $component = $('#note-detail-image'); | class NoteDetailImage { | ||||||
| const $imageWrapper = $('#note-detail-image-wrapper'); |     /** | ||||||
| const $imageView = $('#note-detail-image-view'); |      * @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"); |         this.$imageDownloadButton = ctx.$noteTabContent.find(".image-download"); | ||||||
| const $copyToClipboardButton = $("#image-copy-to-clipboard"); |         this.$imageDownloadButton.click(() => utils.download(this.getFileUrl())); | ||||||
| const $fileName = $("#image-filename"); |  | ||||||
| const $fileType = $("#image-filetype"); |  | ||||||
| const $fileSize = $("#image-filesize"); |  | ||||||
|  |  | ||||||
| async function show() { |         this.$copyToClipboardButton.click(() => { | ||||||
|     const activeNote = noteDetailService.getActiveNote(); |             this.$imageWrapper.attr('contenteditable','true'); | ||||||
|  |  | ||||||
|     const attributes = await server.get('notes/' + activeNote.noteId + '/attributes'); |  | ||||||
|     const attributeMap = utils.toObject(attributes, l => [l.name, l.value]); |  | ||||||
|  |  | ||||||
|     $component.show(); |  | ||||||
|  |  | ||||||
|     $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 { |             try { | ||||||
|         selectImage($imageWrapper.get(0)); |                 this.selectImage(this.$imageWrapper.get(0)); | ||||||
|  |  | ||||||
|                 const success = document.execCommand('copy'); |                 const success = document.execCommand('copy'); | ||||||
|  |  | ||||||
| @@ -56,20 +36,50 @@ $copyToClipboardButton.click(() => { | |||||||
|             } |             } | ||||||
|             finally { |             finally { | ||||||
|                 window.getSelection().removeAllRanges(); |                 window.getSelection().removeAllRanges(); | ||||||
|         $imageWrapper.removeAttr('contenteditable'); |                 this.$imageWrapper.removeAttr('contenteditable'); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
| function getFileUrl() { |     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 |         // electron needs absolute URL so we extract current host, port, protocol | ||||||
|         return utils.getHost() + "/api/notes/" + noteDetailService.getActiveNoteId() + "/download"; |         return utils.getHost() + "/api/notes/" + noteDetailService.getActiveNoteId() + "/download"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| export default { |     getContent() {} | ||||||
|     show, |  | ||||||
|     getContent: () => null, |     focus() {} | ||||||
|     focus: () => null, |  | ||||||
|     onNoteChange: () => null, |     onNoteChange() {} | ||||||
|     cleanup: () => null, |  | ||||||
|     scrollToTop: () => $component.scrollTop(0) |     cleanup() {} | ||||||
|  |  | ||||||
|  |     scrollToTop() { | ||||||
|  |         this.$component.scrollTop(0); | ||||||
|     } |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default NoteDetailImage | ||||||
| @@ -9,7 +9,7 @@ class NoteDetailText { | |||||||
|      */ |      */ | ||||||
|     constructor(ctx) { |     constructor(ctx) { | ||||||
|         this.ctx = ctx; |         this.ctx = ctx; | ||||||
|         this.$component = ctx.$noteTab.find('.note-detail-text'); |         this.$component = ctx.$noteTabContent.find('.note-detail-text'); | ||||||
|         this.textEditor = null; |         this.textEditor = null; | ||||||
|  |  | ||||||
|         this.$component.on("dblclick", "img", e => { |         this.$component.on("dblclick", "img", e => { | ||||||
| @@ -53,7 +53,7 @@ class NoteDetailText { | |||||||
|  |  | ||||||
|         this.$component.show(); |         this.$component.show(); | ||||||
|  |  | ||||||
|         this.textEditor.setData(this.ctx.note.content); | //        this.textEditor.setData(this.ctx.note.content); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getContent() { |     getContent() { | ||||||
|   | |||||||
| @@ -485,7 +485,7 @@ function initFancyTree(tree) { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     $tree.on('contextmenu', '.fancytree-node', function(e) { |     $tree.on('contextmenu', '.fancytree-node', function(e) { | ||||||
|         treeContextMenuService.getContextMenuItems(e).then(contextMenuItems => { |         treeContextMenuService.getContextMenuItems(e).then(([node, contextMenuItems]) => { | ||||||
|             contextMenuWidget.initContextMenu(e, contextMenuItems, treeContextMenuService.selectContextMenuItem); |             contextMenuWidget.initContextMenu(e, contextMenuItems, treeContextMenuService.selectContextMenuItem); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import infoService from "./info.js"; | |||||||
| import treeCache from "./tree_cache.js"; | import treeCache from "./tree_cache.js"; | ||||||
| import syncService from "./sync.js"; | import syncService from "./sync.js"; | ||||||
| import hoistedNoteService from './hoisted_note.js'; | import hoistedNoteService from './hoisted_note.js'; | ||||||
|  | import noteDetailService from './note_detail.js'; | ||||||
|  |  | ||||||
| let clipboardIds = []; | let clipboardIds = []; | ||||||
| let clipboardMode = null; | let clipboardMode = null; | ||||||
| @@ -103,6 +104,7 @@ async function getTopLevelItems(event) { | |||||||
|     const insertChildNoteEnabled = note.type !== 'search'; |     const insertChildNoteEnabled = note.type !== 'search'; | ||||||
|  |  | ||||||
|     return [ |     return [ | ||||||
|  |         { title: "Open in new tab", cmd: "openInTab", uiIcon: "empty" }, | ||||||
|         { title: "Insert note after <kbd>Ctrl+O</kbd>", cmd: "insertNoteAfter", uiIcon: "plus", |         { title: "Insert note after <kbd>Ctrl+O</kbd>", cmd: "insertNoteAfter", uiIcon: "plus", | ||||||
|             items: insertNoteAfterEnabled ? getNoteTypeItems("insertNoteAfter") : null, |             items: insertNoteAfterEnabled ? getNoteTypeItems("insertNoteAfter") : null, | ||||||
|             enabled: insertNoteAfterEnabled }, |             enabled: insertNoteAfterEnabled }, | ||||||
| @@ -143,9 +145,7 @@ async function getTopLevelItems(event) { | |||||||
| async function getContextMenuItems(event) { | async function getContextMenuItems(event) { | ||||||
|     const items = await getTopLevelItems(event); |     const items = await getTopLevelItems(event); | ||||||
|  |  | ||||||
|     // Activate node on right-click |  | ||||||
|     const node = $.ui.fancytree.getNode(event); |     const node = $.ui.fancytree.getNode(event); | ||||||
|     node.setActive(); |  | ||||||
|  |  | ||||||
|     // right click resets selection to just this node |     // 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 |     // 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); |     node.setSelected(true); | ||||||
|     treeService.clearSelectedNodes(); |     treeService.clearSelectedNodes(); | ||||||
|  |  | ||||||
|     return items; |     return [node, items]; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function selectContextMenuItem(event, cmd) { | async function selectContextMenuItem(event, cmd) { | ||||||
|     // context menu is always triggered on current node |     // context menu is always triggered on current node | ||||||
|     const node = treeService.getActiveNode(); |     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 parentNoteId = node.data.parentNoteId; | ||||||
|         const isProtected = await treeUtils.getParentProtectedStatus(node); |         const isProtected = await treeUtils.getParentProtectedStatus(node); | ||||||
|         const type = cmd.split("_")[1]; |         const type = cmd.split("_")[1]; | ||||||
|   | |||||||
| @@ -31,7 +31,6 @@ body { | |||||||
| #note-tab-container { | #note-tab-container { | ||||||
|     grid-area: tab-container; |     grid-area: tab-container; | ||||||
|     min-height: 0; |     min-height: 0; | ||||||
|     min-height: 0; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #search-box { | #search-box { | ||||||
| @@ -148,7 +147,7 @@ li.dropdown-submenu:hover > ul.dropdown-menu { | |||||||
|     border: 1px solid var(--main-border-color); |     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; |     padding: 15px; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -50,7 +50,7 @@ html, body { | |||||||
|     padding-left: 35px; |     padding-left: 35px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-title-row { | .note-title-row { | ||||||
|     display: flex; |     display: flex; | ||||||
|     padding-left: 15px; |     padding-left: 15px; | ||||||
|     flex-shrink: 0; |     flex-shrink: 0; | ||||||
|   | |||||||
| @@ -48,7 +48,7 @@ button.close { | |||||||
|     color: var(--main-text-color) !important; |     color: var(--main-text-color) !important; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-title { | .note-title { | ||||||
|     margin-left: 15px; |     margin-left: 15px; | ||||||
|     margin-right: 10px; |     margin-right: 10px; | ||||||
|     font-size: 150%; |     font-size: 150%; | ||||||
| @@ -95,7 +95,7 @@ ul.fancytree-container { | |||||||
|     content: "\e9ba"; |     content: "\e9ba"; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-title[readonly] { | .note-title[readonly] { | ||||||
|     background: inherit; |     background: inherit; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -103,13 +103,17 @@ ul.fancytree-container { | |||||||
|     display: none; |     display: none; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #note-tab-content-template { | ||||||
|  |     display: none; | ||||||
|  | } | ||||||
|  |  | ||||||
| .note-tab-content { | .note-tab-content { | ||||||
|     display: flex; |     display: flex; | ||||||
|     flex-direction: column; |     flex-direction: column; | ||||||
|     height: 100%; |     height: 100%; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-detail-component-wrapper { | .note-detail-component-wrapper { | ||||||
|     flex-grow: 100; |     flex-grow: 100; | ||||||
|     position: relative; |     position: relative; | ||||||
|     overflow: auto; |     overflow: auto; | ||||||
| @@ -125,14 +129,14 @@ ul.fancytree-container { | |||||||
|     display: none; |     display: none; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-detail-text h1 { font-size: 2.0em; } | .note-detail-text h1 { font-size: 2.0em; } | ||||||
| #note-detail-text h2 { font-size: 1.8em; } | .note-detail-text h2 { font-size: 1.8em; } | ||||||
| #note-detail-text h3 { font-size: 1.6em; } | .note-detail-text h3 { font-size: 1.6em; } | ||||||
| #note-detail-text h4 { font-size: 1.4em; } | .note-detail-text h4 { font-size: 1.4em; } | ||||||
| #note-detail-text h5 { font-size: 1.2em; } | .note-detail-text h5 { font-size: 1.2em; } | ||||||
| #note-detail-text h6 { font-size: 1.1em; } | .note-detail-text h6 { font-size: 1.1em; } | ||||||
|  |  | ||||||
| #note-detail-text { | .note-detail-text { | ||||||
|     border: 0 !important; |     border: 0 !important; | ||||||
|     box-shadow: none !important; |     box-shadow: none !important; | ||||||
|     /* This is because with empty content height of editor is 0 and it's impossible to click into it */ |     /* 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); |     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; |     margin-top: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -352,12 +356,12 @@ div.ui-tooltip { | |||||||
|     color: #aaa !important; |     color: #aaa !important; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-detail-code { | .note-detail-code { | ||||||
|     min-height: 200px; |     min-height: 200px; | ||||||
|     overflow: auto; |     overflow: auto; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-detail-render { | .note-detail-render { | ||||||
|     min-height: 200px; |     min-height: 200px; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -376,7 +380,7 @@ div.ui-tooltip { | |||||||
|     border-right: none; |     border-right: none; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-type-dropdown { | .note-type-dropdown { | ||||||
|     max-height: 500px; |     max-height: 500px; | ||||||
|     overflow-y: auto; |     overflow-y: auto; | ||||||
|     overflow-x: hidden; |     overflow-x: hidden; | ||||||
| @@ -403,7 +407,7 @@ div.ui-tooltip { | |||||||
|     margin-right: 5px; |     margin-right: 5px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #file-table th, #file-table td { | .file-table th, .file-table td { | ||||||
|     padding: 10px; |     padding: 10px; | ||||||
|     font-size: larger; |     font-size: larger; | ||||||
| } | } | ||||||
| @@ -464,7 +468,7 @@ div.ui-tooltip { | |||||||
|     background-color: var(--button-disabled-background-color) !important; |     background-color: var(--button-disabled-background-color) !important; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-path-list a.current { | .note-path-list a.current { | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -473,12 +477,12 @@ button.icon-button { | |||||||
|     padding: 2px; |     padding: 2px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-actions { | .note-actions { | ||||||
|     margin-left: 10px; |     margin-left: 10px; | ||||||
|     margin-right: 10px; |     margin-right: 10px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-actions .dropdown-menu { | .note-actions .dropdown-menu { | ||||||
|     width: 15em; |     width: 15em; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -495,7 +499,7 @@ button.icon-button { | |||||||
|     padding: 0; |     padding: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-detail-promoted-attributes { | .note-detail-promoted-attributes { | ||||||
|     margin: auto; |     margin: auto; | ||||||
|     /* setting the display to block since "table" doesn't support scrolling */ |     /* setting the display to block since "table" doesn't support scrolling */ | ||||||
|     display: block; |     display: block; | ||||||
| @@ -505,15 +509,15 @@ button.icon-button { | |||||||
|     overflow: auto; |     overflow: auto; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-detail-promoted-attributes td, #note-detail-promoted-attributes th { | .note-detail-promoted-attributes td, .note-detail-promoted-attributes th { | ||||||
|     padding: 5px; |     padding: 5px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-detail-image { | .note-detail-image { | ||||||
|     text-align: center; |     text-align: center; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-detail-image-view { | .note-detail-image-view { | ||||||
|     max-width: 100%; |     max-width: 100%; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -521,7 +525,7 @@ pre:not(.CodeMirror-line) { | |||||||
|     color: var(--main-text-color) !important; |     color: var(--main-text-color) !important; | ||||||
| } | } | ||||||
|  |  | ||||||
| #file-preview-content { | .file-preview-content { | ||||||
|     background-color: var(--accented-background-color); |     background-color: var(--accented-background-color); | ||||||
|     padding: 15px; |     padding: 15px; | ||||||
|     max-width: 600px; |     max-width: 600px; | ||||||
| @@ -580,7 +584,7 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th | |||||||
|     padding: 10px; |     padding: 10px; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-detail-render-help { | .note-detail-render-help { | ||||||
|     margin: 50px; |     margin: 50px; | ||||||
|     padding: 20px; |     padding: 20px; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ | |||||||
|             <th>File size:</th> |             <th>File size:</th> | ||||||
|             <td class="file-filesize"></td> |             <td class="file-filesize"></td> | ||||||
|         </tr> |         </tr> | ||||||
|         <tr id="file-preview-row"> |         <tr class="file-preview-row"> | ||||||
|             <th>Preview:</th> |             <th>Preview:</th> | ||||||
|             <td> |             <td> | ||||||
|                 <pre class="file-preview-content"></pre> |                 <pre class="file-preview-content"></pre> | ||||||
|   | |||||||
| @@ -1,20 +1,5 @@ | |||||||
| <div class="chrome-tabs"> | <div class="chrome-tabs"> | ||||||
|     <div class="chrome-tabs-content"> |     <div class="chrome-tabs-content"></div> | ||||||
|         <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> | </div> | ||||||
|  |  | ||||||
| <div id="note-tab-container"> | <div id="note-tab-container"> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user