note tooltip displays the whole note with scrollbar, other behavior changes. closes #4120

This commit is contained in:
zadam 2023-07-25 22:27:15 +02:00
parent d0e5ad5b7e
commit ddf75cd5e5
11 changed files with 62 additions and 62 deletions

View File

@ -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");
} }

View File

@ -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);
} }
} }

View File

@ -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);

View File

@ -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);

View File

@ -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() {

View File

@ -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;

View File

@ -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}'`));

View File

@ -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
if ($link.closest(".ck-link-actions").length) { return;
} 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,16 +80,20 @@ 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(() => {
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'); $(this).tooltip('dispose');
} }
}, 1000);
}
}
async function renderTooltip(note) { async function renderTooltip(note) {
if (!note) { if (!note) {

View File

@ -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 {

View File

@ -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) {

View File

@ -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;