mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
can't allow opening externally on attachment list
This commit is contained in:
parent
cc02546ed3
commit
8284c673f9
@ -76,6 +76,10 @@ class AppContext extends Component {
|
|||||||
$("body").append($renderedWidget);
|
$("body").append($renderedWidget);
|
||||||
|
|
||||||
$renderedWidget.on('click', "[data-trigger-command]", function() {
|
$renderedWidget.on('click', "[data-trigger-command]", function() {
|
||||||
|
if ($(this).hasClass("disabled")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const commandName = $(this).attr('data-trigger-command');
|
const commandName = $(this).attr('data-trigger-command');
|
||||||
const $component = $(this).closest(".component");
|
const $component = $(this).closest(".component");
|
||||||
const component = $component.prop("component");
|
const component = $component.prop("component");
|
||||||
|
@ -4,11 +4,9 @@ import noteTooltipService from './services/note_tooltip.js';
|
|||||||
import bundleService from "./services/bundle.js";
|
import bundleService from "./services/bundle.js";
|
||||||
import noteAutocompleteService from './services/note_autocomplete.js';
|
import noteAutocompleteService from './services/note_autocomplete.js';
|
||||||
import macInit from './services/mac_init.js';
|
import macInit from './services/mac_init.js';
|
||||||
import contextMenu from "./menus/context_menu.js";
|
import electronContextMenu from "./menus/electron_context_menu.js";
|
||||||
import DesktopLayout from "./layouts/desktop_layout.js";
|
import DesktopLayout from "./layouts/desktop_layout.js";
|
||||||
import glob from "./services/glob.js";
|
import glob from "./services/glob.js";
|
||||||
import zoomService from './components/zoom.js';
|
|
||||||
import options from "./services/options.js";
|
|
||||||
|
|
||||||
bundleService.getWidgetBundlesByParent().then(widgetBundles => {
|
bundleService.getWidgetBundlesByParent().then(widgetBundles => {
|
||||||
appContext.setLayout(new DesktopLayout(widgetBundles));
|
appContext.setLayout(new DesktopLayout(widgetBundles));
|
||||||
@ -18,9 +16,8 @@ bundleService.getWidgetBundlesByParent().then(widgetBundles => {
|
|||||||
glob.setupGlobs();
|
glob.setupGlobs();
|
||||||
|
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
utils.dynamicRequire('electron').ipcRenderer.on('globalShortcut', async function(event, actionName) {
|
utils.dynamicRequire('electron').ipcRenderer.on('globalShortcut',
|
||||||
appContext.triggerCommand(actionName);
|
async (event, actionName) => appContext.triggerCommand(actionName));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macInit.init();
|
macInit.init();
|
||||||
@ -30,131 +27,5 @@ noteTooltipService.setupGlobalTooltip();
|
|||||||
noteAutocompleteService.init();
|
noteAutocompleteService.init();
|
||||||
|
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
const electron = utils.dynamicRequire('electron');
|
electronContextMenu.setupContextMenu();
|
||||||
|
|
||||||
const remote = utils.dynamicRequire('@electron/remote');
|
|
||||||
const {webContents} = remote.getCurrentWindow();
|
|
||||||
|
|
||||||
webContents.on('context-menu', (event, params) => {
|
|
||||||
const {editFlags} = params;
|
|
||||||
const hasText = params.selectionText.trim().length > 0;
|
|
||||||
const isMac = process.platform === "darwin";
|
|
||||||
const platformModifier = isMac ? 'Meta' : 'Ctrl';
|
|
||||||
|
|
||||||
const items = [];
|
|
||||||
|
|
||||||
if (params.misspelledWord) {
|
|
||||||
for (const suggestion of params.dictionarySuggestions) {
|
|
||||||
items.push({
|
|
||||||
title: suggestion,
|
|
||||||
command: "replaceMisspelling",
|
|
||||||
spellingSuggestion: suggestion,
|
|
||||||
uiIcon: "bx bx-empty"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
title: `Add "${params.misspelledWord}" to dictionary`,
|
|
||||||
uiIcon: "bx bx-plus",
|
|
||||||
handler: () => webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord)
|
|
||||||
});
|
|
||||||
|
|
||||||
items.push({ title: `----` });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.isEditable) {
|
|
||||||
items.push({
|
|
||||||
enabled: editFlags.canCut && hasText,
|
|
||||||
title: `Cut <kbd>${platformModifier}+X`,
|
|
||||||
uiIcon: "bx bx-cut",
|
|
||||||
handler: () => webContents.cut()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.isEditable || hasText) {
|
|
||||||
items.push({
|
|
||||||
enabled: editFlags.canCopy && hasText,
|
|
||||||
title: `Copy <kbd>${platformModifier}+C`,
|
|
||||||
uiIcon: "bx bx-copy",
|
|
||||||
handler: () => webContents.copy()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!["", "javascript:", "about:blank#blocked"].includes(params.linkURL) && params.mediaType === 'none') {
|
|
||||||
items.push({
|
|
||||||
title: `Copy link`,
|
|
||||||
uiIcon: "bx bx-copy",
|
|
||||||
handler: () => {
|
|
||||||
electron.clipboard.write({
|
|
||||||
bookmark: params.linkText,
|
|
||||||
text: params.linkURL
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.isEditable) {
|
|
||||||
items.push({
|
|
||||||
enabled: editFlags.canPaste,
|
|
||||||
title: `Paste <kbd>${platformModifier}+V`,
|
|
||||||
uiIcon: "bx bx-paste",
|
|
||||||
handler: () => webContents.paste()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.isEditable) {
|
|
||||||
items.push({
|
|
||||||
enabled: editFlags.canPaste,
|
|
||||||
title: `Paste as plain text <kbd>${platformModifier}+Shift+V`,
|
|
||||||
uiIcon: "bx bx-paste",
|
|
||||||
handler: () => webContents.pasteAndMatchStyle()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasText) {
|
|
||||||
const shortenedSelection = params.selectionText.length > 15
|
|
||||||
? (`${params.selectionText.substr(0, 13)}…`)
|
|
||||||
: params.selectionText;
|
|
||||||
|
|
||||||
// Read the search engine from the options and fallback to DuckDuckGo if the option is not set.
|
|
||||||
const customSearchEngineName = options.get("customSearchEngineName");
|
|
||||||
const customSearchEngineUrl = options.get("customSearchEngineUrl");
|
|
||||||
let searchEngineName;
|
|
||||||
let searchEngineUrl;
|
|
||||||
if (customSearchEngineName && customSearchEngineUrl) {
|
|
||||||
searchEngineName = customSearchEngineName;
|
|
||||||
searchEngineUrl = customSearchEngineUrl;
|
|
||||||
} else {
|
|
||||||
searchEngineName = "Duckduckgo";
|
|
||||||
searchEngineUrl = "https://duckduckgo.com/?q={keyword}";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the placeholder with the real search keyword.
|
|
||||||
let searchUrl = searchEngineUrl.replace("{keyword}", encodeURIComponent(params.selectionText));
|
|
||||||
|
|
||||||
items.push({
|
|
||||||
enabled: editFlags.canPaste,
|
|
||||||
title: `Search for "${shortenedSelection}" with ${searchEngineName}`,
|
|
||||||
uiIcon: "bx bx-search-alt",
|
|
||||||
handler: () => electron.shell.openExternal(searchUrl)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (items.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const zoomLevel = zoomService.getCurrentZoom();
|
|
||||||
|
|
||||||
contextMenu.show({
|
|
||||||
x: params.x / zoomLevel,
|
|
||||||
y: params.y / zoomLevel,
|
|
||||||
items,
|
|
||||||
selectMenuItemHandler: ({command, spellingSuggestion}) => {
|
|
||||||
if (command === 'replaceMisspelling') {
|
|
||||||
webContents.insertText(spellingSuggestion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
138
src/public/app/menus/electron_context_menu.js
Normal file
138
src/public/app/menus/electron_context_menu.js
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import utils from "../services/utils.js";
|
||||||
|
import options from "../services/options.js";
|
||||||
|
import zoomService from "../components/zoom.js";
|
||||||
|
import contextMenu from "./context_menu.js";
|
||||||
|
|
||||||
|
function setupContextMenu() {
|
||||||
|
const electron = utils.dynamicRequire('electron');
|
||||||
|
|
||||||
|
const remote = utils.dynamicRequire('@electron/remote');
|
||||||
|
const {webContents} = remote.getCurrentWindow();
|
||||||
|
|
||||||
|
webContents.on('context-menu', (event, params) => {
|
||||||
|
const {editFlags} = params;
|
||||||
|
const hasText = params.selectionText.trim().length > 0;
|
||||||
|
const isMac = process.platform === "darwin";
|
||||||
|
const platformModifier = isMac ? 'Meta' : 'Ctrl';
|
||||||
|
|
||||||
|
const items = [];
|
||||||
|
|
||||||
|
if (params.misspelledWord) {
|
||||||
|
for (const suggestion of params.dictionarySuggestions) {
|
||||||
|
items.push({
|
||||||
|
title: suggestion,
|
||||||
|
command: "replaceMisspelling",
|
||||||
|
spellingSuggestion: suggestion,
|
||||||
|
uiIcon: "bx bx-empty"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
title: `Add "${params.misspelledWord}" to dictionary`,
|
||||||
|
uiIcon: "bx bx-plus",
|
||||||
|
handler: () => webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord)
|
||||||
|
});
|
||||||
|
|
||||||
|
items.push({ title: `----` });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.isEditable) {
|
||||||
|
items.push({
|
||||||
|
enabled: editFlags.canCut && hasText,
|
||||||
|
title: `Cut <kbd>${platformModifier}+X`,
|
||||||
|
uiIcon: "bx bx-cut",
|
||||||
|
handler: () => webContents.cut()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.isEditable || hasText) {
|
||||||
|
items.push({
|
||||||
|
enabled: editFlags.canCopy && hasText,
|
||||||
|
title: `Copy <kbd>${platformModifier}+C`,
|
||||||
|
uiIcon: "bx bx-copy",
|
||||||
|
handler: () => webContents.copy()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!["", "javascript:", "about:blank#blocked"].includes(params.linkURL) && params.mediaType === 'none') {
|
||||||
|
items.push({
|
||||||
|
title: `Copy link`,
|
||||||
|
uiIcon: "bx bx-copy",
|
||||||
|
handler: () => {
|
||||||
|
electron.clipboard.write({
|
||||||
|
bookmark: params.linkText,
|
||||||
|
text: params.linkURL
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.isEditable) {
|
||||||
|
items.push({
|
||||||
|
enabled: editFlags.canPaste,
|
||||||
|
title: `Paste <kbd>${platformModifier}+V`,
|
||||||
|
uiIcon: "bx bx-paste",
|
||||||
|
handler: () => webContents.paste()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.isEditable) {
|
||||||
|
items.push({
|
||||||
|
enabled: editFlags.canPaste,
|
||||||
|
title: `Paste as plain text <kbd>${platformModifier}+Shift+V`,
|
||||||
|
uiIcon: "bx bx-paste",
|
||||||
|
handler: () => webContents.pasteAndMatchStyle()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasText) {
|
||||||
|
const shortenedSelection = params.selectionText.length > 15
|
||||||
|
? (`${params.selectionText.substr(0, 13)}…`)
|
||||||
|
: params.selectionText;
|
||||||
|
|
||||||
|
// Read the search engine from the options and fallback to DuckDuckGo if the option is not set.
|
||||||
|
const customSearchEngineName = options.get("customSearchEngineName");
|
||||||
|
const customSearchEngineUrl = options.get("customSearchEngineUrl");
|
||||||
|
let searchEngineName;
|
||||||
|
let searchEngineUrl;
|
||||||
|
if (customSearchEngineName && customSearchEngineUrl) {
|
||||||
|
searchEngineName = customSearchEngineName;
|
||||||
|
searchEngineUrl = customSearchEngineUrl;
|
||||||
|
} else {
|
||||||
|
searchEngineName = "Duckduckgo";
|
||||||
|
searchEngineUrl = "https://duckduckgo.com/?q={keyword}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the placeholder with the real search keyword.
|
||||||
|
let searchUrl = searchEngineUrl.replace("{keyword}", encodeURIComponent(params.selectionText));
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
enabled: editFlags.canPaste,
|
||||||
|
title: `Search for "${shortenedSelection}" with ${searchEngineName}`,
|
||||||
|
uiIcon: "bx bx-search-alt",
|
||||||
|
handler: () => electron.shell.openExternal(searchUrl)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const zoomLevel = zoomService.getCurrentZoom();
|
||||||
|
|
||||||
|
contextMenu.show({
|
||||||
|
x: params.x / zoomLevel,
|
||||||
|
y: params.y / zoomLevel,
|
||||||
|
items,
|
||||||
|
selectMenuItemHandler: ({command, spellingSuggestion}) => {
|
||||||
|
if (command === 'replaceMisspelling') {
|
||||||
|
webContents.insertText(spellingSuggestion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setupContextMenu
|
||||||
|
};
|
@ -27,7 +27,7 @@ async function mouseEnterHandler() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is to avoid showing tooltip from inside CKEditor link editor dialog
|
// this is to avoid showing tooltip from inside the CKEditor link editor dialog
|
||||||
if ($link.closest(".ck-link-actions").length) {
|
if ($link.closest(".ck-link-actions").length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -67,13 +67,13 @@ const TPL = `
|
|||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
export default class AttachmentDetailWidget extends BasicWidget {
|
export default class AttachmentDetailWidget extends BasicWidget {
|
||||||
constructor(attachment) {
|
constructor(attachment, isFullDetail) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.contentSized();
|
this.contentSized();
|
||||||
this.attachment = attachment;
|
this.attachment = attachment;
|
||||||
this.attachmentActionsWidget = new AttachmentActionsWidget(attachment);
|
this.attachmentActionsWidget = new AttachmentActionsWidget(attachment, isFullDetail);
|
||||||
this.isFullDetail = true;
|
this.isFullDetail = isFullDetail;
|
||||||
this.child(this.attachmentActionsWidget);
|
this.child(this.attachmentActionsWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,10 +29,8 @@ const TPL = `
|
|||||||
aria-expanded="false" class="icon-action icon-action-always-border bx bx-dots-vertical-rounded"></button>
|
aria-expanded="false" class="icon-action icon-action-always-border bx bx-dots-vertical-rounded"></button>
|
||||||
|
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
<a data-trigger-command="openAttachment" class="dropdown-item">Open</a>
|
<a data-trigger-command="openAttachment" class="dropdown-item"
|
||||||
<a data-trigger-command="openAttachmentExternally" class="dropdown-item"
|
title="File will be open in an external application and watched for changes. You'll then be able to upload the modified version back to Trilium.">Open externally</a>
|
||||||
title="File will be open in an external application and watched for changes. You'll then be able to upload the modified version back to Trilium.">
|
|
||||||
Open externally</a>
|
|
||||||
<a data-trigger-command="downloadAttachment" class="dropdown-item">Download</a>
|
<a data-trigger-command="downloadAttachment" class="dropdown-item">Download</a>
|
||||||
<a data-trigger-command="uploadNewAttachmentRevision" class="dropdown-item">Upload new revision</a>
|
<a data-trigger-command="uploadNewAttachmentRevision" class="dropdown-item">Upload new revision</a>
|
||||||
<a data-trigger-command="copyAttachmentReferenceToClipboard" class="dropdown-item">Copy reference to clipboard</a>
|
<a data-trigger-command="copyAttachmentReferenceToClipboard" class="dropdown-item">Copy reference to clipboard</a>
|
||||||
@ -44,10 +42,11 @@ const TPL = `
|
|||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
export default class AttachmentActionsWidget extends BasicWidget {
|
export default class AttachmentActionsWidget extends BasicWidget {
|
||||||
constructor(attachment) {
|
constructor(attachment, isFullDetail) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.attachment = attachment;
|
this.attachment = attachment;
|
||||||
|
this.isFullDetail = isFullDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
get attachmentId() {
|
get attachmentId() {
|
||||||
@ -83,6 +82,17 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
|||||||
toastService.showError("Upload of a new attachment revision failed.");
|
toastService.showError("Upload of a new attachment revision failed.");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!this.isFullDetail) {
|
||||||
|
// we deactivate this button because the WatchedFileUpdateStatusWidget assumes only one visible attachment
|
||||||
|
// in a note context, so it doesn't work in a list
|
||||||
|
const $openAttachmentButton = this.$widget.find("[data-trigger-command='openAttachment']");
|
||||||
|
$openAttachmentButton
|
||||||
|
.addClass("disabled")
|
||||||
|
.append($('<span class="disabled-tooltip"> (?)</span>')
|
||||||
|
.attr("title", "Opening attachment externally is available only from the detail page, please first click on the attachment detail first and repeat the action.")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async openAttachmentCommand() {
|
async openAttachmentCommand() {
|
||||||
@ -101,10 +111,6 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
|||||||
this.parent.copyAttachmentReferenceToClipboard();
|
this.parent.copyAttachmentReferenceToClipboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
async openAttachmentExternallyCommand() {
|
|
||||||
await openService.openAttachmentExternally(this.attachmentId, this.attachment.mime);
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteAttachmentCommand() {
|
async deleteAttachmentCommand() {
|
||||||
if (!await dialogService.confirm(`Are you sure you want to delete attachment '${this.attachment.title}'?`)) {
|
if (!await dialogService.confirm(`Are you sure you want to delete attachment '${this.attachment.title}'?`)) {
|
||||||
return;
|
return;
|
||||||
|
@ -37,8 +37,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachmentDetailWidget = new AttachmentDetailWidget(attachment);
|
const attachmentDetailWidget = new AttachmentDetailWidget(attachment, true);
|
||||||
attachmentDetailWidget.isFullDetail = true;
|
|
||||||
this.child(attachmentDetailWidget);
|
this.child(attachmentDetailWidget);
|
||||||
|
|
||||||
this.$wrapper.append(attachmentDetailWidget.render());
|
this.$wrapper.append(attachmentDetailWidget.render());
|
||||||
|
@ -39,8 +39,7 @@ export default class AttachmentListTypeWidget extends TypeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const attachment of attachments) {
|
for (const attachment of attachments) {
|
||||||
const attachmentDetailWidget = new AttachmentDetailWidget(attachment);
|
const attachmentDetailWidget = new AttachmentDetailWidget(attachment, false);
|
||||||
attachmentDetailWidget.isFullDetail = false;
|
|
||||||
|
|
||||||
this.child(attachmentDetailWidget);
|
this.child(attachmentDetailWidget);
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ const TPL = `
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<p>File <code class="file-path"></code> has been last modified on <span class="file-last-modified"></span>.</p>
|
<p>File <code class="file-path"></code> has been last modified on <span class="file-last-modified"></span>.</p>
|
||||||
|
|
||||||
<div style="display: flex; flex-direction: row; justify-content: space-evenly;">
|
<div style="display: flex; flex-direction: row; justify-content: space-evenly;">
|
||||||
<button class="btn btn-sm file-upload-button">Upload modified file</button>
|
<button class="btn btn-sm file-upload-button">Upload modified file</button>
|
||||||
|
|
||||||
|
@ -195,6 +195,12 @@ div.ui-tooltip {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-menu .disabled .disabled-tooltip {
|
||||||
|
pointer-events: all;
|
||||||
|
color: var(--menu-text-color);
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
.dropdown-menu a:hover:not(.disabled), .dropdown-item:hover:not(.disabled) {
|
.dropdown-menu a:hover:not(.disabled), .dropdown-item:hover:not(.disabled) {
|
||||||
color: var(--hover-item-text-color) !important;
|
color: var(--hover-item-text-color) !important;
|
||||||
background-color: var(--hover-item-background-color) !important;
|
background-color: var(--hover-item-background-color) !important;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user