wip attachment support

pull/255/head
zadam 2023-04-03 23:47:24 +07:00
parent 2bc78ccafb
commit 5d6d9ab6d6
27 changed files with 289 additions and 63 deletions

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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