mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Merge branch 't34'
This commit is contained in:
commit
3851bedb57
@ -707,6 +707,18 @@ messagingService.subscribeToMessages(message => {
|
||||
if (message.type === 'refresh-tree') {
|
||||
reload();
|
||||
}
|
||||
else if (message.type === 'open-note') {
|
||||
noteDetailService.loadNoteDetail(message.noteId, {
|
||||
newTab: true,
|
||||
activate: true
|
||||
});
|
||||
|
||||
if (utils.isElectron()) {
|
||||
const currentWindow = require("electron").remote.getCurrentWindow();
|
||||
|
||||
currentWindow.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
messagingService.subscribeToSyncMessages(syncData => {
|
||||
|
@ -832,4 +832,8 @@ a.external:after, a[href^="http://"]:after, a[href^="https://"]:after {
|
||||
|
||||
.note-detail-empty {
|
||||
margin: 50px;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 0.7rem 1rem !important; /* make modal header padding slightly smaller */
|
||||
}
|
121
src/routes/api/clipper.js
Normal file
121
src/routes/api/clipper.js
Normal file
@ -0,0 +1,121 @@
|
||||
"use strict";
|
||||
|
||||
const noteService = require('../../services/notes');
|
||||
const dateNoteService = require('../../services/date_notes');
|
||||
const dateUtils = require('../../services/date_utils');
|
||||
const imageService = require('../../services/image');
|
||||
const messagingService = require('../../services/messaging');
|
||||
const log = require('../../services/log');
|
||||
const path = require('path');
|
||||
const Link = require('../../entities/link');
|
||||
|
||||
async function createNote(req) {
|
||||
const {title, html, url, images} = req.body;
|
||||
|
||||
const todayNote = await dateNoteService.getDateNote(dateUtils.localNowDate());
|
||||
|
||||
const {note} = await noteService.createNote(todayNote.noteId, title, html, {
|
||||
attributes: [
|
||||
{
|
||||
type: 'label',
|
||||
name: 'sourceUrl',
|
||||
value: url
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
let rewrittenHtml = html;
|
||||
|
||||
for (const {src, dataUrl, imageId} of images) {
|
||||
const filename = path.basename(src);
|
||||
|
||||
if (!dataUrl.startsWith("data:image")) {
|
||||
log.info("Image could not be recognized as data URL:", dataUrl.substr(0, Math.min(100, dataUrl.length)));
|
||||
continue;
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(dataUrl.split(",")[1], 'base64');
|
||||
|
||||
const {note: imageNote, url} = await imageService.saveImage(buffer, filename, note.noteId, true);
|
||||
|
||||
await new Link({
|
||||
noteId: note.noteId,
|
||||
targetNoteId: imageNote.noteId,
|
||||
type: 'image'
|
||||
}).save();
|
||||
|
||||
console.log(`Replacing ${imageId} with ${url}`);
|
||||
|
||||
rewrittenHtml = rewrittenHtml.replace(imageId, url);
|
||||
}
|
||||
|
||||
console.log("Done", rewrittenHtml);
|
||||
|
||||
await note.setContent(rewrittenHtml);
|
||||
|
||||
return {
|
||||
noteId: note.noteId
|
||||
};
|
||||
}
|
||||
|
||||
async function createImage(req) {
|
||||
let {dataUrl, title, sourceUrl, pageUrl} = req.body;
|
||||
|
||||
if (!dataUrl) {
|
||||
dataUrl = sourceUrl;
|
||||
sourceUrl = null;
|
||||
}
|
||||
|
||||
if (!dataUrl.startsWith("data:image/")) {
|
||||
const message = "Unrecognized prefix: " + dataUrl.substr(0, Math.min(dataUrl.length, 100));
|
||||
log.info(message);
|
||||
|
||||
return [400, message];
|
||||
}
|
||||
|
||||
if (!title && sourceUrl) {
|
||||
title = path.basename(sourceUrl);
|
||||
}
|
||||
|
||||
if (!title) {
|
||||
title = "clipped image";
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(dataUrl.split(",")[1], 'base64');
|
||||
|
||||
const todayNote = await dateNoteService.getDateNote(dateUtils.localNowDate());
|
||||
|
||||
const {note} = await imageService.saveImage(buffer, title, todayNote.noteId, true);
|
||||
|
||||
if (sourceUrl) {
|
||||
await note.setLabel('sourceUrl', sourceUrl);
|
||||
}
|
||||
|
||||
if (pageUrl) {
|
||||
await note.setLabel('pageUrl', pageUrl);
|
||||
}
|
||||
|
||||
return {
|
||||
noteId: note.noteId
|
||||
};
|
||||
}
|
||||
|
||||
async function openNote(req) {
|
||||
messagingService.sendMessageToAllClients({
|
||||
type: 'open-note',
|
||||
noteId: req.params.noteId
|
||||
});
|
||||
}
|
||||
|
||||
async function ping(req, res) {
|
||||
console.log("PING!!!!");
|
||||
|
||||
res.status(200).send("TriliumClipperServer");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createNote,
|
||||
createImage,
|
||||
openNote,
|
||||
ping
|
||||
};
|
@ -34,7 +34,7 @@ async function uploadImage(req) {
|
||||
return [404, `Note ${noteId} doesn't exist.`];
|
||||
}
|
||||
|
||||
if (!["image/png", "image/jpeg", "image/gif"].includes(file.mimetype)) {
|
||||
if (!["image/png", "image/jpeg", "image/gif", "image/webp"].includes(file.mimetype)) {
|
||||
return [400, "Unknown image type: " + file.mimetype];
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,8 @@ const eventService = require('../../services/events');
|
||||
const cls = require('../../services/cls');
|
||||
const sqlInit = require('../../services/sql_init');
|
||||
const sql = require('../../services/sql');
|
||||
const optionService = require('../../services/options');
|
||||
const ApiToken = require('../../entities/api_token');
|
||||
|
||||
async function loginSync(req) {
|
||||
if (!await sqlInit.schemaExists()) {
|
||||
@ -76,7 +78,28 @@ async function loginToProtectedSession(req) {
|
||||
};
|
||||
}
|
||||
|
||||
async function token(req) {
|
||||
const username = req.body.username;
|
||||
const password = req.body.password;
|
||||
|
||||
const isUsernameValid = username === await optionService.getOption('username');
|
||||
const isPasswordValid = await passwordEncryptionService.verifyPassword(password);
|
||||
|
||||
if (!isUsernameValid || !isPasswordValid) {
|
||||
return [401, "Incorrect username/password"];
|
||||
}
|
||||
|
||||
const apiToken = await new ApiToken({
|
||||
token: utils.randomSecureToken()
|
||||
}).save();
|
||||
|
||||
return {
|
||||
token: apiToken.token
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loginSync,
|
||||
loginToProtectedSession
|
||||
loginToProtectedSession,
|
||||
token
|
||||
};
|
@ -1,33 +1,8 @@
|
||||
"use strict";
|
||||
|
||||
const imageService = require('../../services/image');
|
||||
const utils = require('../../services/utils');
|
||||
const dateNoteService = require('../../services/date_notes');
|
||||
const sql = require('../../services/sql');
|
||||
const noteService = require('../../services/notes');
|
||||
const passwordEncryptionService = require('../../services/password_encryption');
|
||||
const optionService = require('../../services/options');
|
||||
const ApiToken = require('../../entities/api_token');
|
||||
|
||||
async function login(req) {
|
||||
const username = req.body.username;
|
||||
const password = req.body.password;
|
||||
|
||||
const isUsernameValid = username === await optionService.getOption('username');
|
||||
const isPasswordValid = await passwordEncryptionService.verifyPassword(password);
|
||||
|
||||
if (!isUsernameValid || !isPasswordValid) {
|
||||
return [401, "Incorrect username/password"];
|
||||
}
|
||||
|
||||
const apiToken = await new ApiToken({
|
||||
token: utils.randomSecureToken()
|
||||
}).save();
|
||||
|
||||
return {
|
||||
token: apiToken.token
|
||||
};
|
||||
}
|
||||
|
||||
async function uploadImage(req) {
|
||||
const file = req.file;
|
||||
@ -64,7 +39,6 @@ async function saveNote(req) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
login,
|
||||
uploadImage,
|
||||
saveNote
|
||||
};
|
@ -32,6 +32,7 @@ const filesRoute = require('./api/file_upload');
|
||||
const searchRoute = require('./api/search');
|
||||
const dateNotesRoute = require('./api/date_notes');
|
||||
const linkMapRoute = require('./api/link_map');
|
||||
const clipperRoute = require('./api/clipper');
|
||||
|
||||
const log = require('../services/log');
|
||||
const express = require('express');
|
||||
@ -212,7 +213,7 @@ function register(app) {
|
||||
apiRoute(GET, '/api/script/relation/:noteId/:relationName', scriptRoute.getRelationBundles);
|
||||
|
||||
// no CSRF since this is called from android app
|
||||
route(POST, '/api/sender/login', [], senderRoute.login, apiResultHandler);
|
||||
route(POST, '/api/sender/login', [], loginApiRoute.token, apiResultHandler);
|
||||
route(POST, '/api/sender/image', [auth.checkSenderToken, uploadMiddleware], senderRoute.uploadImage, apiResultHandler);
|
||||
route(POST, '/api/sender/note', [auth.checkSenderToken], senderRoute.saveNote, apiResultHandler);
|
||||
|
||||
@ -222,6 +223,12 @@ function register(app) {
|
||||
route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler);
|
||||
// this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username)
|
||||
apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession);
|
||||
route(POST, '/api/login/token', [], loginApiRoute.token, apiResultHandler);
|
||||
|
||||
route(POST, '/api/clipper/notes', [], clipperRoute.createNote, apiResultHandler);
|
||||
route(POST, '/api/clipper/image', [], clipperRoute.createImage, apiResultHandler);
|
||||
route(POST, '/api/clipper/open/:noteId', [], clipperRoute.openNote, apiResultHandler);
|
||||
route(GET, '/api/clipper/ping', [], clipperRoute.ping);
|
||||
|
||||
app.use('', router);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ const {TRILIUM_DATA_DIR} = require('./data_dir');
|
||||
|
||||
const APP_DB_VERSION = 136;
|
||||
const SYNC_VERSION = 9;
|
||||
const CLIPPER_VERSION = 1;
|
||||
|
||||
module.exports = {
|
||||
appVersion: packageJson.version,
|
||||
|
@ -13,6 +13,13 @@ const imageType = require('image-type');
|
||||
const sanitizeFilename = require('sanitize-filename');
|
||||
|
||||
async function saveImage(buffer, originalName, parentNoteId, shrinkImageSwitch) {
|
||||
const origImageFormat = imageType(buffer);
|
||||
|
||||
if (origImageFormat.ext === "webp") {
|
||||
// JIMP does not support webp at the moment: https://github.com/oliver-moran/jimp/issues/144
|
||||
shrinkImageSwitch = false;
|
||||
}
|
||||
|
||||
const finalImageBuffer = shrinkImageSwitch ? await shrinkImage(buffer, originalName) : buffer;
|
||||
|
||||
const imageFormat = imageType(finalImageBuffer);
|
||||
@ -48,7 +55,7 @@ async function shrinkImage(buffer, originalName) {
|
||||
try {
|
||||
finalImageBuffer = await optimize(resizedImage);
|
||||
} catch (e) {
|
||||
log.error("Failed to optimize image '" + originalName + "\nStack: " + e.stack);
|
||||
log.error("Failed to optimize image '" + originalName + "'\nStack: " + e.stack);
|
||||
finalImageBuffer = resizedImage;
|
||||
}
|
||||
|
||||
@ -93,7 +100,7 @@ async function optimize(buffer) {
|
||||
quality: 50
|
||||
}),
|
||||
imageminPngQuant({
|
||||
quality: "0-70"
|
||||
quality: [0, 0.7]
|
||||
}),
|
||||
imageminGifLossy({
|
||||
lossy: 80,
|
||||
|
@ -251,7 +251,7 @@ async function importEnex(importContext, file, parentNote) {
|
||||
noteContent = noteContent.replace(mediaRegex, resourceLink);
|
||||
};
|
||||
|
||||
if (["image/jpeg", "image/png", "image/gif"].includes(resource.mime)) {
|
||||
if (["image/jpeg", "image/png", "image/gif", "image/webp"].includes(resource.mime)) {
|
||||
try {
|
||||
const originalName = "image." + resource.mime.substr(6);
|
||||
|
||||
|
@ -100,7 +100,7 @@ async function importSingleFile(importContext, file, parentNote) {
|
||||
return await importCodeNote(importContext, file, parentNote);
|
||||
}
|
||||
|
||||
if (["image/jpeg", "image/gif", "image/png"].includes(mime)) {
|
||||
if (["image/jpeg", "image/gif", "image/png", "image/webp"].includes(mime)) {
|
||||
return await importImage(file, parentNote, importContext);
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ const config = require('./config');
|
||||
const utils = require('./utils');
|
||||
|
||||
if (utils.isElectron()) {
|
||||
module.exports = getPort();
|
||||
module.exports = 53010;//getPort();
|
||||
}
|
||||
else {
|
||||
module.exports = Promise.resolve(config['Network']['port'] || '3000');
|
||||
|
Loading…
x
Reference in New Issue
Block a user