wip attachment support

This commit is contained in:
zadam 2023-04-03 23:47:24 +02:00
parent 2bc78ccafb
commit 5d6d9ab6d6
27 changed files with 289 additions and 63 deletions

View file

@ -7,8 +7,9 @@ CREATE TABLE IF NOT EXISTS "attachments"
title TEXT not null,
isProtected INT not null DEFAULT 0,
blobId TEXT DEFAULT null,
utcDateScheduledForDeletionSince TEXT DEFAULT NULL,
dateModified TEXT NOT NULL,
utcDateModified TEXT not null,
utcDateScheduledForDeletionSince TEXT DEFAULT NULL,
isDeleted INT not null,
deleteId TEXT DEFAULT NULL);

View file

@ -118,8 +118,9 @@ CREATE TABLE IF NOT EXISTS "attachments"
title TEXT not null,
isProtected INT not null DEFAULT 0,
blobId TEXT DEFAULT null,
utcDateScheduledForDeletionSince TEXT DEFAULT NULL,
dateModified TEXT NOT NULL,
utcDateModified TEXT not null,
utcDateScheduledForDeletionSince TEXT DEFAULT NULL,
isDeleted INT not null,
deleteId TEXT DEFAULT NULL);
CREATE INDEX IDX_attachments_parentId_role

View file

@ -6,6 +6,8 @@ const becca = require('../becca');
const AbstractBeccaEntity = require("./abstract_becca_entity");
/**
* FIXME: how to order attachments?
*
* Attachment represent data related/attached to the note. Conceptually similar to attributes, but intended for
* larger amounts of data and generally not accessible to the user.
*
@ -45,9 +47,11 @@ class BAttachment extends AbstractBeccaEntity {
/** @type {boolean} */
this.isProtected = !!row.isProtected;
/** @type {string} */
this.utcDateScheduledForDeletionSince = row.utcDateScheduledForDeletionSince;
this.dateModified = row.dateModified;
/** @type {string} */
this.utcDateModified = row.utcDateModified;
/** @type {string} */
this.utcDateScheduledForDeletionSince = row.utcDateScheduledForDeletionSince;
}
getNote() {
@ -76,6 +80,7 @@ class BAttachment extends AbstractBeccaEntity {
beforeSaving() {
super.beforeSaving();
this.dateModified = dateUtils.localNowDateTime();
this.utcDateModified = dateUtils.utcNowDateTime();
}
@ -89,8 +94,9 @@ class BAttachment extends AbstractBeccaEntity {
blobId: this.blobId,
isProtected: !!this.isProtected,
isDeleted: false,
utcDateScheduledForDeletionSince: this.utcDateScheduledForDeletionSince,
utcDateModified: this.utcDateModified
dateModified: this.dateModified,
utcDateModified: this.utcDateModified,
utcDateScheduledForDeletionSince: this.utcDateScheduledForDeletionSince
};
}

View file

@ -38,7 +38,7 @@ export default class Entrypoints extends Component {
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.openTabWithNoteWithHoisting(note.noteId, true);
await appContext.tabManager.openTabWithNoteWithHoisting(note.noteId, {activate: true});
appContext.triggerEvent('focusAndSelectTitle', {isNewNote: true});
}
@ -135,7 +135,7 @@ export default class Entrypoints extends Component {
utils.reloadFrontendApp("Switching to mobile version");
}
async openInWindowCommand({notePath, hoistedNoteId}) {
async openInWindowCommand({notePath, hoistedNoteId, viewScope}) {
if (!hoistedNoteId) {
hoistedNoteId = 'root';
}
@ -143,10 +143,10 @@ export default class Entrypoints extends Component {
if (utils.isElectron()) {
const {ipcRenderer} = utils.dynamicRequire('electron');
ipcRenderer.send('create-extra-window', {notePath, hoistedNoteId});
ipcRenderer.send('create-extra-window', {notePath, hoistedNoteId, viewScope});
}
else {
const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?extra=1#${notePath}`;
const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?extraWindow=1&extraHoistedNoteId=${hoistedNoteId}&extraViewScope=${JSON.stringify(viewScope)}#${notePath}`;
window.open(url, '', 'width=1000,height=800');
}

View file

@ -53,8 +53,8 @@ class NoteContext extends Component {
this.notePath = resolvedNotePath;
({noteId: this.noteId, parentNoteId: this.parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(resolvedNotePath));
this.resetViewScope();
this.viewScope.viewMode = opts.viewMode || "default";
this.viewScope = opts.viewScope || {};
this.viewScope.viewMode = this.viewScope.viewMode || "default";
this.saveToRecentNotes(resolvedNotePath);
@ -187,7 +187,7 @@ class NoteContext extends Component {
notePath: this.notePath,
hoistedNoteId: this.hoistedNoteId,
active: this.isActive(),
viewMode: this.viewScope.viewMode
viewScope: this.viewScope
}
}

View file

@ -117,7 +117,12 @@ export default class RootCommandExecutor extends Component {
const notePath = appContext.tabManager.getActiveContextNotePath();
if (notePath) {
await appContext.tabManager.openContextWithNote(notePath, { activate: true, viewMode: 'source' });
await appContext.tabManager.openContextWithNote(notePath, {
activate: true,
viewScope: {
viewMode: 'source'
}
});
}
}
@ -125,7 +130,25 @@ export default class RootCommandExecutor extends Component {
const notePath = appContext.tabManager.getActiveContextNotePath();
if (notePath) {
await appContext.tabManager.openContextWithNote(notePath, { activate: true, viewMode: 'attachments' });
await appContext.tabManager.openContextWithNote(notePath, {
activate: true,
viewScope: {
viewMode: 'attachments'
}
});
}
}
async showAttachmentDetailCommand() {
const notePath = appContext.tabManager.getActiveContextNotePath();
if (notePath) {
await appContext.tabManager.openContextWithNote(notePath, {
activate: true,
viewScope: {
viewMode: 'attachments'
}
});
}
}
}

View file

@ -86,7 +86,8 @@ export default class TabManager extends Component {
filteredTabs.push({
notePath: notePathInUrl || 'root',
active: true,
hoistedNoteId: glob.extraHoistedNoteId || 'root'
hoistedNoteId: glob.extraHoistedNoteId || 'root',
viewScope: glob.extraViewScope || {}
});
}
@ -101,7 +102,7 @@ export default class TabManager extends Component {
ntxId: tab.ntxId,
mainNtxId: tab.mainNtxId,
hoistedNoteId: tab.hoistedNoteId,
viewMode: tab.viewMode
viewScope: tab.viewScope
});
}
});
@ -271,7 +272,7 @@ export default class TabManager extends Component {
/**
* If the requested notePath is within current note hoisting scope then keep the note hoisting also for the new tab.
*/
async openTabWithNoteWithHoisting(notePath, activate = false) {
async openTabWithNoteWithHoisting(notePath, opts = {}) {
const noteContext = this.getActiveContext();
let hoistedNoteId = 'root';
@ -283,7 +284,9 @@ export default class TabManager extends Component {
}
}
return this.openContextWithNote(notePath, { activate, hoistedNoteId });
opts.hoistedNoteId = hoistedNoteId;
return this.openContextWithNote(notePath, opts);
}
async openContextWithNote(notePath, opts = {}) {
@ -291,7 +294,7 @@ export default class TabManager extends Component {
const ntxId = opts.ntxId || null;
const mainNtxId = opts.mainNtxId || null;
const hoistedNoteId = opts.hoistedNoteId || 'root';
const viewMode = opts.viewMode || "default";
const viewScope = opts.viewScope || { viewMode: "default" };
const noteContext = await this.openEmptyTab(ntxId, hoistedNoteId, mainNtxId);
@ -299,7 +302,7 @@ export default class TabManager extends Component {
await noteContext.setNote(notePath, {
// if activate is false then send normal noteSwitched event
triggerSwitchEvent: !activate,
viewMode: viewMode
viewScope: viewScope
});
}

View file

@ -0,0 +1,33 @@
class FAttachment {
constructor(froca, row) {
this.froca = froca;
this.update(row);
}
update(row) {
/** @type {string} */
this.attachmentId = row.attachmentId;
/** @type {string} */
this.parentId = row.parentId;
/** @type {string} */
this.role = row.role;
/** @type {string} */
this.mime = row.mime;
/** @type {string} */
this.title = row.title;
/** @type {string} */
this.dateModified = row.dateModified;
/** @type {string} */
this.utcDateModified = row.utcDateModified;
/** @type {string} */
this.utcDateScheduledForDeletionSince = row.utcDateScheduledForDeletionSince;
this.froca.attachments[this.attachmentId] = this;
}
/** @returns {FNote} */
getNote() {
return this.froca.notes[this.parentId];
}
}

View file

@ -51,6 +51,9 @@ class FNote {
/** @type {Object.<string, string>} */
this.childToBranch = {};
/** @type {FAttachment[]|null} */
this.attachments = null; // lazy loaded
this.update(row);
}
@ -225,6 +228,23 @@ class FNote {
return await this.froca.getNotes(this.children);
}
/** @returns {Promise<FAttachment[]>} */
async getAttachments() {
if (!this.attachments) {
this.attachments = (await server.get(`notes/${this.noteId}/attachments`))
.map(row => new FAttachment(froca, row));
}
return this.attachments;
}
/** @returns {Promise<FAttachment>} */
async getAttachmentById(attachmentId) {
const attachments = await this.getAttachments();
return attachments.find(att => att.attachmentId === attachmentId);
}
/**
* @param {string} [type] - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter

View file

@ -1,4 +1,5 @@
/**
* FIXME: probably make it a FBlob
* Complements the FNote with the main note content and other extra attributes
*/
class FNoteComplement {

View file

@ -1,7 +1,7 @@
import contextMenu from "./context_menu.js";
import appContext from "../components/app_context.js";
function openContextMenu(notePath, hoistedNoteId, e) {
function openContextMenu(notePath, e, viewScope = {}, hoistedNoteId = null) {
contextMenu.show({
x: e.pageX,
y: e.pageY,
@ -16,16 +16,16 @@ function openContextMenu(notePath, hoistedNoteId, e) {
}
if (command === 'openNoteInNewTab') {
appContext.tabManager.openContextWithNote(notePath, { hoistedNoteId });
appContext.tabManager.openContextWithNote(notePath, { hoistedNoteId, viewScope });
}
else if (command === 'openNoteInNewSplit') {
const subContexts = appContext.tabManager.getActiveContext().getSubContexts();
const {ntxId} = subContexts[subContexts.length - 1];
appContext.triggerCommand("openNewNoteSplit", {ntxId, notePath, hoistedNoteId});
appContext.triggerCommand("openNewNoteSplit", {ntxId, notePath, hoistedNoteId, viewScope});
}
else if (command === 'openNoteInNewWindow') {
appContext.triggerCommand('openInWindow', {notePath, hoistedNoteId});
appContext.triggerCommand('openInWindow', {notePath, hoistedNoteId, viewScope});
}
}
});

View file

@ -34,6 +34,10 @@ class Froca {
/** @type {Object.<string, FAttribute>} */
this.attributes = {};
/** @type {Object.<string, FAttachment>} */
this.attachments = {};
// FIXME
/** @type {Object.<string, Promise<FNoteComplement>>} */
this.blobPromises = {};
@ -311,6 +315,7 @@ class Froca {
}
/**
* // FIXME
* @returns {Promise<FNoteComplement>}
*/
async getNoteComplement(noteId) {

View file

@ -34,7 +34,7 @@ async function processEntityChanges(entityChanges) {
loadResults.addOption(ec.entity.name);
} else if (ec.entityName === 'attachments') {
loadResults.addAttachment(ec.entity);
processAttachment(loadResults, ec);
} else if (ec.entityName === 'etapi_tokens') {
// NOOP
}
@ -231,6 +231,43 @@ function processAttributeChange(loadResults, ec) {
}
}
function processAttachment(loadResults, ec) {
if (ec.isErased && ec.entityId in froca.attachments) {
utils.reloadFrontendApp(`${ec.entityName} ${ec.entityId} is erased, need to do complete reload.`);
return;
}
const attachment = froca.attachments[ec.entityId];
if (ec.isErased || ec.entity?.isDeleted) {
if (attachment) {
const note = attachment.getNote();
if (note && note.attachments) {
note.attachments = note.attachments.filter(att => att.attachmentId !== attachment.attachmentId);
}
loadResults.addAttachment(ec.entity);
delete froca.attachments[ec.entityId];
}
return;
}
if (attachment) {
attachment.update(ec.entity);
} else {
const note = froca.notes[ec.entity.parentId];
if (note && note.attachments) {
note.attachments.push(new FAttachment(froca, ec.entity));
}
}
loadResults.addAttachment(ec.entity);
}
export default {
processEntityChanges
}

View file

@ -87,7 +87,16 @@ function getNotePathFromLink($link) {
const url = $link.attr('href');
return url ? getNotePathFromUrl(url) : null;
const notePath = url ? getNotePathFromUrl(url) : null;
const viewScope = {
viewMode: $link.attr('data-view-mode'),
attachmentId: $link.attr('data-attachment-id'),
};
return {
notePath,
viewScope
};
}
function goToLink(evt) {
@ -101,22 +110,25 @@ function goToLink(evt) {
evt.preventDefault();
evt.stopPropagation();
const notePath = getNotePathFromLink($link);
const {notePath, viewScope} = getNotePathFromLink($link);
const ctrlKey = utils.isCtrlKey(evt);
const isLeftClick = evt.which === 1;
const isMiddleClick = evt.which === 2;
const openInNewTab = (isLeftClick && ctrlKey) || isMiddleClick;
if (notePath) {
if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
appContext.tabManager.openTabWithNoteWithHoisting(notePath);
if (openInNewTab) {
appContext.tabManager.openTabWithNoteWithHoisting(notePath, { viewScope });
}
else if (evt.which === 1) {
else if (isLeftClick) {
const ntxId = $(evt.target).closest("[data-ntx-id]").attr("data-ntx-id");
const noteContext = ntxId
? appContext.tabManager.getNoteContextById(ntxId)
: appContext.tabManager.getActiveContext();
noteContext.setNote(notePath).then(() => {
noteContext.setNote(notePath, { viewScope }).then(() => {
if (noteContext !== appContext.tabManager.getActiveContext()) {
appContext.tabManager.activateNoteContext(noteContext.ntxId);
}
@ -124,7 +136,7 @@ function goToLink(evt) {
}
}
else {
if ((evt.which === 1 && ctrlKey) || evt.which === 2
if (openInNewTab
|| $link.hasClass("ck-link-actions__preview") // within edit link dialog single click suffices
|| $link.closest("[contenteditable]").length === 0 // outside of CKEditor single click suffices
) {
@ -147,7 +159,7 @@ function goToLink(evt) {
function linkContextMenu(e) {
const $link = $(e.target).closest("a");
const notePath = getNotePathFromLink($link);
const {notePath, viewScope} = getNotePathFromLink($link);
if (!notePath) {
return;
@ -155,7 +167,7 @@ function linkContextMenu(e) {
e.preventDefault();
linkContextMenuService.openContextMenu(notePath, null, e);
linkContextMenuService.openContextMenu(notePath, e, viewScope, null);
}
async function loadReferenceLinkTitle(noteId, $el) {

View file

@ -37,7 +37,7 @@ const TPL = `
<div class="attachment-detail-wrapper">
<div class="attachment-title-line">
<h4 class="attachment-title"></h4>
<h4 class="attachment-title"><a href="javascript:" data-trigger-command="openAttachmentDetail"></a></h4>
<div class="attachment-details"></div>
<div style="flex: 1 1;"></div>
<div class="attachment-actions-container"></div>
@ -73,7 +73,7 @@ export default class AttachmentDetailWidget extends BasicWidget {
.html()
);
this.$wrapper = this.$widget.find('.attachment-detail-wrapper');
this.$wrapper.find('.attachment-title').text(this.attachment.title);
this.$wrapper.find('.attachment-title a').text(this.attachment.title);
this.$wrapper.find('.attachment-details')
.text(`Role: ${this.attachment.role}, Size: ${utils.formatSize(this.attachment.contentLength)}`);
this.$wrapper.find('.attachment-actions-container').append(this.attachmentActionsWidget.render());
@ -90,9 +90,11 @@ export default class AttachmentDetailWidget extends BasicWidget {
}
}
async entitiesReloadedEvent({loadResults}) {
console.log("AttachmentDetailWidget: entitiesReloadedEvent");
openAttachmentDetailCommand() {
}
async entitiesReloadedEvent({loadResults}) {
const attachmentChange = loadResults.getAttachments().find(att => att.attachmentId === this.attachment.attachmentId);
if (attachmentChange) {

View file

@ -27,7 +27,7 @@ export default class NoteLauncher extends AbstractLauncher {
const hoistedNoteId = this.getHoistedNoteId();
linkContextMenuService.openContextMenu(targetNoteId, hoistedNoteId, evt);
linkContextMenuService.openContextMenu(targetNoteId, evt, {}, hoistedNoteId);
});
}

View file

@ -13,7 +13,7 @@ export default class OpenNoteButtonWidget extends OnClickButtonWidget {
.icon(() => this.noteToOpen.getIcon())
.onClick((widget, evt) => this.launch(evt))
.onAuxClick((widget, evt) => this.launch(evt))
.onContextMenu(evt => linkContextMenuService.openContextMenu(this.noteToOpen.noteId, null, evt));
.onContextMenu(evt => linkContextMenuService.openContextMenu(this.noteToOpen.noteId, evt));
}
async launch(evt) {

View file

@ -34,7 +34,7 @@ export default class SplitNoteContainer extends FlexContainer {
this.child(widget);
}
async openNewNoteSplitEvent({ntxId, notePath, hoistedNoteId}) {
async openNewNoteSplitEvent({ntxId, notePath, hoistedNoteId, viewScope}) {
const mainNtxId = appContext.tabManager.getActiveMainContext().ntxId;
if (!ntxId) {
@ -63,7 +63,7 @@ export default class SplitNoteContainer extends FlexContainer {
await appContext.tabManager.activateNoteContext(noteContext.ntxId);
if (notePath) {
await noteContext.setNote(notePath);
await noteContext.setNote(notePath, viewScope);
}
else {
await noteContext.setEmpty();

View file

@ -61,6 +61,10 @@ export default class NoteContextAwareWidget extends BasicWidget {
}
}
/**
* @param {FNote} note
* @returns {Promise<void>}
*/
async refreshWithNote(note) {}
async noteSwitchedEvent({noteContext, notePath}) {

View file

@ -27,7 +27,8 @@ import NoteMapTypeWidget from "./type_widgets/note_map.js";
import WebViewTypeWidget from "./type_widgets/web_view.js";
import DocTypeWidget from "./type_widgets/doc.js";
import ContentWidgetTypeWidget from "./type_widgets/content_widget.js";
import AttachmentsTypeWidget from "./type_widgets/attachments.js";
import AttachmentListTypeWidget from "./type_widgets/attachment_list.js";
import AttachmentDetailTypeWidget from "./type_widgets/attachment_detail.js";
const TPL = `
<div class="note-detail">
@ -63,7 +64,8 @@ const typeWidgetClasses = {
'webView': WebViewTypeWidget,
'doc': DocTypeWidget,
'contentWidget': ContentWidgetTypeWidget,
'attachments': AttachmentsTypeWidget
'attachmentDetail': AttachmentDetailTypeWidget,
'attachmentList': AttachmentListTypeWidget
};
export default class NoteDetailWidget extends NoteContextAwareWidget {
@ -188,11 +190,12 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
}
let type = note.type;
const viewScope = this.noteContext.viewScope;
if (type === 'text' && this.noteContext.viewScope.viewMode === 'source') {
if (type === 'text' && viewScope.viewMode === 'source') {
type = 'readOnlyCode';
} else if (this.noteContext.viewScope.viewMode === 'attachments') {
type = 'attachments';
} else if (viewScope.viewMode === 'attachments') {
type = viewScope.attachmentId ? 'attachmentDetail' : 'attachmentList';
} else if (type === 'text' && await this.noteContext.isReadOnly()) {
type = 'readOnlyText';
} else if ((type === 'code' || type === 'mermaid') && await this.noteContext.isReadOnly()) {

View file

@ -113,7 +113,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
.linkWidth(1)
.linkColor(() => this.css.mutedTextColor)
.onNodeClick(node => appContext.tabManager.getActiveContext().setNote(node.id))
.onNodeRightClick((node, e) => linkContextMenuService.openContextMenu(node.id, null, e));
.onNodeRightClick((node, e) => linkContextMenuService.openContextMenu(node.id, e));
if (this.mapType === 'link') {
this.graph

View file

@ -70,20 +70,38 @@ export default class NoteTitleWidget extends NoteContextAwareWidget {
}
async refreshWithNote(note) {
const viewMode = this.noteContext.viewScope.viewMode;
this.$noteTitle.val(viewMode === 'default'
? note.title
: `${viewMode}: ${note.title}`);
this.$noteTitle.val(await this.getTitleText(note));
this.$noteTitle.prop("readonly",
(note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable())
|| ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(note.noteId)
|| viewMode !== 'default'
|| this.noteContext.viewScope.viewMode !== 'default'
);
this.setProtectedStatus(note);
}
/** @param {FNote} note */
async getTitleText(note) {
const viewScope = this.noteContext.viewScope;
let title = viewScope.viewMode === 'default'
? note.title
: `${note.title}: ${viewScope.viewMode}`;
if (viewScope.attachmentId) {
// assuming the attachment has been already loaded
const attachment = await note.getAttachmentById(viewScope.attachmentId);
if (attachment) {
title += `: ${attachment.title}`;
}
}
return title;
}
/** @param {FNote} note */
setProtectedStatus(note) {
this.$noteTitle.toggleClass("protected", !!note.isProtected);
}

View file

@ -0,0 +1,54 @@
import TypeWidget from "./type_widget.js";
import server from "../../services/server.js";
import AttachmentDetailWidget from "../attachment_detail.js";
const TPL = `
<div class="attachment-detail note-detail-printable">
<style>
.attachment-detail {
padding: 15px;
}
</style>
<div class="attachment-wrapper"></div>
</div>`;
export default class AttachmentDetailTypeWidget extends TypeWidget {
static getType() {
return "attachmentDetail";
}
doRender() {
this.$widget = $(TPL);
this.$wrapper = this.$widget.find('.attachment-wrapper');
super.doRender();
}
async doRefresh(note) {
this.$wrapper.empty();
this.children = [];
this.renderedAttachmentIds = new Set();
const attachment = await server.get(`notes/${this.noteId}/attachments/${this.noteContext.viewScope.attachment.attachmentId}/?includeContent=true`);
if (!attachment) {
this.$list.html("<strong>This attachment has been deleted.</strong>");
return;
}
const attachmentDetailWidget = new AttachmentDetailWidget(attachment);
this.child(attachmentDetailWidget);
this.$list.append(attachmentDetailWidget.render());
}
async entitiesReloadedEvent({loadResults}) {
const attachmentChange = loadResults.getAttachments().find(att => att.attachmentId === this.attachment.attachmentId);
if (attachmentChange.isDeleted) {
this.refresh(); // all other updates are handled within AttachmentDetailWidget
}
}
}

View file

@ -3,24 +3,24 @@ import server from "../../services/server.js";
import AttachmentDetailWidget from "../attachment_detail.js";
const TPL = `
<div class="attachments note-detail-printable">
<div class="attachment-list note-detail-printable">
<style>
.attachments {
.attachment-list {
padding: 15px;
}
</style>
<div class="attachment-list"></div>
<div class="attachment-list-wrapper"></div>
</div>`;
export default class AttachmentsTypeWidget extends TypeWidget {
export default class AttachmentListTypeWidget extends TypeWidget {
static getType() {
return "attachments";
return "attachmentList";
}
doRender() {
this.$widget = $(TPL);
this.$list = this.$widget.find('.attachment-list');
this.$list = this.$widget.find('.attachment-list-wrapper');
super.doRender();
}

View file

@ -34,8 +34,10 @@ function index(req, res) {
instanceName: config.General ? config.General.instanceName : null,
appCssNoteIds: getAppCssNoteIds(),
isDev: env.isDev(),
isMainWindow: !req.query.extra,
isMainWindow: !req.query.extraWindow,
extraHoistedNoteId: req.query.extraHoistedNoteId,
// make sure only valid JSON gets rendered
extraViewScope: JSON.stringify(req.query.extraViewScope ? JSON.parse(req.query.extraViewScope) : {}),
isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable(),
maxContentWidth: parseInt(options.maxContentWidth),
triliumVersion: packageJson.version,

View file

@ -15,7 +15,7 @@ let mainWindow;
/** @type {Electron.BrowserWindow} */
let setupWindow;
async function createExtraWindow(notePath, hoistedNoteId = 'root') {
async function createExtraWindow(notePath, hoistedNoteId = 'root', viewScope = {}) {
const spellcheckEnabled = optionService.getOptionBool('spellCheckEnabled');
const {BrowserWindow} = require('electron');
@ -35,13 +35,13 @@ async function createExtraWindow(notePath, hoistedNoteId = 'root') {
});
win.setMenuBarVisibility(false);
win.loadURL(`http://127.0.0.1:${port}/?extra=1&extraHoistedNoteId=${hoistedNoteId}#${notePath}`);
win.loadURL(`http://127.0.0.1:${port}/?extraWindow=1&extraHoistedNoteId=${hoistedNoteId}&extraViewScope=${JSON.stringify(viewScope)}#${notePath}`);
configureWebContents(win.webContents, spellcheckEnabled);
}
ipcMain.on('create-extra-window', (event, arg) => {
createExtraWindow(arg.notePath, arg.hoistedNoteId);
createExtraWindow(arg.notePath, arg.hoistedNoteId, arg.viewScope);
});
async function createMainWindow(app) {

View file

@ -33,6 +33,7 @@
appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %>,
isMainWindow: <%= isMainWindow %>,
extraHoistedNoteId: '<%= extraHoistedNoteId %>',
extraViewScope: <%- extraViewScope %>,
isProtectedSessionAvailable: <%= isProtectedSessionAvailable %>,
triliumVersion: "<%= triliumVersion %>",
assetPath: "<%= assetPath %>",