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() {
$(".tooltip").removeClass("show");
if (utils.isDesktop()) {
$(".aa-input").autocomplete("close");
}

View File

@ -1,5 +1,6 @@
class FAttachment {
constructor(froca, row) {
/** @type {Froca} */
this.froca = froca;
this.update(row);
@ -34,12 +35,9 @@ class FAttachment {
return this.froca.notes[this.ownerId];
}
/**
* @param [opts.preview=false] - retrieve only first 10 000 characters for a preview
* @return {FBlob}
*/
async getBlob(opts = {}) {
return await this.froca.getBlob('attachments', this.attachmentId, opts);
/** @return {FBlob} */
async getBlob() {
return await this.froca.getBlob('attachments', this.attachmentId);
}
}

View File

@ -6,6 +6,7 @@ import promotedAttributeDefinitionParser from '../services/promoted_attribute_de
*/
class FAttribute {
constructor(froca, row) {
/** @type {Froca} */
this.froca = froca;
this.update(row);

View File

@ -4,6 +4,7 @@
*/
class FBranch {
constructor(froca, row) {
/** @type {Froca} */
this.froca = froca;
this.update(row);

View File

@ -31,6 +31,7 @@ class FNote {
* @param {Object.<string, Object>} row
*/
constructor(froca, row) {
/** @type {Froca} */
this.froca = froca;
/** @type {string[]} */
@ -859,12 +860,9 @@ class FNote {
return this.getBlob();
}
/**
* @param [opts.preview=false] - retrieve only first 10 000 characters for a preview
* @return {Promise<FBlob>}
*/
async getBlob(opts = {}) {
return await this.froca.getBlob('notes', this.noteId, opts);
/** @return {Promise<FBlob>} */
async getBlob() {
return await this.froca.getBlob('notes', this.noteId);
}
toString() {

View File

@ -19,7 +19,6 @@ let idCounter = 1;
*/
async function getRenderedContent(entity, options = {}) {
options = Object.assign({
trim: false,
tooltip: false
}, options);
@ -29,7 +28,7 @@ async function getRenderedContent(entity, options = {}) {
const $renderedContent = $('<div class="rendered-content">');
if (type === 'text') {
await renderText(entity, options, $renderedContent);
await renderText(entity, $renderedContent);
}
else if (type === 'code') {
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
const blob = await note.getBlob({preview: options.trim});
const blob = await note.getBlob();
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) {
await libraryLoader.requireLibrary(libraryLoader.KATEX);
@ -112,10 +112,11 @@ async function renderText(note, options, $renderedContent) {
}
}
async function renderCode(note, options, $renderedContent) {
const blob = await note.getBlob({preview: options.trim});
/** @param {FNote} note */
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 = {}) {
@ -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) {
let type = entity.type || entity.role;
const mime = entity.mime;

View File

@ -368,12 +368,11 @@ class Froca {
}
/** @returns {Promise<FBlob>} */
async getBlob(entityType, entityId, opts = {}) {
opts.preview = !!opts.preview;
const key = `${entityType}-${entityId}-${opts.preview}`;
async getBlob(entityType, entityId) {
const key = `${entityType}-${entityId}`;
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))
.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() {
$(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
$(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) {
$el.on('mouseenter', mouseEnterHandler);
$el.on('mouseleave', mouseLeaveHandler);
}
async function mouseEnterHandler() {
const $link = $(this);
if ($link.hasClass("no-tooltip-preview")
|| $link.hasClass("disabled")) {
if ($link.hasClass("no-tooltip-preview") || $link.hasClass("disabled")) {
return;
}
// this is to avoid showing tooltip from inside the CKEditor link editor dialog
if ($link.closest(".ck-link-actions").length) {
} else if ($link.closest(".ck-link-actions").length) {
// this is to avoid showing tooltip from inside the CKEditor link editor dialog
return;
} else if ($link.closest(".note-tooltip").length) {
// don't show tooltip for links within tooltip
return;
}
@ -39,8 +44,21 @@ async function mouseEnterHandler() {
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 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)) {
return;
@ -53,7 +71,6 @@ async function mouseEnterHandler() {
// we now create tooltip which won't close because it won't receive mouseleave event
if ($(this).is(":hover")) {
$(this).tooltip({
delay: {"show": 300, "hide": 100},
container: 'body',
// https://github.com/zadam/trilium/issues/2794 https://github.com/zadam/trilium/issues/2988
// with bottom this flickering happens a bit less
@ -63,15 +80,19 @@ async function mouseEnterHandler() {
title: html,
html: true,
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');
}
}
function mouseLeaveHandler() {
$(this).tooltip('dispose');
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');
}
}, 1000);
}
}
async function renderTooltip(note) {

View File

@ -471,7 +471,7 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
.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 */
max-height: 300px;
overflow: hidden;
overflow: auto;
}
.note-tooltip-content .note-title-with-path .note-path {

View File

@ -15,9 +15,7 @@ function getNote(req) {
}
function getNoteBlob(req) {
const preview = req.query.preview === 'true';
return blobService.getBlobPojo('notes', req.params.noteId, { preview });
return blobService.getBlobPojo('notes', req.params.noteId);
}
function getNoteMetadata(req) {

View File

@ -2,9 +2,7 @@ const becca = require('../becca/becca');
const NotFoundError = require("../errors/not_found_error");
const protectedSessionService = require("./protected_session");
function getBlobPojo(entityName, entityId, opts = {}) {
opts.preview = !!opts.preview;
function getBlobPojo(entityName, entityId) {
const entity = becca.getEntity(entityName, entityId);
if (!entity) {
@ -19,10 +17,6 @@ function getBlobPojo(entityName, entityId, opts = {}) {
pojo.content = null;
} else {
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;