Merge branch 'develop' into feat_add-link-to-swagger-ui

This commit is contained in:
Elian Doran
2025-02-26 22:46:31 +02:00
committed by GitHub
18 changed files with 300 additions and 28 deletions

View File

@@ -203,7 +203,7 @@ function renderFile(entity: FNote | FAttachment, type: string, $renderedContent:
// open doesn't work for protected notes since it works through a browser which isn't in protected session
$openButton.toggle(!entity.isProtected);
$content.append($('<div style="display: flex; justify-content: space-evenly; margin-top: 5px;">').append($downloadButton).append($openButton));
$content.append($('<footer class="file-footer">').append($downloadButton).append($openButton));
}
$renderedContent.append($content);
@@ -239,7 +239,7 @@ async function renderMermaid(note: FNote | FAttachment, $renderedContent: JQuery
* @param {FNote} note
* @returns {Promise<void>}
*/
async function renderChildrenList($renderedContent: JQuery<HTMLElement>, note: FNote) {
async function renderChildrenList($renderedContent: JQuery<HTMLElement>, note: FNote) {
let childNoteIds = note.getChildNoteIds();
if (!childNoteIds.length) {

View File

@@ -9,7 +9,7 @@ interface CreateNewTasksOpts {
export async function createNewTask({ parentNoteId, title }: CreateNewTasksOpts) {
await server.post(`tasks`, {
parentNoteId,
title
title: title.trim()
});
}

View File

@@ -2,6 +2,9 @@ import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "happy-dom"
environment: "happy-dom",
coverage: {
reporter: [ "text", "html" ]
}
}
});

View File

@@ -20,6 +20,10 @@ const TPL = `
.note-list-widget.full-height .note-list-widget-content {
height: 100%;
}
.note-list-widget video {
height: 100%;
}
</style>
<div class="note-list-widget-content">

View File

@@ -63,7 +63,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
return true;
}
if (note.type === "file" && note.mime === "application/pdf") {
if (note.type === "file" && (note.mime === "application/pdf" || note.mime.startsWith("video/"))) {
return true;
}

View File

@@ -22,6 +22,10 @@ const TPL = `
padding: 0;
}
.note-split.full-content-width .note-detail-file[data-preview-type="video"] {
overflow: hidden;
}
.file-preview-content {
background-color: var(--accented-background-color);
padding: 15px;
@@ -29,6 +33,11 @@ const TPL = `
overflow: auto;
margin: 10px;
}
.note-detail-file > .video-preview {
width: 100%;
height: 100%;
}
</style>
<div class="file-preview-too-big alert alert-info hidden-ext">
@@ -85,6 +94,8 @@ export default class FileTypeWidget extends TypeWidget {
this.$videoPreview.hide();
this.$audioPreview.hide();
let previewType: string;
if (blob?.content) {
this.$previewContent.show().scrollTop(0);
const trimmedContent = blob.content.substring(0, TEXT_MAX_NUM_CHARS);
@@ -92,23 +103,30 @@ export default class FileTypeWidget extends TypeWidget {
this.$previewTooBig.removeClass("hidden-ext");
}
this.$previewContent.text(trimmedContent);
previewType = "text";
} else if (note.mime === "application/pdf") {
this.$pdfPreview.show().attr("src", openService.getUrlForDownload(`api/notes/${this.noteId}/open`));
previewType = "pdf";
} else if (note.mime.startsWith("video/")) {
this.$videoPreview
.show()
.attr("src", openService.getUrlForDownload(`api/notes/${this.noteId}/open-partial`))
.attr("type", this.note?.mime ?? "")
.css("width", this.$widget.width() ?? 0);
previewType = "video";
} else if (note.mime.startsWith("audio/")) {
this.$audioPreview
.show()
.attr("src", openService.getUrlForDownload(`api/notes/${this.noteId}/open-partial`))
.attr("type", this.note?.mime ?? "")
.css("width", this.$widget.width() ?? 0);
previewType = "audio";
} else {
this.$previewNotAvailable.show();
previewType = "not-available";
}
this.$widget.attr("data-preview-type", previewType ?? "");
}
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {

View File

@@ -6,6 +6,7 @@ import * as taskService from "../../services/tasks.js";
import type { EventData } from "../../components/app_context.js";
import dayjs from "dayjs";
import calendarTime from "dayjs/plugin/calendar.js";
import { t } from "../../services/i18n.js";
dayjs.extend(calendarTime);
const TPL = `
@@ -25,7 +26,7 @@ const TPL = `
padding: 10px;
}
.note-detail-task-list header {
.note-detail-task-list > header {
position: sticky;
top: 0;
z-index: 100;
@@ -59,15 +60,23 @@ const TPL = `
transition: background 250ms ease-in-out;
}
.note-detail-task-list .task-container li > header {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
align-items: center;
}
.note-detail-task-list .task-container li .check {
margin-right: 0.5em;
}
.note-detail-task-list .task-container li .title {
flex-grow: 1;
}
.note-detail-task-list .task-container li .due-date {
float: right;
font-size: 0.9rem;
margin-top: 0.1rem;
vertical-align: middle;
}
.note-detail-task-list .task-container li.overdue .due-date {
@@ -81,6 +90,7 @@ function buildTasks(tasks: FTask[]) {
let html = '';
const now = dayjs();
const dateFormat = "DD-MM-YYYY";
for (const task of tasks) {
const classes = ["task"];
@@ -89,14 +99,25 @@ function buildTasks(tasks: FTask[]) {
}
html += `<li class="${classes.join(" ")}" data-task-id="${task.taskId}">`;
html += "<header>";
html += '<span class="title">';
html += `<input type="checkbox" class="check" ${task.isDone ? "checked" : ""} />`;
html += task.title;
html += `${task.title}</span>`;
html += '</span>';
if (task.dueDate) {
html += `<span class="due-date">`;
html += `<span class="bx bx-calendar"></span> `;
html += dayjs(task.dueDate).calendar();
html += dayjs(task.dueDate).calendar(null, {
sameDay: `[${t("tasks.due.today")}]`,
nextDay: `[${t("tasks.due.tomorrow")}]`,
nextWeek: "dddd",
lastDay: `[${t("tasks.due.yesterday")}]`,
lastWeek: dateFormat,
sameElse: dateFormat
});
html += "</span>";
}
html += "</header>";
html += `<div class="edit-container"></div>`;
html += `</li>`;
}
@@ -142,20 +163,19 @@ export default class TaskListWidget extends TypeWidget {
});
this.$taskContainer.on("click", "li", (e) => {
const $target = $(e.target);
// Don't collapse when clicking on an inside element such as the due date dropdown.
if (e.currentTarget !== e.target) {
if ((e.target as HTMLElement).tagName === "INPUT") {
return;
}
const $target = $(e.target);
// Clear existing edit containers.
const $existingContainers = this.$taskContainer.find(".edit-container");
$existingContainers.html("");
// Add the new edit container.
const $editContainer = $target.find(".edit-container");
const $editContainer = $target.closest("li").find(".edit-container");
const task = this.#getCorrespondingTask($target);
if (task) {
$editContainer.html(buildEditContainer(task));
@@ -183,7 +203,11 @@ export default class TaskListWidget extends TypeWidget {
}
#getCorrespondingTask($target: JQuery<HTMLElement>) {
const taskId = $target.closest("li")[0].dataset.taskId;
const $parentEl = $target.closest("li");
if (!$parentEl.length) {
return;
}
const taskId = $parentEl[0].dataset.taskId;
if (!taskId) {
return;
}

View File

@@ -1684,4 +1684,15 @@ body.zen .note-title-widget,
body.zen .note-title-widget input {
font-size: 1rem !important;
background: transparent !important;
}
/* Content renderer */
footer.file-footer {
display: flex;
justify-content: center;
}
footer.file-footer button {
margin: 5px;
}

View File

@@ -1560,6 +1560,10 @@ div.bookmark-folder-widget .note-link .bx {
border-bottom-color: var(--card-border-color);
}
.note-list-wrapper .note-book-card footer.file-footer {
border: 1px solid var(--card-border-color);
}
.note-list-wrapper .note-book-card .note-book-header .note-icon {
font-size: 17px;
vertical-align: text-bottom;
@@ -1610,7 +1614,8 @@ div.bookmark-folder-widget .note-link .bx {
.note-list-wrapper .note-book-card .note-book-content.type-canvas .rendered-content,
.note-list-wrapper .note-book-card .note-book-content.type-mindMap .rendered-content,
.note-list-wrapper .note-book-card .note-book-content.type-code .rendered-content {
.note-list-wrapper .note-book-card .note-book-content.type-code .rendered-content,
.note-list-wrapper .note-book-card .note-book-content.type-video .rendered-content {
padding: 0;
}

View File

@@ -1675,5 +1675,12 @@
"time_selector": {
"invalid_input": "The entered time value is not a valid number.",
"minimum_input": "The entered time value needs to be at least {{minimumSeconds}} seconds."
},
"tasks": {
"due": {
"today": "Today",
"tomorrow": "Tomorrow",
"yesterday": "Yesterday"
}
}
}

View File

@@ -1678,5 +1678,12 @@
"redirect_bare_domain_description": "Redirecționează utilizatorii anonimi către pagina de partajare în locul paginii de autentificare",
"redirect_bare_domain": "Redirecționează domeniul principal la pagina de partajare",
"check_share_root": "Verificare stare pagină partajată principală"
},
"tasks": {
"due": {
"today": "Azi",
"tomorrow": "Mâine",
"yesterday": "Ieri"
}
}
}

34
src/share/routes.spec.ts Normal file
View File

@@ -0,0 +1,34 @@
import { beforeAll, beforeEach, describe, expect, it } from "vitest";
import supertest from "supertest";
import { initializeTranslations } from "../services/i18n.js";
import type { Application, Request, Response, NextFunction } from "express";
let app: Application;
describe("Share API test", () => {
let cannotSetHeadersCount = 0;
beforeAll(async () => {
initializeTranslations();
app = (await import("../app.js")).default;
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
if (err.message.includes("Cannot set headers after they are sent to the client")) {
cannotSetHeadersCount++;
}
next();
});
});
beforeEach(() => {
cannotSetHeadersCount = 0;
});
it("requests password for password-protected share", async () => {
await supertest(app)
.get("/share/YjlPRj2E9fOV")
.expect("WWW-Authenticate", 'Basic realm="User Visible Realm", charset="UTF-8"');
expect(cannotSetHeadersCount).toBe(0);
});
});

View File

@@ -87,7 +87,6 @@ function checkNoteAccess(noteId: string, req: Request, res: Response) {
const header = req.header("Authorization");
if (!header?.startsWith("Basic ")) {
requestCredentials(res);
return false;
}