Compare commits

...

6 Commits

Author SHA1 Message Date
zsviczian
1b62983016 1.8.18 2023-03-04 19:14:15 +01:00
zsviczian
52fb7ab546 updated excalidraw package 2023-02-26 17:35:13 +01:00
zsviczian
604bfbf23f 1.8.17 2023-02-26 17:28:59 +01:00
zsviczian
3c1a3c18c2 Merge pull request #1038 from tswwe/patch-2
Update zh-cn.ts
2023-02-26 17:16:20 +01:00
thxnder
f531c361de Update zh-cn.ts
keep up with en.ts
2023-02-26 18:20:37 +08:00
zsviczian
4609ea33bb 1.8.16 2023-02-21 21:11:23 +01:00
20 changed files with 522 additions and 196 deletions

View File

@@ -5,8 +5,7 @@ The script will convert your drawing into a slideshow presentation.
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.8.2")) {
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.8.17")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
@@ -14,6 +13,7 @@ if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.8.2")) {
//constants
const STEPCOUNT = 100;
const FRAME_SLEEP = 1; //milliseconds
const EDIT_ZOOMOUT = 0.7; //70% of original slide zoom, set to a value between 1 and 0
//utility & convenience functions
const doc = ea.targetView.ownerDocument;
@@ -26,34 +26,76 @@ const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
window.removePresentationEventHandlers?.();
//check if line or arrow is selected, if not inform the user and terminate presentation
const lineEl = ea.getViewSelectedElement();
let lineEl = ea.getViewElements().filter(el=>["line","arrow"].contains(el.type) && el.customData?.slideshow)[0];
const selectedEl = ea.getViewSelectedElement();
let preventHideAction = false;
if(lineEl && selectedEl && ["line","arrow"].contains(selectedEl.type)) {
api.setToast({
message:"Using selected line instead of hidden line. Note that there is a hidden presentation path for this drawing. Run the slideshow script without selecting any elements to access the hidden presentation path",
duration: 5000,
closable: true
})
preventHideAction = true;
lineEl = selectedEl;
}
if(!lineEl) lineEl = selectedEl;
if(!lineEl || !["line","arrow"].contains(lineEl.type)) {
new Notice("Please select the line or arrow for the presentation path");
api.setToast({
message:"Please select the line or arrow for the presentation path",
duration: 3000,
closable: true
})
return;
}
//goto fullscreen
if(app.isMobile) {
ea.viewToggleFullScreen(true);
} else {
await contentEl.webkitRequestFullscreen();
await sleep(500);
ea.setViewModeEnabled(true);
const gotoFullscreen = async () => {
if(app.isMobile) {
ea.viewToggleFullScreen(true);
} else {
await contentEl.webkitRequestFullscreen();
await sleep(500);
ea.setViewModeEnabled(true);
}
const deltaWidth = () => contentEl.clientWidth-api.getAppState().width;
let watchdog = 0;
while (deltaWidth()>50 && watchdog++<20) await sleep(100); //wait for Excalidraw to resize to fullscreen
contentEl.querySelector(".layer-ui__wrapper").addClass("excalidraw-hidden");
}
const deltaWidth = () => contentEl.clientWidth-api.getAppState().width;
let watchdog = 0;
while (deltaWidth()>50 && watchdog++<20) await sleep(100); //wait for Excalidraw to resize to fullscreen
contentEl.querySelector(".layer-ui__wrapper").addClass("excalidraw-hidden");
//hide the arrow and save the arrow color before doing so
const originalColor = {
strokeColor: lineEl.strokeColor,
backgroundColor: lineEl.backgroundColor
const originalProps = lineEl.customData?.slideshow?.hidden
? lineEl.customData.slideshow.originalProps
: {
strokeColor: lineEl.strokeColor,
backgroundColor: lineEl.backgroundColor,
locked: lineEl.locked,
};
let hidden = lineEl.customData?.slideshow?.hidden ?? false;
const hideArrow = async (setToHidden) => {
ea.clear();
ea.copyViewElementsToEAforEditing(ea.getViewElements().filter(el=>el.id === lineEl.id));
const el = ea.getElement(lineEl.id);
el.strokeColor = "transparent";
el.backgroundColor = "transparent";
const customData = el.customData;
if(setToHidden && !preventHideAction) {
el.locked = true;
el.customData = {
...customData,
slideshow: {
originalProps,
hidden: true
}
}
hidden = true;
} else {
if(customData) delete el.customData.slideshow;
hidden = false;
}
await ea.addElementsToView();
}
ea.copyViewElementsToEAforEditing([lineEl]);
ea.getElement(lineEl.id).strokeColor = "transparent";
ea.getElement(lineEl.id).backgroundColor = "transparent";
await ea.addElementsToView();
//----------------------------
//scroll-to-location functions
@@ -64,8 +106,11 @@ const slideCount = Math.floor(lineEl.points.length/2)-1;
const getNextSlide = (forward) => {
slide = forward
? slide < slideCount ? slide + 1 : 0
: slide <= 0 ? slideCount : slide - 1;
return {pointA:lineEl.points[slide*2], pointB:lineEl.points[slide*2+1]}
: slide <= 0 ? slideCount : slide - 1;
return {
pointA:lineEl.points[slide*2],
pointB:lineEl.points[slide*2+1]
}
}
const getSlideRect = ({pointA, pointB}) => {
@@ -91,22 +136,22 @@ const getSlideRect = ({pointA, pointB}) => {
}
let busy = false;
const scrollToNextRect = async ({left,top,right,bottom,nextZoom}) => {
const scrollToNextRect = async ({left,top,right,bottom,nextZoom},steps = STEPCOUNT) => {
let watchdog = 0;
while(busy && watchdog++<15) await(100);
if(busy && watchdog >= 15) return;
busy = true;
api.updateScene({appState:{shouldCacheIgnoreZoom:true}});
const {scrollX, scrollY, zoom} = api.getAppState();
const zoomStep = (zoom.value-nextZoom)/STEPCOUNT;
const xStep = (left+scrollX)/STEPCOUNT;
const yStep = (top+scrollY)/STEPCOUNT;
for(i=1;i<=STEPCOUNT;i++) {
const zoomStep = (zoom.value-nextZoom)/steps;
const xStep = (left+scrollX)/steps;
const yStep = (top+scrollY)/steps;
for(i=1;i<=steps;i++) {
api.updateScene({
appState: {
scrollX:scrollX-(xStep*i),
scrollY:scrollY-(yStep*i),
zoom:{value:zoom.value-zoomStep*i},
shouldCacheIgnoreZoom:true,
}
});
await sleep(FRAME_SLEEP);
@@ -125,66 +170,142 @@ const navigate = async (dir) => {
? slide<=prevSlide
: slide>=prevSlide;
if(shouldExit) {
if(!app.isMobile) await doc.exitFullscreen();
exitPresentation();
return;
}
if(slideNumberEl) slideNumberEl.innerText = `${slide+1}/${slideCount+1}`;
const nextRect = getSlideRect(nextSlide);
await scrollToNextRect(nextRect);
if(settingsModal) {
slideNumberDropdown.setValue(`${slide}`.padStart(3,"0"));
}
}
//--------------------------
// Settings Modal
//--------------------------
let settingsModal;
let slideNumberDropdown;
const presentationSettings = () => {
let dirty = false;
settingsModal = new ea.obsidian.Modal(app);
const getSlideNumberLabel = (i) => {
switch(i) {
case 0: return "1 - Start";
case slideCount: return `${i+1} - End`;
default: return `${i+1}`;
}
}
const getSlidesList = () => {
const options = {};
for(i=0;i<=slideCount;i++) {
options[`${i}`.padStart(3,"0")] = getSlideNumberLabel(i);
}
return options;
}
settingsModal.onOpen = () => {
settingsModal.contentEl.createEl("h1",{text: "Slideshow Actions"});
settingsModal.contentEl.createEl("p",{text: "To open this window double click presentation script icon or press ENTER during presentation."});
new ea.obsidian.Setting(settingsModal.contentEl)
.setName("Jump to slide")
.addDropdown(dropdown => {
slideNumberDropdown = dropdown;
dropdown
.addOptions(getSlidesList())
.setValue(`${slide}`.padStart(3,"0"))
.onChange(value => {
slide = parseInt(value)-1;
navigate("fwd");
})
})
if(!preventHideAction) {
new ea.obsidian.Setting(settingsModal.contentEl)
.setName("Hide navigation arrow after slideshow")
.setDesc("Toggle on: arrow hidden, toggle off: arrow visible")
.addToggle(toggle => toggle
.setValue(hidden)
.onChange(value => hideArrow(value))
)
}
new ea.obsidian.Setting(settingsModal.contentEl)
.setName("Edit current slide")
.setDesc("Pressing 'e' during the presentation will open the current slide for editing.")
.addButton(button => button
.setButtonText("Edit")
.onClick(async ()=>{
await hideArrow(false);
exitPresentation(true);
})
)
}
settingsModal.onClose = () => {
setTimeout(()=>delete settingsModal);
}
settingsModal.open();
contentEl.appendChild(settingsModal.containerEl);
}
//--------------------------------------
//Slideshow control
//--------------------------------------
//create slideshow controlpanel container
const top = contentEl.innerHeight;
const left = contentEl.innerWidth;
const containerEl = contentEl.createDiv({
cls: ["excalidraw","excalidraw-presentation-panel"],
attr: {
style: `
width: calc(var(--default-button-size)*3);
z-index:5;
position: absolute;
top:calc(${top}px - var(--default-button-size)*2);
left:calc(${left}px - var(--default-button-size)*3.5);`
}
});
const panelColumn = containerEl.createDiv({
cls: "panelColumn",
});
let controlPanelEl;
let slideNumberEl;
panelColumn.createDiv({
cls: ["Island", "buttonList"],
attr: {
style: `
height: calc(var(--default-button-size)*1.5);
width: 100%;
background: var(--island-bg-color);`,
}
}, el=>{
el.createEl("button",{
text: "<",
const createNavigationPanel = () => {
//create slideshow controlpanel container
const top = contentEl.innerHeight;
const left = contentEl.innerWidth;
controlPanelEl = contentEl.createDiv({
cls: ["excalidraw","excalidraw-presentation-panel"],
attr: {
style: `
margin-top: calc(var(--default-button-size)*0.25);
margin-left: calc(var(--default-button-size)*0.25);`
width: calc(var(--default-button-size)*3);
z-index:5;
position: absolute;
top:calc(${top}px - var(--default-button-size)*2);
left:calc(${left}px - var(--default-button-size)*3.5);`
}
}, button => button .onclick = () => navigate("bkwd"));
el.createEl("button",{
text: ">",
attr: {
style: `
margin-top: calc(var(--default-button-size)*0.25);
margin-right: calc(var(--default-button-size)*0.25);`
}
}, button => button.onclick = () => navigate("fwd"));
slideNumberEl = el.createEl("span",{
text: "1",
cls: ["ToolIcon__keybinding"],
})
});
});
const panelColumn = controlPanelEl.createDiv({
cls: "panelColumn",
});
panelColumn.createDiv({
cls: ["Island", "buttonList"],
attr: {
style: `
height: calc(var(--default-button-size)*1.5);
width: 100%;
background: var(--island-bg-color);`,
}
}, el=>{
el.createEl("button",{
text: "<",
attr: {
style: `
margin-top: calc(var(--default-button-size)*0.25);
margin-left: calc(var(--default-button-size)*0.25);`
}
}, button => button .onclick = () => navigate("bkwd"));
el.createEl("button",{
text: ">",
attr: {
style: `
margin-top: calc(var(--default-button-size)*0.25);
margin-right: calc(var(--default-button-size)*0.25);`
}
}, button => button.onclick = () => navigate("fwd"));
slideNumberEl = el.createEl("span",{
text: "1",
cls: ["ToolIcon__keybinding"],
})
});
}
//keyboard navigation
const keydownListener = (e) => {
@@ -201,9 +322,25 @@ const keydownListener = (e) => {
case "ArrowUp":
navigate("bkwd");
break;
}
case "Enter":
presentationSettings();
break;
case "End":
slide = slideCount - 1;
navigate("fwd");
break;
case "Home":
slide = -1;
navigate("fwd");
break;
case "e":
(async ()=>{
await hideArrow(false);
exitPresentation(true);
})()
break;
}
}
doc.addEventListener('keydown',keydownListener);
//slideshow panel drag
let pos1 = pos2 = pos3 = pos4 = 0;
@@ -214,19 +351,25 @@ const updatePosition = (deltaY = 0, deltaX = 0) => {
offsetLeft,
clientWidth: width,
clientHeight: height,
} = containerEl;
containerEl.style.top = (offsetTop - deltaY) + 'px';
containerEl.style.left = (offsetLeft - deltaX) + 'px';
} = controlPanelEl;
controlPanelEl.style.top = (offsetTop - deltaY) + 'px';
controlPanelEl.style.left = (offsetLeft - deltaX) + 'px';
}
const pointerUp = () => {
win.removeEventListener('pointermove', onDrag, true);
}
let dblClickTimer = 0;
const pointerDown = (e) => {
const now = Date.now();
pos3 = e.clientX;
pos4 = e.clientY;
win.addEventListener('pointermove', onDrag, true);
if(now-dblClickTimer < 400) {
presentationSettings();
}
dblClickTimer = now;
}
const onDrag = (e) => {
@@ -238,45 +381,108 @@ const onDrag = (e) => {
updatePosition(pos2, pos1);
}
containerEl.addEventListener('pointerdown', pointerDown, false);
win.addEventListener('pointerup', pointerUp, false);
const initializeEventListners = () => {
doc.addEventListener('keydown',keydownListener);
controlPanelEl.addEventListener('pointerdown', pointerDown, false);
win.addEventListener('pointerup', pointerUp, false);
//event listners for terminating the presentation
window.removePresentationEventHandlers = () => {
ea.onLinkClickHook = null;
containerEl.parentElement?.removeChild(containerEl);
if(!app.isMobile) win.removeEventListener('fullscreenchange', fullscreenListener);
doc.removeEventListener('keydown',keydownListener);
win.removeEventListener('pointerup',pointerUp);
contentEl.querySelector(".layer-ui__wrapper").removeClass("excalidraw-hidden");
delete window.removePresentationEventHandlers;
//event listners for terminating the presentation
window.removePresentationEventHandlers = () => {
ea.onLinkClickHook = null;
controlPanelEl.parentElement?.removeChild(controlPanelEl);
if(!app.isMobile) win.removeEventListener('fullscreenchange', fullscreenListener);
doc.removeEventListener('keydown',keydownListener);
win.removeEventListener('pointerup',pointerUp);
contentEl.querySelector(".layer-ui__wrapper")?.removeClass("excalidraw-hidden");
delete window.removePresentationEventHandlers;
}
ea.onLinkClickHook = () => {
exitPresentation();
return true;
};
if(!app.isMobile) {
win.addEventListener('fullscreenchange', fullscreenListener);
}
}
const exitPresentation = () => {
window.removePresentationEventHandlers?.();
if(app.isMobile) ea.viewToggleFullScreen(true);
else ea.setViewModeEnabled(false);
const exitPresentation = async (openForEdit = false) => {
if(openForEdit) ea.targetView.preventAutozoom();
if(!app.isMobile) await doc.exitFullscreen();
if(app.isMobile) {
ea.viewToggleFullScreen(true);
} else {
ea.setViewModeEnabled(false);
}
if(settingsModal) settingsModal.close();
ea.clear();
ea.copyViewElementsToEAforEditing(ea.getViewElements().filter(el=>el.id === lineEl.id));
ea.getElement(lineEl.id).strokeColor = originalColor.strokeColor;
ea.getElement(lineEl.id).backgroundColor = originalColor.backgroundColor;
ea.addElementsToView();
ea.selectElementsInView(ea.getElements());
const el = ea.getElement(lineEl.id);
if(!hidden) {
el.strokeColor = originalProps.strokeColor;
el.backgroundProps = originalProps.backgroundColor;
el.locked = openForEdit ? false : originalProps.locked;
}
await ea.addElementsToView();
ea.selectElementsInView([el]);
if(openForEdit) {
const nextSlide = getNextSlide(--slide);
let nextRect = getSlideRect(nextSlide);
const offsetW = (nextRect.right-nextRect.left)*(1-EDIT_ZOOMOUT)/2;
const offsetH = (nextRect.bottom-nextRect.top)*(1-EDIT_ZOOMOUT)/2
nextRect = {
left: nextRect.left-offsetW,
right: nextRect.right+offsetW,
top: nextRect.top-offsetH,
bottom: nextRect.bottom+offsetH,
nextZoom: nextRect.nextZoom*EDIT_ZOOMOUT > 0.1 ? nextRect.nextZoom*EDIT_ZOOMOUT : 0.1 //0.1 is the minimu zoom value
};
await scrollToNextRect(nextRect,1);
api.startLineEditor(
ea.getViewSelectedElement(),
[slide*2,slide*2+1]
);
}
window.removePresentationEventHandlers?.();
setTimeout(()=>{
//Resets pointer offsets. Ugly solution.
//During testing offsets were wrong after presentation, but don't know why.
//This should solve it even if they are wrong.
ea.targetView.refresh();
})
}
ea.onLinkClickHook = () => {
exitPresentation();
return true;
};
const fullscreenListener = (e) => {
e.preventDefault();
exitPresentation();
}
if(!app.isMobile) {
win.addEventListener('fullscreenchange', fullscreenListener);
//--------------------------
// Start presentation or open presentation settings on double click
//--------------------------
const start = async () => {
await gotoFullscreen();
await hideArrow(hidden);
createNavigationPanel();
initializeEventListners();
//navigate to the first slide on start
setTimeout(()=>navigate("fwd"));
}
//navigate to the first slide on start
setTimeout(()=>navigate("fwd"));
const timestamp = Date.now();
if(window.ExcalidrawSlideshow && (window.ExcalidrawSlideshow.script === utils.scriptFile.path) && (timestamp - window.ExcalidrawSlideshow.timestamp <400) ) {
if(window.ExcalidrawSlideshowStartTimer) {
clearTimeout(window.ExcalidrawSlideshowStartTimer);
delete window.ExcalidrawSlideshowStartTimer;
}
await start();
presentationSettings();
} else {
window.ExcalidrawSlideshow = {
script: utils.scriptFile.path,
timestamp
};
window.ExcalidrawSlideshowStartTimer = setTimeout(start,500);
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 322 KiB

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "1.8.14",
"version": "1.8.18",
"minAppVersion": "1.0.0",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",

View File

@@ -18,7 +18,7 @@
"license": "MIT",
"dependencies": {
"@types/lz-string": "^1.3.34",
"@zsviczian/excalidraw": "0.14.1-obsidian",
"@zsviczian/excalidraw": "0.14.2-obsidian-2",
"chroma-js": "^2.4.2",
"clsx": "^1.2.1",
"colormaster": "^1.2.1",

View File

@@ -22,6 +22,7 @@ import {
MAX_IMAGE_SIZE,
COLOR_NAMES,
fileid,
GITHUB_RELEASES,
} from "./Constants";
import { getDrawingFilename, } from "./utils/FileUtils";
import {
@@ -374,7 +375,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
const scene = {
type: "excalidraw",
version: 2,
source: "https://excalidraw.com",
source: GITHUB_RELEASES+PLUGIN_VERSION,
elements,
appState: {
theme: template?.appState?.theme ?? this.canvas.theme,
@@ -1864,7 +1865,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
* @returns
*/
verifyMinimumPluginVersion(requiredVersion: string): boolean {
return PLUGIN_VERSION === requiredVersion || isVersionNewerThanOther(PLUGIN_VERSION,requiredVersion);
return verifyMinimumPluginVersion(requiredVersion);
};
/**
@@ -2242,7 +2243,7 @@ export async function createPNG(
{
type: "excalidraw",
version: 2,
source: "https://excalidraw.com",
source: GITHUB_RELEASES+PLUGIN_VERSION,
elements,
appState: {
theme: forceTheme ?? template?.appState?.theme ?? canvasTheme,
@@ -2295,7 +2296,7 @@ export async function createSVG(
//createAndOpenDrawing
type: "excalidraw",
version: 2,
source: "https://excalidraw.com",
source: GITHUB_RELEASES+PLUGIN_VERSION,
elements,
appState: {
theme: forceTheme ?? template?.appState?.theme ?? canvasTheme,
@@ -2499,4 +2500,8 @@ export const cloneElement = (el: ExcalidrawElement):any => {
updated: Date.now(),
versionNonce: Math.floor(Math.random() * 1000000000),
}
}
export const verifyMinimumPluginVersion = (requiredVersion: string): boolean => {
return PLUGIN_VERSION === requiredVersion || isVersionNewerThanOther(PLUGIN_VERSION,requiredVersion);
}

View File

@@ -19,7 +19,7 @@ import {
FRONTMATTER_KEY_AUTOEXPORT,
DEVICE,
} from "./Constants";
import { _measureText } from "./ExcalidrawAutomate";
import { verifyMinimumPluginVersion, _measureText } from "./ExcalidrawAutomate";
import ExcalidrawPlugin from "./main";
import { JSON_parse } from "./Constants";
import { TextMode } from "./ExcalidrawView";
@@ -32,6 +32,7 @@ import {
getExportTheme,
getLinkParts,
hasExportTheme,
isVersionNewerThanOther,
LinkParts,
wrapTextAtCharLength,
} from "./utils/Utils";
@@ -267,6 +268,8 @@ export class ExcalidrawData {
return;
}
const saveVersion = this.scene.source.split("https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/")[1]??"1.8.16";
const elements = this.scene.elements;
for (const el of elements) {
if (el.boundElements) {
@@ -357,12 +360,17 @@ export class ExcalidrawData {
} catch (e) {}
});
const ellipseAndRhombusContainerWrapping = !isVersionNewerThanOther(saveVersion,"1.8.16");
//Remove from bound elements references that do not exist in the scene
const containers = elements.filter(
(container: any) =>
container.boundElements && container.boundElements.length > 0,
);
containers.forEach((container: any) => {
if(ellipseAndRhombusContainerWrapping && !container.customData?.legacyTextWrap) {
container.customData = {...container.customData, legacyTextWrap: true};
}
const filteredBoundElements = container.boundElements.filter(
(boundEl: any) => elements.some((el: any) => el.id === boundEl.id),
);

View File

@@ -44,6 +44,7 @@ import {
FRONTMATTER_KEY_EXPORT_DARK,
FRONTMATTER_KEY_EXPORT_TRANSPARENT,
DEVICE,
GITHUB_RELEASES,
} from "./Constants";
import ExcalidrawPlugin from "./main";
import { repositionElementsToCursor, ExcalidrawAutomate, getTextElementsMatchingQuery, cloneElement } from "./ExcalidrawAutomate";
@@ -107,7 +108,9 @@ import { ICONS, saveIcon } from "./menu/ActionIcons";
//import {WelcomeScreen} from "@zsviczian/excalidraw";
import { ExportDialog } from "./dialogs/ExportDialog";
import { getEA } from "src";
import { externalDragModifierType, internalDragModifierType, isALT, isCTRL, isMETA, isSHIFT, linkClickModifierType, mdPropModifier, ModifierKeys } from "./utils/ModifierkeyHelper";
import { emulateCTRLClickForLinks, externalDragModifierType, internalDragModifierType, isALT, isCTRL, isMETA, isSHIFT, linkClickModifierType, mdPropModifier, ModifierKeys } from "./utils/ModifierkeyHelper";
declare const PLUGIN_VERSION:string;
type SelectedElementWithLink = { id: string; text: string };
type SelectedImage = { id: string; fileId: FileId };
@@ -2556,7 +2559,7 @@ export default class ExcalidrawView extends TextFileView {
return {
type: "excalidraw",
version: 2,
source: "https://excalidraw.com",
source: GITHUB_RELEASES+PLUGIN_VERSION,
elements: el,
appState: {
theme: st.theme,
@@ -2866,7 +2869,7 @@ export default class ExcalidrawView extends TextFileView {
case "image-fullsize": msg = "Embed image @100%"; break;
case "link": msg = "Insert link"; break;
}
} else if(e.dataTransfer.types.includes("Files")) {
} else if(e.dataTransfer.types.length === 1 && e.dataTransfer.types.includes("Files")) {
//drag from OS file manager
msg = "External file"
} else {
@@ -2878,7 +2881,7 @@ export default class ExcalidrawView extends TextFileView {
}
}
if(this.draginfoDiv.innerText !== msg) this.draginfoDiv.innerText = msg;
const top = `${e.clientY-parseFloat(getComputedStyle(this.draginfoDiv).fontSize)*3}px`;
const top = `${e.clientY-parseFloat(getComputedStyle(this.draginfoDiv).fontSize)*8}px`;
const left = `${e.clientX-this.draginfoDiv.clientWidth/2}px`;
if(this.draginfoDiv.style.top !== top) this.draginfoDiv.style.top = top;
if(this.draginfoDiv.style.left !== left) this.draginfoDiv.style.left = left;
@@ -2961,11 +2964,20 @@ export default class ExcalidrawView extends TextFileView {
onChange: (et: ExcalidrawElement[], st: AppState) => {
const canvasColorChangeHook = () => {
if(this.plugin.ea.onCanvasColorChangeHook) {
this.plugin.ea.onCanvasColorChangeHook(
this.plugin.ea,
this,
st.viewBackgroundColor
)
try {
this.plugin.ea.onCanvasColorChangeHook(
this.plugin.ea,
this,
st.viewBackgroundColor
)
} catch (e) {
errorlog({
where: canvasColorChangeHook,
source: this.plugin.ea.onCanvasColorChangeHook,
error: e,
message: "ea.onCanvasColorChangeHook exception"
})
}
}
}
viewModeEnabled = st.viewModeEnabled;
@@ -3012,7 +3024,7 @@ export default class ExcalidrawView extends TextFileView {
const lib = {
type: "excalidrawlib",
version: 2,
source: "https://excalidraw.com",
source: GITHUB_RELEASES+PLUGIN_VERSION,
libraryItems: items,
};
this.plugin.setStencilLibrary(lib);
@@ -3448,12 +3460,7 @@ export default class ExcalidrawView extends TextFileView {
null,
null,
{id: element.id, text: element.link},
{
shiftKey: event.shitKey,
ctrlKey: event.ctrlKey || !(DEVICE.isIOS || DEVICE.isMacOS),
metaKey: event.metaKey || (DEVICE.isIOS || DEVICE.isMacOS),
altKey: event.altKey
}
emulateCTRLClickForLinks(event)
);
return;
},
@@ -3718,7 +3725,7 @@ export default class ExcalidrawView extends TextFileView {
const modalContainer = document.body.querySelector("div.modal-container");
if(modalContainer) return; //do not autozoom when the command palette or other modal container is envoked on iPad
const api = this.excalidrawAPI;
if (!api || !this.excalidrawRef || this.semaphores.isEditingText) {
if (!api || !this.excalidrawRef || this.semaphores.isEditingText || this.semaphores.preventAutozoom) {
return;
}
const maxZoom = this.plugin.settings.zoomToFitMaxLevel;

View File

@@ -1,5 +1,6 @@
import { customAlphabet } from "nanoid";
//This is only for backward compatibility because an early version of obsidian included an encoding to avoid fantom links from littering Obsidian graph view
declare const PLUGIN_VERSION:string;
export function JSON_parse(x: string): any {
return JSON.parse(x.replaceAll("&#91;", "["));
}
@@ -38,6 +39,7 @@ export const ROUNDNESS = { //should at one point publish @zsviczian/excalidraw/t
PROPORTIONAL_RADIUS: 2,
ADAPTIVE_RADIUS: 3,
} as const;
export const GITHUB_RELEASES = "https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/";
export const URLFETCHTIMEOUT = 1000;
export const PLUGIN_ID = "obsidian-excalidraw-plugin";
export const SCRIPT_INSTALL_CODEBLOCK = "excalidraw-script-install";
@@ -74,9 +76,9 @@ export const MAX_COLORS = 5;
export const COLOR_FREQ = 6;
export const RERENDER_EVENT = "excalidraw-embed-rerender";
export const BLANK_DRAWING =
'{"type":"excalidraw","version":2,"source":"https://excalidraw.com","elements":[],"appState":{"gridSize":null,"viewBackgroundColor":"#ffffff"}}';
`{"type":"excalidraw","version":2,"source":"${GITHUB_RELEASES+PLUGIN_VERSION}","elements":[],"appState":{"gridSize":null,"viewBackgroundColor":"#ffffff"}}`;
export const DARK_BLANK_DRAWING =
'{"type":"excalidraw","version":2,"source":"https://excalidraw.com","elements":[],"appState":{"theme":"dark","gridSize":null,"viewBackgroundColor":"#ffffff"}}';
`{"type":"excalidraw","version":2,"source":"${GITHUB_RELEASES+PLUGIN_VERSION}","elements":[],"appState":{"theme":"dark","gridSize":null,"viewBackgroundColor":"#ffffff"}}`;
export const FRONTMATTER = [
"---",
"",

View File

@@ -17,6 +17,39 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://cdn.ko-fi.com/cdn/kofi3.png?v=3" height=45></a></div>
`,
"1.8.18": `
## Fixed
- Text scaling issue introduced in 1.8.17
- [#1043](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1043): Error handling when ${String.fromCharCode(96)}onCanvasColorChangeHook${String.fromCharCode(96)} is executed. This is used in the [Dynamic Styling Script](https://youtu.be/LtR04fNTKTM).
`,
"1.8.17": `
## New from Excalidraw.com
- Improved text wrapping in the ellipse and diamond shapes [6172](https://github.com/excalidraw/excalidraw/pull/6172)
## New
- Updated slideshow script
<div class="excalidraw-videoWrapper"><div>
<iframe src="https://www.youtube.com/embed/mQ2eLk_0TV4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div></div>
## Fixed:
- "Save to..." in the Stencil Library menu now works as expected [#1032](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1032)
`,
"1.8.16": `
**!!! Modifier keys have changed, please review the table below !!!**
[Click this to see the new shortcuts overview image](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/excalidraw-modifiers.png)
## Fixed
- This version was extensively tested and developed on MacOS to remove usability issues.
- New command palette action to create a new drawing in a new tab
- Modifier keys to open links in the active window, splitting the current view to the right, in a new tab, or in a popout window now behave consistently both in Excalidraw and when clicking a drawing that is embedded in a markdown note.
- Drag & Drop properly works from within Obsidian, from a web browser, and from the OS file explorer
<div class="excalidraw-videoWrapper"><div>
<iframe src="https://www.youtube.com/embed/9HlipSIzRhc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div></div>
`,
"1.8.14":`
## Fixed
- text element link gets deleted when the drawing is reloaded

View File

@@ -139,7 +139,7 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
after: "",
},
{
field: "toCliboard",
field: "toClipboard",
code: "toClipboard(templatePath?: string): void;",
desc: "Copies current elements using template to clipboard, ready to be pasted into an excalidraw canvas",
after: "",

View File

@@ -81,7 +81,7 @@ export default {
OPEN_LINK: "Open selected text as link\n(SHIFT+CLICK to open in a new pane)",
EXPORT_EXCALIDRAW: "Export to an .Excalidraw file",
LINK_BUTTON_CLICK_NO_TEXT:
"Select a an ImageElement, or select a TextElement that contains an internal or external link.\n",
"Select a ImageElement, or select a TextElement that contains an internal or external link.\n",
FILENAME_INVALID_CHARS:
'File name cannot contain any of the following characters: * " \\ < > : | ? #',
FORCE_SAVE:

View File

@@ -1,20 +1,22 @@
import {
DEVICE,
FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS,
FRONTMATTER_KEY_CUSTOM_PREFIX,
FRONTMATTER_KEY_CUSTOM_URL_PREFIX,
} from "src/Constants";
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
// 简体中文
export default {
// main.ts
INSTALL_SCRIPT: "安装此脚本",
UPDATE_SCRIPT: "发现可用更新 - 点击安装",
UPDATE_SCRIPT: "可用更新 - 点击安装",
CHECKING_SCRIPT:
"检查脚本更新 - 点击重新安装",
"检查更新 - 点击重新安装",
UNABLETOCHECK_SCRIPT:
"检查更新失败 - 点击重新安装",
UPTODATE_SCRIPT:
"已安装最新脚本 - 点击重新安装",
"脚本已是最新 - 点击重新安装",
OPEN_AS_EXCALIDRAW: "打开为 Excalidraw 绘图",
TOGGLE_MODE: "在 Excalidraw 和 Markdown 模式之间切换",
CONVERT_NOTE_TO_EXCALIDRAW: "转换空白笔记为 Excalidraw 绘图",
@@ -30,58 +32,66 @@ export default {
TRANSCLUDE_MOST_RECENT: "嵌入最近编辑过的绘图(形如 ![[drawing]])到当前文档",
TOGGLE_LEFTHANDED_MODE: "切换为左手模式",
NEW_IN_NEW_PANE: "新建绘图 - 于新面板",
NEW_IN_NEW_TAB: "新建绘图 - 于新页签",
NEW_IN_ACTIVE_PANE: "新建绘图 - 于当前面板",
NEW_IN_POPOUT_WINDOW: "新建绘图 - 于新窗口",
NEW_IN_NEW_PANE_EMBED:
"新建绘图 - 于新面板 - 并将其嵌入(形如 ![[drawing]])到当前文档",
NEW_IN_NEW_TAB_EMBED:
"新建绘图 - 于新页签 - 并将其嵌入(形如 ![[drawing]])到当前文档",
NEW_IN_ACTIVE_PANE_EMBED:
"新建绘图 - 于当前面板 - 并将其嵌入(形如 ![[drawing]])到当前文档",
NEW_IN_POPOUT_WINDOW_EMBED: "新建绘图 - 于新窗口 - 并将其嵌入(形如 ![[drawing]])到当前文档",
EXPORT_SVG: "导出 SVG 文件到当前目录",
EXPORT_PNG: "导出 PNG 文件到当前目录",
TOGGLE_LOCK: "切换文本元素为原文模式RAW/预览模式PREVIEW",
EXPORT_SVG_WITH_SCENE: "导出 SVG 文件(包含 Scene到当前目录",
EXPORT_PNG_WITH_SCENE: "导出 PNG 文件(包含 Scene到当前目录",
TOGGLE_LOCK: "文本元素原文模式RAW⟺ 预览模式PREVIEW",
DELETE_FILE: "从库中删除所选图像(或 MD-Embed的源文件",
INSERT_LINK_TO_ELEMENT:
"复制所选元素的内部链接。按住 CTRL/CMD 可复制元素所在分组的内部链接。按住 SHIFT 可复制元素周围区域的内部链接。",
`复制所选元素的内部链接(形如 [[file#^elementID]])。\n按住 ${labelCTRL()} 可复制元素所在分组的内部链接(形如 [[file#^group=elementID]])。\n按住 ${labelSHIFT()} 可复制所选元素周围区域的内部链接(形如 [[file#^area=elementID]])。\n按住 ${labelALT()} 可观看视频演示。`,
INSERT_LINK_TO_ELEMENT_GROUP:
"复制所选元素所在分组的内部链接(形如 [[file#^group=elementID]]",
INSERT_LINK_TO_ELEMENT_AREA:
"复制所选元素周围区域的内部链接(形如 [[file#^area=elementID]]",
INSERT_LINK_TO_ELEMENT_NORMAL:
"复制所选元素的引用链接(形如 [[file#^elementID]]",
"复制所选元素的内部链接(形如 [[file#^elementID]]",
INSERT_LINK_TO_ELEMENT_ERROR: "未选择画布里的单个元素",
INSERT_LINK_TO_ELEMENT_READY: "链接已生成并复制到剪贴板",
INSERT_LINK: "插入文件的内部链接(形如 [[drawing]])到当前绘图",
INSERT_IMAGE: "插入图像(以图像形式嵌入)到当前绘图",
IMPORT_SVG: "插入 SVG 矢量图形到当前绘图(支持有限,尚不支持文本)",
INSERT_MD: "插入 Markdown 文档(以图像形式嵌入)到当前绘图",
INSERT_LATEX:
"插入 LaTeX 公式到当前绘图",
`插入 LaTeX 公式到当前绘图。按住 ${labelALT()} 可观看视频演示。`,
ENTER_LATEX: "输入 LaTeX 表达式",
READ_RELEASE_NOTES: "阅读本插件的最新发行版本说明",
TRAY_MODE: "切换绘图工具属性页为面板模式Panel/托盘模式Tray",
READ_RELEASE_NOTES: "阅读本插件的更新说明",
RUN_OCR: "OCR 识别涂鸦和图片里的文本并复制到剪贴板",
TRAY_MODE: "绘图工具属性页:面板模式 ⟺ 托盘模式",
SEARCH: "搜索文本",
RESET_IMG_TO_100: "重设图像元素的尺寸为 100%",
TEMPORARY_DISABLE_AUTOSAVE: "临时禁用自动保存功能,直到 Obsidian 退出(勿点,除非你清楚自己在干什么)",
TEMPORARY_ENABLE_AUTOSAVE: "恢复启用自动保存功能",
//ExcalidrawView.ts
INSTALL_SCRIPT_BUTTON: "安装或更新 Excalidraw 自动化脚本",
INSTALL_SCRIPT_BUTTON: "安装或更新 Excalidraw 脚本",
OPEN_AS_MD: "打开为 Markdown 文件",
SAVE_AS_PNG: "导出 PNG 到当前目录(按住 CTRL/CMD 设定导出路径)",
SAVE_AS_SVG: "导出 SVG 到当前目录(按住 CTRL/CMD 设定导出路径)",
SAVE_AS_PNG: `导出 PNG 到当前目录(按住 ${labelCTRL()} 设定导出路径;按住 SHIFT 在导出时包含 Scene`,
SAVE_AS_SVG: `导出 SVG 到当前目录(按住 ${labelCTRL()} 设定导出路径;按住 SHIFT 在导出时包含 Scene`,
OPEN_LINK: "打开所选元素里的链接 \n按住 SHIFT 在新面板打开)",
EXPORT_EXCALIDRAW: "导出为 .Excalidraw 文件",
LINK_BUTTON_CLICK_NO_TEXT:
"请选择一个含有链接的图形或文本元素。\n" +
"按住 SHIFT 并点击此按钮可在新面板中打开链接。\n" +
"您也可以直接在画布中按住 CTRL/CMD 并点击图形或文本元素来打开链接。",
"请选择一个含有链接的图形或文本元素。",
FILENAME_INVALID_CHARS:
'文件名不能含有以下符号: * " \\ < > : | ? #',
FORCE_SAVE:
"立刻保存绘图(并更新嵌入了该绘图的面板)。\n详见插件设置中的定期保存选项",
"保存绘图(并更新嵌入了该绘图的面板)",
RAW: "文本元素正以原文RAW模式显示链接。\n点击切换到预览PREVIEW模式",
PARSED:
"文本元素正以预览PREVIEW模式显示链接。\n点击切换到原文RAW模式",
NOFILE: "Excalidraw没有文件",
COMPATIBILITY_MODE:
"*.excalidraw 文件以兼容模式打开。转换为新格式以获得完整的插件功能。",
"*.excalidraw 文件以兼容模式打开。需要转换为新格式才能使用插件的全部功能。",
CONVERT_FILE: "转换为新格式",
//settings.ts
@@ -110,12 +120,13 @@ export default {
"Template.md则此项应设为 Excalidraw/Template.md也可省略 .md 扩展名,即 Excalidraw/Template。<br>" +
"如果您在兼容模式下使用 Excalidraw那么您的模板文件也必须是旧的 *.excalidraw 格式," +
"例如 Excalidraw/Template.excalidraw。",
SCRIPT_FOLDER_NAME: "Excalidraw 自动化脚本的文件夹",
SCRIPT_FOLDER_NAME: "Excalidraw 自动化脚本的文件夹(大小写敏感!)",
SCRIPT_FOLDER_DESC:
"此文件夹用于存放 Excalidraw 自动化脚本。" +
"您可以在 Obsidian 命令面板中执行这些脚本," +
"还可以为喜欢的脚本分配快捷键,就像为其他 Obsidian 命令分配快捷键一样。<br>" +
"该项不能设为库的根目录。",
SAVING_HEAD: "保存",
COMPRESS_NAME: "压缩 Excalidraw JSON",
COMPRESS_DESC:
"Excalidraw 绘图文件默认将元素记录为 JSON 格式。开启此项,可将元素的 JSON 数据以 BASE64 编码" +
@@ -127,15 +138,18 @@ export default {
"而当您切换回 Excalidraw 模式时,数据就会被再次编码。<br>" +
"开启此项后,对于之前已存在的未压缩的绘图文件," +
"需要重新打开并保存它们才能生效。",
AUTOSAVE_NAME: "定期保存",
AUTOSAVE_DESC:
"定期保存当前绘图。此功能专为移动设备设计 —— " +
"在桌面端,当您关闭 Excalidraw 或 Obsidian或者移动焦点到其他面板的时候软件是会自动保存的;" +
"但是在手机或平板上通过滑动手势退出 Obsidian 时,可能无法顺利触发自动保存。因此我添加了定期保存功能作为弥补。",
AUTOSAVE_INTERVAL_NAME: "定期保存时间间隔",
AUTOSAVE_INTERVAL_DESC:
"每隔多长时间执行一次保存。如果当前绘图没有发生改变,将不会触发保存。",
FILENAME_HEAD: "文件名",
AUTOSAVE_INTERVAL_DESKTOP_NAME: "桌面端定期保存时间间隔",
AUTOSAVE_INTERVAL_DESKTOP_DESC:
"每隔多长时间触发一次自动保存。但如果当前绘图没有发生改变,将不会触发自动保存。" +
"当 Obsidian 应用内的焦点离开活动文档(如关闭工作空间、点击菜单栏、切换到其他页签或面板等)的时候,会触发自动保存" +
"直接退出 Obsidian 应用(不管是终结进程还是点关闭按钮)不会触发自动保存。",
AUTOSAVE_INTERVAL_MOBILE_NAME: "移动端定期保存时间间隔",
AUTOSAVE_INTERVAL_MOBILE_DESC:
"建议在移动端设置更短的自动保存时间间隔。" +
"当 Obsidian 应用内的焦点离开活动文档(如关闭工作空间、点击菜单栏、切换到其他页签或面板等)的时候,会触发自动保存。" +
"直接退出 Obsidian 应用(在应用切换器中划掉)不会触发自动保存。此外,当您切换到其他应用时,有时候" +
"系统会自动清理 Obsidian 后台以释放资源。这种情况下Excalidraw 无法保存最新的变动。",
FILENAME_HEAD: "文件名",
FILENAME_DESC:
"<p>点击阅读" +
"<a href='https://momentjs.com/docs/#/displaying/format/'>日期和时间格式参考</a>。</p>",
@@ -185,15 +199,28 @@ export default {
DEFAULT_PEN_MODE_NAME: "触控笔模式Pen mode",
DEFAULT_PEN_MODE_DESC:
"打开绘图时,是否自动开启触控笔模式?",
ZOOM_TO_FIT_NAME: "自动缩放以适应面板调整",
ZOOM_TO_FIT_DESC: "调整面板大小时,自适应地缩放画布" +
DEFAULT_PINCHZOOM_NAME: "允许在触控笔模式下进行双指缩放",
DEFAULT_PINCHZOOM_DESC:
"在触控笔模式下使用自由画笔工具时,双指缩放可能造成干扰。<br>" +
"<b>开启: </b>允许在触控笔模式下进行双指缩放<br><b>关闭: </b>禁止在触控笔模式下进行双指缩放",
DEFAULT_WHEELZOOM_NAME: "鼠标滚轮缩放页面",
DEFAULT_WHEELZOOM_DESC:
`<b>开启:</b>鼠标滚轮为缩放页面,${labelCTRL()}+鼠标滚轮为滚动页面</br><b>关闭:</b>鼠标滚轮为滚动页面,${labelCTRL()}+鼠标滚轮为缩放页面`,
ZOOM_TO_FIT_NAME: "调节面板尺寸后自动缩放页面",
ZOOM_TO_FIT_DESC: "调节面板尺寸后,自适应地缩放页面" +
"<br><b>开启:</b>自动缩放。<br><b>关闭:</b>禁用自动缩放。",
ZOOM_TO_FIT_ONOPEN_NAME: "打开绘图时自动缩放页面",
ZOOM_TO_FIT_ONOPEN_DESC: "打开绘图文件时,自适应地缩放页面" +
"<br><b>开启:</b>自动缩放。<br><b>关闭:</b>禁用自动缩放。",
ZOOM_TO_FIT_MAX_LEVEL_NAME: "自动缩放的最高级别",
ZOOM_TO_FIT_MAX_LEVEL_DESC:
"自动缩放画布时,允许放大的最高级别。该值不能低于 0.550%)且不能超过 101000%)。",
LINKS_HEAD: "链接Links & 以文本形式嵌入到绘图中的文档Transclusion",
LINKS_DESC:
"按住 CTRL/CMD 并点击包含 <code>[[链接]]</code> 的文本元素可以打开其中的链接。<br>" +
`按住 ${labelCTRL()} 并点击包含 <code>[[链接]]</code> 的文本元素可以打开其中的链接。` +
"如果所选文本元素包含多个 <code>[[有效的内部链接]]</code> ,只会打开第一个链接;" +
"如果所选文本元素包含有效的 URL 链接 (如 <code>https://</code> 或 <code>http://</code>)" +
"插件会在浏览器中打开链接。<br>" +
@@ -201,45 +228,45 @@ export default {
"若您不愿绘图中的链接外观因此而变化,可使用 <code>[[内部链接|别名]]</code>。",
ADJACENT_PANE_NAME: "在相邻面板中打开",
ADJACENT_PANE_DESC:
"按住 CTRL/CMD + SHIFT 并点击绘图里的内部链接时,插件默认会在新面板中打开该链接。<br>" +
`按住 ${labelCTRL()}+${labelSHIFT()} 并点击绘图里的内部链接时,插件默认会在新面板中打开该链接。<br>` +
"若开启此项Excalidraw 会先尝试寻找已有的相邻面板(按照右侧、左侧、上方、下方的顺序)," +
"并在其中打开该链接。如果找不到," +
"再在新面板中打开。",
MAINWORKSPACE_PANE_NAME: "在主工作区中打开",
MAINWORKSPACE_PANE_DESC:
"按住 CTRL/CMD + SHIFT 并点击绘图里的内部链接时,插件默认会在当前窗口的新面板中打开该链接。<br>" +
`按住 ${labelCTRL()}+${labelSHIFT()} 并点击绘图里的内部链接时,插件默认会在当前窗口的新面板中打开该链接。<br>` +
"若开启此项Excalidraw 会在主工作区的面板中打开该链接。",
LINK_BRACKETS_NAME: "在链接的两侧显示 [[中括号]]",
LINK_BRACKETS_NAME: "在链接的两侧显示 <code>[[中括号]]</code>",
LINK_BRACKETS_DESC: `${
"文本元素处于预览模式时,在内部链接的两侧显示中括号。<br>" +
"文本元素处于预览PREVIEW模式时,在内部链接的两侧显示中括号。<br>" +
"您可为某个绘图单独设置此项,方法是在其 frontmatter 中添加形如 <code>"
}${FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS}: true/false</code> 的键值对。`,
LINK_PREFIX_NAME: "内部链接的前缀",
LINK_PREFIX_DESC: `${
"文本元素处于预览模式时,如果其中包含链接,则添加此前缀。<br>" +
"文本元素处于预览PREVIEW模式时,如果其中包含链接,则添加此前缀。<br>" +
"您可为某个绘图单独设置此项,方法是在其 frontmatter 中添加形如 <code>"
}${FRONTMATTER_KEY_CUSTOM_PREFIX}: "📍 "</code> 的键值对。`,
URL_PREFIX_NAME: "外部链接的前缀",
URL_PREFIX_DESC: `${
"文本元素处于预览模式时,如果其中包含外部链接,则添加此前缀。<br>" +
"文本元素处于预览PREVIEW模式时,如果其中包含外部链接,则添加此前缀。<br>" +
"您可为某个绘图单独设置此项,方法是在其 frontmatter 中添加形如 <code>"
}${FRONTMATTER_KEY_CUSTOM_URL_PREFIX}: "🌐 "</code> 的键值对。`,
PARSE_TODO_NAME: "解析任务列表Todo",
PARSE_TODO_NAME: "待办任务Todo",
PARSE_TODO_DESC: "将文本元素中的 <code>- [ ]</code> 和 <code>- [x]</code> 前缀显示为方框。",
TODO_NAME: "未完成的 Todo 项目",
TODO_DESC: "未完成的 Todo 项目的符号",
DONE_NAME: "已完成的 Todo 项目",
DONE_DESC: "已完成的 Todo 项目的符号",
TODO_NAME: "未完成项目",
TODO_DESC: "未完成的待办项目的符号",
DONE_NAME: "已完成项目",
DONE_DESC: "已完成的待办项目的符号",
HOVERPREVIEW_NAME: "鼠标悬停预览内部链接",
HOVERPREVIEW_DESC:
"<b>开启:</b>在 Excalidraw <u>阅读模式View</u>下,鼠标悬停在 <code>[[内部链接]]</code> 上即可预览;" +
`<b>开启:</b>在 Excalidraw <u>阅读模式View</u>下,鼠标悬停在 <code>[[内部链接]]</code> 上即可预览;` +
"而在<u>普通模式Normal</u>下, 鼠标悬停在内部链接右上角的蓝色标识上即可预览。<br> " +
"<b>关闭:</b>鼠标悬停在 <code>[[内部链接]]</code> 上,并且按住 CTRL/CMD 时进行预览。",
`<b>关闭:</b>鼠标悬停在 <code>[[内部链接]]</code> 上,并且按住 ${labelCTRL()} 才能预览。`,
LINKOPACITY_NAME: "链接标识的透明度",
LINKOPACITY_DESC:
"含有链接的元素,其右上角的链接标识的透明度。介于 0全透明到 1不透明之间。",
LINK_CTRL_CLICK_NAME:
"按住 CTRL/CMD 并点击含有 [[链接]] 或 [别名](链接) 的文本来打开链接",
`按住 ${labelCTRL()} 并点击含有 [[链接]] 或 [别名](链接) 的文本来打开链接`,
LINK_CTRL_CLICK_DESC:
"如果此功能影响到您使用某些原版 Excalidraw 功能,可将其关闭。" +
"关闭后,您只能通过绘图面板标题栏中的链接按钮来打开链接。",
@@ -257,13 +284,16 @@ export default {
PAGE_TRANSCLUSION_CHARCOUNT_DESC:
"以 <code>![[内部链接]]</code> 或 <code>![](内部链接)</code> 的形式将文档以文本形式嵌入到绘图中时," +
"该文档在绘图中可显示的最大字符数量。",
QUOTE_TRANSCLUSION_REMOVE_NAME: "隐藏 Transclusion 行首的引用符号",
QUOTE_TRANSCLUSION_REMOVE_DESC: "不显示 Transclusion 中每一行行首的 > 符号,以提高纯文本 Transclusion 的可读性。<br>" +
"<b>开启:</b>隐藏 > 符号<br><b>关闭:</b>不隐藏 > 符号(注意,由于 Obsidian API 的原因,首行行首的 > 符号不会被隐藏)",
GET_URL_TITLE_NAME: "使用 iframly 获取页面标题",
GET_URL_TITLE_DESC:
"拖放链接到 Excalidraw 时,使用 <code>http://iframely.server.crestify.com/iframely?url=</code> 来获取页面的标题。",
MD_HEAD: "以图像形式嵌入到绘图中的 Markdown 文档MD-Embed",
MD_HEAD_DESC:
"您还可以将 Markdown 文档以图像形式(而非文本形式)嵌入到绘图中。" +
"方法是按住 CTRL/CMD 并从文件管理器中把文档拖入绘图,或者执行“以图像形式嵌入”系列命令。",
`方法是按住 ${labelCTRL()} 并从文件管理器中把文档拖入绘图,或者执行“以图像形式嵌入”系列命令。`,
MD_TRANSCLUDE_WIDTH_NAME: "MD-Embed 的默认宽度",
MD_TRANSCLUDE_WIDTH_DESC:
"MD-Embed 的宽度。该选项会影响到折行,以及图像元素的宽度。<br>" +
@@ -380,6 +410,10 @@ export default {
MATHJAX_DESC: "如果您在绘图中使用 LaTeX插件需要从服务器获取并加载一个 javascript 库。" +
"如果您的网络无法访问某些库服务器,可以尝试通过此选项更换库服务器。"+
"更改此选项后,您可能需要重启 Obsidian 来使其生效。",
NONSTANDARD_HEAD: "非 Excalidraw.com 官方支持的特性",
NONSTANDARD_DESC: "这些特性不受 Excalidraw.com 官方支持。当导出绘图到 Excalidraw.com 时,这些特性将会发生变化。",
CUSTOM_PEN_NAME: "自定义画笔的数量",
CUSTOM_PEN_DESC: "在画布上的 Obsidian 菜单旁边切换自定义画笔。长按画笔按钮可以修改其样式。",
EXPERIMENTAL_HEAD: "实验性功能",
EXPERIMENTAL_DESC:
"以下部分设置不会立即生效,需要刷新文件资源管理器或者重启 Obsidian 才会生效。",
@@ -409,9 +443,22 @@ export default {
"选择库文件夹中的一个 .ttf, .woff 或 .woff2 字体文件作为本地字体文件。" +
"若未选择文件,则使用默认的 Virgil 字体。",
SCRIPT_SETTINGS_HEAD: "已安装脚本的设置",
TASKBONE_HEAD: "Taskbone OCR光学符号识别",
TASKBONE_DESC: "这是一个将 OCR 融入 Excalidraw 的实验性功能。请注意Taskbone 是一项独立的外部服务,而不是由 Excalidraw 或 Obsidian-excalidraw-plugin 项目提供的。" +
"OCR 能够对画布上用自由画笔工具写下的涂鸦或者嵌入的图像进行文本识别,并将识别出来的文本写入绘图文件的 frontmatter同时复制到剪贴板。" +
"之所以要写入 frontmatter 是为了便于您在 Obsidian 中能够搜索到这些文本。" +
"注意,识别的过程不是在本地进行的,而是通过在线 API图像会被上传到 taskbone 的服务器(仅用于识别目的)。如果您对此敏感,请不要使用这个功能。",
TASKBONE_ENABLE_NAME: "启用 Taskbone",
TASKBONE_ENABLE_DESC: "启用这个功能意味着你同意 Taskbone <a href='https://www.taskbone.com/legal/terms/' target='_blank'>条款及细则</a> 以及 " +
"<a href='https://www.taskbone.com/legal/privacy/' target='_blank'>隐私政策</a>.",
TASKBONE_APIKEY_NAME: "Taskbone API Key",
TASKBONE_APIKEY_DESC: "Taskbone 的免费 API key 提供了一定数量的每月识别次数。如果您非常频繁地使用此功能,或者想要支持 " +
"Taskbone 的开发者您懂的没有人能用爱发电Taskbone 开发者也需要投入资金才能持续运行这项 OCR 服务)您可以" +
"到 <a href='https://www.taskbone.com/' target='_blank'>taskbone.com</a> 购买一个商用 API key。购买后请将它填写到旁边这个文本框里替换掉原本自动生成的免费 API key。",
//openDrawings.ts
SELECT_FILE: "选择一个文件后按回车。",
SELECT_FILE_WITH_OPTION_TO_SCALE: `选择一个文件后按回车,或者 ${labelSHIFT()}+${labelMETA()}+ENTER 以 100% 尺寸插入。`,
NO_MATCH: "查询不到匹配的文件。",
SELECT_FILE_TO_LINK: "选择要插入(链接)到当前绘图中的文件。",
SELECT_DRAWING: "选择要插入(以图像形式嵌入)到当前绘图中的图像。",
@@ -436,4 +483,6 @@ export default {
GOTO_FULLSCREEN: "进入全屏模式",
EXIT_FULLSCREEN: "退出全屏模式",
TOGGLE_FULLSCREEN: "切换全屏模式",
OPEN_LINK_CLICK: "打开所选的图形或文本元素里的链接",
OPEN_LINK_PROPS: "编辑所选 MD-Embed 的内部链接,或者打开所选的图形或文本元素里的链接"
};

View File

@@ -41,6 +41,7 @@ import {
VIRGIL_FONT,
VIRGIL_DATAURL,
EXPORT_TYPES,
DEVICE,
} from "./Constants";
import ExcalidrawView, { TextMode, getTextMode } from "./ExcalidrawView";
import {
@@ -106,7 +107,7 @@ import { ScriptInstallPrompt } from "./dialogs/ScriptInstallPrompt";
import { check } from "prettier";
import Taskbone from "./ocr/Taskbone";
import { hoverEvent_Legacy, initializeMarkdownPostProcessor_Legacy, markdownPostProcessor_Legacy, observer_Legacy } from "./MarkdownPostProcessor_Legacy";
import { isCTRL, PaneTarget } from "./utils/ModifierkeyHelper";
import { emulateCTRLClickForLinks, isCTRL, linkClickModifierType, PaneTarget } from "./utils/ModifierkeyHelper";
declare module "obsidian" {
@@ -706,8 +707,8 @@ export default class ExcalidrawPlugin extends Plugin {
this.addRibbonIcon(ICON_NAME, t("CREATE_NEW"), async (e) => {
this.createAndOpenDrawing(
getDrawingFilename(this.settings),
isCTRL(e)?"new-pane":"active-pane",
); //.ctrlKey||e.metaKey);
linkClickModifierType(emulateCTRLClickForLinks(e)),
);
});
const fileMenuHandlerCreateNew = (menu: Menu, file: TFile) => {
@@ -715,7 +716,7 @@ export default class ExcalidrawPlugin extends Plugin {
item
.setTitle(t("CREATE_NEW"))
.setIcon(ICON_NAME)
.onClick(() => {
.onClick((e) => {
let folderpath = file.path;
if (file instanceof TFile) {
folderpath = normalizePath(
@@ -724,7 +725,7 @@ export default class ExcalidrawPlugin extends Plugin {
}
this.createAndOpenDrawing(
getDrawingFilename(this.settings),
"active-pane",
linkClickModifierType(emulateCTRLClickForLinks(e)),
folderpath,
);
});
@@ -2166,6 +2167,9 @@ export default class ExcalidrawPlugin extends Plugin {
active: boolean = false,
subpath?: string
) {
if(location === "md-properties") {
location = "new-tab";
}
let leaf: WorkspaceLeaf;
if(location === "popout-window") {
leaf = app.workspace.openPopoutLeaf();

View File

@@ -7,7 +7,7 @@ import {
TextComponent,
TFile,
} from "obsidian";
import { VIEW_TYPE_EXCALIDRAW } from "./Constants";
import { GITHUB_RELEASES, VIEW_TYPE_EXCALIDRAW } from "./Constants";
import ExcalidrawView from "./ExcalidrawView";
import { t } from "./lang/helpers";
import type ExcalidrawPlugin from "./main";
@@ -124,6 +124,8 @@ export interface ExcalidrawSettings {
numberOfCustomPens: number;
}
declare const PLUGIN_VERSION:string;
export const DEFAULT_SETTINGS: ExcalidrawSettings = {
folder: "Excalidraw",
embedUseExcalidrawFolder: false,
@@ -195,7 +197,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
library2: {
type: "excalidrawlib",
version: 2,
source: "https://excalidraw.com",
source: GITHUB_RELEASES+PLUGIN_VERSION,
libraryItems: [],
},
//patchCommentBlock: true,

View File

@@ -1,9 +1,11 @@
import { GITHUB_RELEASES } from "src/Constants";
import { ExcalidrawGenericElement } from "./ExcalidrawElement";
declare const PLUGIN_VERSION:string;
class ExcalidrawScene {
type = "excalidraw";
version = 2;
source = "https://excalidraw.com";
source = GITHUB_RELEASES+PLUGIN_VERSION;
elements: ExcalidrawGenericElement[] = [];
constructor(elements:any = []) {

View File

@@ -30,7 +30,7 @@ export function splitFolderAndFilename(filepath: string): {
* @param data
* @param filename
*/
export function download(encoding: string, data: any, filename: string) {
export const download = (encoding: string, data: any, filename: string) => {
const element = document.createElement("a");
element.setAttribute("href", (encoding ? `${encoding},` : "") + data);
element.setAttribute("download", filename);

View File

@@ -44,4 +44,13 @@ export const internalDragModifierType = (ev: KeyEvent):InternalDragAction => {
if(!isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image";
if(scaleToFullsizeModifier(ev)) return "image-fullsize";
return "link";
}
export const emulateCTRLClickForLinks = (e:KeyEvent) => {
return {
shiftKey: e.shiftKey,
ctrlKey: e.ctrlKey || !(DEVICE.isIOS || DEVICE.isMacOS),
metaKey: e.metaKey || (DEVICE.isIOS || DEVICE.isMacOS),
altKey: e.altKey
}
}

View File

@@ -25,7 +25,6 @@ import { compressToBase64, decompressFromBase64 } from "lz-string";
import { getIMGFilename } from "./FileUtils";
import ExcalidrawScene from "../svgToExcalidraw/elements/ExcalidrawScene";
import { IMAGE_TYPES } from "../Constants";
import { dataURLToFile } from "@zsviczian/excalidraw/types/data/blob";
declare const PLUGIN_VERSION:string;

View File

@@ -2272,10 +2272,10 @@
dependencies:
"@zerollup/ts-helpers" "^1.7.18"
"@zsviczian/excalidraw@0.14.1-obsidian":
"integrity" "sha512-bSc/JIH4v8tfr9SdPa9bkB7F2B+TnOuf3Dm1RYakmHjdcTPru4tG2zfGfC3glyP1zZDuR83ttidiJEgglCHwwQ=="
"resolved" "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.14.1-obsidian.tgz"
"version" "0.14.1-obsidian"
"@zsviczian/excalidraw@0.14.2-obsidian-2":
"integrity" "sha512-ij0HN4LRbBP1Snk6wzlSnOJuRDGDiTmxaC+xs7whr199kXpRp60a9d+vgv4rTH2NTCWF2yqttaBe5/SQ+OeNqg=="
"resolved" "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.14.2-obsidian-2.tgz"
"version" "0.14.2-obsidian-2"
"abab@^2.0.3", "abab@^2.0.5":
"integrity" "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q=="
@@ -3143,7 +3143,7 @@
"resolved" "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz"
"version" "2.0.16"
"colormaster@1.2.1":
"colormaster@^1.2.1":
"integrity" "sha512-+G9g1fOW+TC4+y5IWYI4f7mb+l+oEto/N3qJTFrJNaayFpidZUWhZJGxoSJjhNMIiTttcU1kf4XSyfxHyHkQYw=="
"resolved" "https://registry.npmjs.org/colormaster/-/colormaster-1.2.1.tgz"
"version" "1.2.1"