mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	note tooltip displays the whole note with scrollbar, other behavior changes. closes #4120
This commit is contained in:
		| @@ -182,8 +182,6 @@ export default class Entrypoints extends Component { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     hideAllPopups() { |     hideAllPopups() { | ||||||
|         $(".tooltip").removeClass("show"); |  | ||||||
|  |  | ||||||
|         if (utils.isDesktop()) { |         if (utils.isDesktop()) { | ||||||
|             $(".aa-input").autocomplete("close"); |             $(".aa-input").autocomplete("close"); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| class FAttachment { | class FAttachment { | ||||||
|     constructor(froca, row) { |     constructor(froca, row) { | ||||||
|  |         /** @type {Froca} */ | ||||||
|         this.froca = froca; |         this.froca = froca; | ||||||
|  |  | ||||||
|         this.update(row); |         this.update(row); | ||||||
| @@ -34,12 +35,9 @@ class FAttachment { | |||||||
|         return this.froca.notes[this.ownerId]; |         return this.froca.notes[this.ownerId]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** @return {FBlob} */ | ||||||
|      * @param [opts.preview=false] - retrieve only first 10 000 characters for a preview |     async getBlob() { | ||||||
|      * @return {FBlob} |         return await this.froca.getBlob('attachments', this.attachmentId); | ||||||
|      */ |  | ||||||
|     async getBlob(opts = {}) { |  | ||||||
|         return await this.froca.getBlob('attachments', this.attachmentId, opts); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import promotedAttributeDefinitionParser from '../services/promoted_attribute_de | |||||||
|  */ |  */ | ||||||
| class FAttribute { | class FAttribute { | ||||||
|     constructor(froca, row) { |     constructor(froca, row) { | ||||||
|  |         /** @type {Froca} */ | ||||||
|         this.froca = froca; |         this.froca = froca; | ||||||
|  |  | ||||||
|         this.update(row); |         this.update(row); | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ | |||||||
|  */ |  */ | ||||||
| class FBranch { | class FBranch { | ||||||
|     constructor(froca, row) { |     constructor(froca, row) { | ||||||
|  |         /** @type {Froca} */ | ||||||
|         this.froca = froca; |         this.froca = froca; | ||||||
|  |  | ||||||
|         this.update(row); |         this.update(row); | ||||||
|   | |||||||
| @@ -31,6 +31,7 @@ class FNote { | |||||||
|      * @param {Object.<string, Object>} row |      * @param {Object.<string, Object>} row | ||||||
|      */ |      */ | ||||||
|     constructor(froca, row) { |     constructor(froca, row) { | ||||||
|  |         /** @type {Froca} */ | ||||||
|         this.froca = froca; |         this.froca = froca; | ||||||
|  |  | ||||||
|         /** @type {string[]} */ |         /** @type {string[]} */ | ||||||
| @@ -859,12 +860,9 @@ class FNote { | |||||||
|         return this.getBlob(); |         return this.getBlob(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** @return {Promise<FBlob>} */ | ||||||
|      * @param [opts.preview=false] - retrieve only first 10 000 characters for a preview |     async getBlob() { | ||||||
|      * @return {Promise<FBlob>} |         return await this.froca.getBlob('notes', this.noteId); | ||||||
|      */ |  | ||||||
|     async getBlob(opts = {}) { |  | ||||||
|         return await this.froca.getBlob('notes', this.noteId, opts); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     toString() { |     toString() { | ||||||
|   | |||||||
| @@ -19,7 +19,6 @@ let idCounter = 1; | |||||||
|  */ |  */ | ||||||
| async function getRenderedContent(entity, options = {}) { | async function getRenderedContent(entity, options = {}) { | ||||||
|     options = Object.assign({ |     options = Object.assign({ | ||||||
|         trim: false, |  | ||||||
|         tooltip: false |         tooltip: false | ||||||
|     }, options); |     }, options); | ||||||
|  |  | ||||||
| @@ -29,7 +28,7 @@ async function getRenderedContent(entity, options = {}) { | |||||||
|     const $renderedContent = $('<div class="rendered-content">'); |     const $renderedContent = $('<div class="rendered-content">'); | ||||||
|  |  | ||||||
|     if (type === 'text') { |     if (type === 'text') { | ||||||
|         await renderText(entity, options, $renderedContent); |         await renderText(entity, $renderedContent); | ||||||
|     } |     } | ||||||
|     else if (type === 'code') { |     else if (type === 'code') { | ||||||
|         await renderCode(entity, options, $renderedContent); |         await renderCode(entity, options, $renderedContent); | ||||||
| @@ -86,12 +85,13 @@ async function getRenderedContent(entity, options = {}) { | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function renderText(note, options, $renderedContent) { | /** @param {FNote} note */ | ||||||
|  | async function renderText(note, $renderedContent) { | ||||||
|     // entity must be FNote |     // entity must be FNote | ||||||
|     const blob = await note.getBlob({preview: options.trim}); |     const blob = await note.getBlob(); | ||||||
|  |  | ||||||
|     if (!utils.isHtmlEmpty(blob.content)) { |     if (!utils.isHtmlEmpty(blob.content)) { | ||||||
|         $renderedContent.append($('<div class="ck-content">').html(trim(blob.content, options.trim))); |         $renderedContent.append($('<div class="ck-content">').html(blob.content)); | ||||||
|  |  | ||||||
|         if ($renderedContent.find('span.math-tex').length > 0) { |         if ($renderedContent.find('span.math-tex').length > 0) { | ||||||
|             await libraryLoader.requireLibrary(libraryLoader.KATEX); |             await libraryLoader.requireLibrary(libraryLoader.KATEX); | ||||||
| @@ -112,10 +112,11 @@ async function renderText(note, options, $renderedContent) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function renderCode(note, options, $renderedContent) { | /** @param {FNote} note */ | ||||||
|     const blob = await note.getBlob({preview: options.trim}); | async function renderCode(note, $renderedContent) { | ||||||
|  |     const blob = await note.getBlob(); | ||||||
|  |  | ||||||
|     $renderedContent.append($("<pre>").text(trim(blob.content, options.trim))); |     $renderedContent.append($("<pre>").text(blob.content)); | ||||||
| } | } | ||||||
|  |  | ||||||
| function renderImage(entity, $renderedContent, options = {}) { | function renderImage(entity, $renderedContent, options = {}) { | ||||||
| @@ -285,15 +286,6 @@ async function renderChildrenList($renderedContent, note) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function trim(text, doTrim) { |  | ||||||
|     if (!doTrim) { |  | ||||||
|         return text; |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         return text.substr(0, Math.min(text.length, 2000)); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getRenderingType(entity) { | function getRenderingType(entity) { | ||||||
|     let type = entity.type || entity.role; |     let type = entity.type || entity.role; | ||||||
|     const mime = entity.mime; |     const mime = entity.mime; | ||||||
|   | |||||||
| @@ -368,12 +368,11 @@ class Froca { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {Promise<FBlob>} */ |     /** @returns {Promise<FBlob>} */ | ||||||
|     async getBlob(entityType, entityId, opts = {}) { |     async getBlob(entityType, entityId) { | ||||||
|         opts.preview = !!opts.preview; |         const key = `${entityType}-${entityId}`; | ||||||
|         const key = `${entityType}-${entityId}-${opts.preview}`; |  | ||||||
|  |  | ||||||
|         if (!this.blobPromises[key]) { |         if (!this.blobPromises[key]) { | ||||||
|             this.blobPromises[key] = server.get(`${entityType}/${entityId}/blob?preview=${opts.preview}`) |             this.blobPromises[key] = server.get(`${entityType}/${entityId}/blob`) | ||||||
|                 .then(row => new FBlob(row)) |                 .then(row => new FBlob(row)) | ||||||
|                 .catch(e => console.error(`Cannot get blob for ${entityType} '${entityId}'`)); |                 .catch(e => console.error(`Cannot get blob for ${entityType} '${entityId}'`)); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,27 +8,32 @@ import appContext from "../components/app_context.js"; | |||||||
|  |  | ||||||
| function setupGlobalTooltip() { | function setupGlobalTooltip() { | ||||||
|     $(document).on("mouseenter", "a", mouseEnterHandler); |     $(document).on("mouseenter", "a", mouseEnterHandler); | ||||||
|     $(document).on("mouseleave", "a", mouseLeaveHandler); |  | ||||||
|  |  | ||||||
|     // close any note tooltip after click, this fixes the problem that sometimes tooltips remained on the screen |     // close any note tooltip after click, this fixes the problem that sometimes tooltips remained on the screen | ||||||
|     $(document).on("click", () => $('.note-tooltip').remove()); |     $(document).on("click", e => { | ||||||
|  |         if ($(e.target).closest(".note-tooltip").length) { | ||||||
|  |             // click within the tooltip shouldn't close it | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         $('.note-tooltip').remove(); | ||||||
|  |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| function setupElementTooltip($el) { | function setupElementTooltip($el) { | ||||||
|     $el.on('mouseenter', mouseEnterHandler); |     $el.on('mouseenter', mouseEnterHandler); | ||||||
|     $el.on('mouseleave', mouseLeaveHandler); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| async function mouseEnterHandler() { | async function mouseEnterHandler() { | ||||||
|     const $link = $(this); |     const $link = $(this); | ||||||
|  |  | ||||||
|     if ($link.hasClass("no-tooltip-preview") |     if ($link.hasClass("no-tooltip-preview") || $link.hasClass("disabled")) { | ||||||
|         || $link.hasClass("disabled")) { |  | ||||||
|         return; |         return; | ||||||
|     } |     } else if ($link.closest(".ck-link-actions").length) { | ||||||
|  |         // this is to avoid showing tooltip from inside the CKEditor link editor dialog | ||||||
|     // this is to avoid showing tooltip from inside the CKEditor link editor dialog |         return; | ||||||
|     if ($link.closest(".ck-link-actions").length) { |     } else if ($link.closest(".note-tooltip").length) { | ||||||
|  |         // don't show tooltip for links within tooltip | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -39,8 +44,21 @@ async function mouseEnterHandler() { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     const linkId = $link.attr("data-link-id") || `link-${Math.floor(Math.random() * 1000000)}`; | ||||||
|  |     $link.attr("data-link-id", linkId); | ||||||
|  |  | ||||||
|  |     if ($(`.${linkId}`).is(":visible")) { | ||||||
|  |         // tooltip is already open for this link | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     const note = await froca.getNote(noteId); |     const note = await froca.getNote(noteId); | ||||||
|     const content = await renderTooltip(note); |  | ||||||
|  |     const [content] = await Promise.all([ | ||||||
|  |         renderTooltip(note), | ||||||
|  |         // to reduce flicker due to accidental mouseover, cursor must stay for a bit over the link for tooltip to appear | ||||||
|  |         new Promise(res => setTimeout(res, 500)) | ||||||
|  |     ]); | ||||||
|  |  | ||||||
|     if (utils.isHtmlEmpty(content)) { |     if (utils.isHtmlEmpty(content)) { | ||||||
|         return; |         return; | ||||||
| @@ -53,7 +71,6 @@ async function mouseEnterHandler() { | |||||||
|     // we now create tooltip which won't close because it won't receive mouseleave event |     // we now create tooltip which won't close because it won't receive mouseleave event | ||||||
|     if ($(this).is(":hover")) { |     if ($(this).is(":hover")) { | ||||||
|         $(this).tooltip({ |         $(this).tooltip({ | ||||||
|             delay: {"show": 300, "hide": 100}, |  | ||||||
|             container: 'body', |             container: 'body', | ||||||
|             // https://github.com/zadam/trilium/issues/2794 https://github.com/zadam/trilium/issues/2988 |             // https://github.com/zadam/trilium/issues/2794 https://github.com/zadam/trilium/issues/2988 | ||||||
|             // with bottom this flickering happens a bit less |             // with bottom this flickering happens a bit less | ||||||
| @@ -63,15 +80,19 @@ async function mouseEnterHandler() { | |||||||
|             title: html, |             title: html, | ||||||
|             html: true, |             html: true, | ||||||
|             template: '<div class="tooltip note-tooltip" role="tooltip"><div class="arrow"></div><div class="tooltip-inner"></div></div>', |             template: '<div class="tooltip note-tooltip" role="tooltip"><div class="arrow"></div><div class="tooltip-inner"></div></div>', | ||||||
|             sanitize: false |             sanitize: false, | ||||||
|  |             customClass: linkId | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         $(this).tooltip('show'); |         $(this).tooltip('show'); | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function mouseLeaveHandler() { |         setTimeout(() => { | ||||||
|     $(this).tooltip('dispose'); |             if (!$(this).is(":hover") && !$(`.${linkId}`).is(":hover")) { | ||||||
|  |                 // cursor is neither over the link nor over the tooltip, user likely is not interested | ||||||
|  |                 $(this).tooltip('dispose'); | ||||||
|  |             } | ||||||
|  |         }, 1000); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function renderTooltip(note) { | async function renderTooltip(note) { | ||||||
|   | |||||||
| @@ -471,7 +471,7 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th | |||||||
| .note-tooltip-content { | .note-tooltip-content { | ||||||
|     /* height needs to stay small because tooltip has problem when it can't fit to either top or bottom of the cursor */ |     /* height needs to stay small because tooltip has problem when it can't fit to either top or bottom of the cursor */ | ||||||
|     max-height: 300px; |     max-height: 300px; | ||||||
|     overflow: hidden; |     overflow: auto; | ||||||
| } | } | ||||||
|  |  | ||||||
| .note-tooltip-content .note-title-with-path .note-path { | .note-tooltip-content .note-title-with-path .note-path { | ||||||
|   | |||||||
| @@ -15,9 +15,7 @@ function getNote(req) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function getNoteBlob(req) { | function getNoteBlob(req) { | ||||||
|     const preview = req.query.preview === 'true'; |     return blobService.getBlobPojo('notes', req.params.noteId); | ||||||
|  |  | ||||||
|     return blobService.getBlobPojo('notes', req.params.noteId, { preview }); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| function getNoteMetadata(req) { | function getNoteMetadata(req) { | ||||||
|   | |||||||
| @@ -2,9 +2,7 @@ const becca = require('../becca/becca'); | |||||||
| const NotFoundError = require("../errors/not_found_error"); | const NotFoundError = require("../errors/not_found_error"); | ||||||
| const protectedSessionService = require("./protected_session"); | const protectedSessionService = require("./protected_session"); | ||||||
|  |  | ||||||
| function getBlobPojo(entityName, entityId, opts = {}) { | function getBlobPojo(entityName, entityId) { | ||||||
|     opts.preview = !!opts.preview; |  | ||||||
|  |  | ||||||
|     const entity = becca.getEntity(entityName, entityId); |     const entity = becca.getEntity(entityName, entityId); | ||||||
|  |  | ||||||
|     if (!entity) { |     if (!entity) { | ||||||
| @@ -19,10 +17,6 @@ function getBlobPojo(entityName, entityId, opts = {}) { | |||||||
|         pojo.content = null; |         pojo.content = null; | ||||||
|     } else { |     } else { | ||||||
|         pojo.content = processContent(pojo.content, entity.isProtected, true); |         pojo.content = processContent(pojo.content, entity.isProtected, true); | ||||||
|  |  | ||||||
|         if (opts.preview && pojo.content.length > 10000) { |  | ||||||
|             pojo.content = `${pojo.content.substr(0, 10000)}\r\n\r\n... and ${pojo.content.length - 10000} more characters.`; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return pojo; |     return pojo; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user