Skip to content

Commit

Permalink
Merge pull request #15 from oleeskild/feature/ExcaliDrawSupport
Browse files Browse the repository at this point in the history
Feature/excali draw support
  • Loading branch information
oleeskild authored Mar 14, 2022
2 parents d0b3b48 + 4b24d09 commit 438f118
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 52 deletions.
73 changes: 46 additions & 27 deletions PublishModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class PublishModal {
deletedContainer: HTMLElement;
unpublishedContainer: HTMLElement;

constructor(app: App, siteManager: IDigitalGardenSiteManager, publisher: IPublisher,
constructor(app: App, siteManager: IDigitalGardenSiteManager, publisher: IPublisher,
settings: DigitalGardenSettings) {
this.modal = new Modal(app)
this.siteManager = siteManager;
Expand All @@ -25,19 +25,19 @@ export class PublishModal {
this.initialize();
}

createCollapsable(title:string): HTMLElement{
const toggleHeader = this.modal.contentEl.createEl("h3", { text: `➡️️ ${title}`, attr:{class:"collapsable collapsed"} });
createCollapsable(title: string): HTMLElement {
const toggleHeader = this.modal.contentEl.createEl("h3", { text: `➕️ ${title}`, attr: { class: "collapsable collapsed" } });
const toggledList = this.modal.contentEl.createEl("ul");
toggledList.hide();

toggleHeader.onClickEvent(() => {
if(toggledList.isShown()){
toggleHeader.textContent = `➡️️ ${title}`;
if (toggledList.isShown()) {
toggleHeader.textContent = `➕️ ${title}`;
toggledList.hide();
toggleHeader.removeClass("open");
toggleHeader.addClass("collapsed");
}else{
toggleHeader.textContent = `⬇️ ${title}`;
} else {
toggleHeader.textContent = ` ${title}`;
toggledList.show()
toggleHeader.removeClass("collapsed");
toggleHeader.addClass("open");
Expand All @@ -54,29 +54,37 @@ export class PublishModal {
this.modal.contentEl.createEl("h2", { text: "Publication Status" });

this.publishedContainer = this.createCollapsable("Published");
this.changedContainer= this.createCollapsable("Changed");
this.changedContainer = this.createCollapsable("Changed");
this.deletedContainer = this.createCollapsable("Deleted from vault");
this.unpublishedContainer = this.createCollapsable("Unpublished");
this.modal.onOpen = ()=>this.populateWithNotes();
this.modal.onClose = ()=>this.clearView();

this.modal.onOpen = () => this.populateWithNotes();
this.modal.onClose = () => this.clearView();
}

async clearView(){
this.publishedContainer.childNodes.forEach(node=>node.remove());
this.changedContainer.childNodes.forEach(node=>node.remove());
this.deletedContainer.childNodes.forEach(node=>node.remove());
this.unpublishedContainer.childNodes.forEach(node=>node.remove());
async clearView() {
while (this.publishedContainer.lastElementChild) {
this.publishedContainer.removeChild(this.publishedContainer.lastElementChild);
}
while (this.changedContainer.lastElementChild) {
this.changedContainer.removeChild(this.changedContainer.lastElementChild);
}
while (this.deletedContainer.lastElementChild) {
this.deletedContainer.removeChild(this.deletedContainer.lastElementChild);
}
while (this.unpublishedContainer.lastElementChild) {
this.unpublishedContainer.removeChild(this.unpublishedContainer.lastElementChild);
}
}
async populateWithNotes(){
async populateWithNotes() {
const publishStatus = await this.buildPublishStatus();
publishStatus.publishedNotes.map(file=>this.publishedContainer.createEl("li", { text: file.path}));
publishStatus.unpublishedNotes.map(file=>this.unpublishedContainer.createEl("li", { text: file.path}));
publishStatus.changedNotes.map(file=>this.changedContainer.createEl("li", { text: file.path}));
publishStatus.deletedNotePaths.map(path=>this.deletedContainer.createEl("li", { text: path}));
publishStatus.publishedNotes.map(file => this.publishedContainer.createEl("li", { text: file.path }));
publishStatus.unpublishedNotes.map(file => this.unpublishedContainer.createEl("li", { text: file.path }));
publishStatus.changedNotes.map(file => this.changedContainer.createEl("li", { text: file.path }));
publishStatus.deletedNotePaths.map(path => this.deletedContainer.createEl("li", { text: path }));
}

async buildPublishStatus(){
async buildPublishStatus(): Promise<PublishStatus> {
const unpublishedNotes: Array<TFile> = [];
const publishedNotes: Array<TFile> = [];
const changedNotes: Array<TFile> = [];
Expand All @@ -91,27 +99,38 @@ export class PublishModal {

const localHash = generateBlobHash(content);
const remoteHash = remoteNoteHashes[file.path];
if(!remoteHash) {
if (!remoteHash) {
unpublishedNotes.push(file);
}
else if(remoteHash === localHash) {
else if (remoteHash === localHash) {
publishedNotes.push(file);
}
else{
else {
changedNotes.push(file);
}
}

Object.keys(remoteNoteHashes).forEach(key => {
if(!marked.find(f => f.path === key)) {
if (!marked.find(f => f.path === key)) {
deletedNotePaths.push(key);
}
});

return {unpublishedNotes, publishedNotes, changedNotes, deletedNotePaths};
unpublishedNotes.sort((a, b) => a.path > b.path ? 1 : -1);
publishedNotes.sort((a, b) => a.path > b.path ? 1 : -1);
changedNotes.sort((a, b) => a.path > b.path ? 1 : -1);
deletedNotePaths.sort((a, b) => a > b ? 1 : -1);
return { unpublishedNotes, publishedNotes, changedNotes, deletedNotePaths };
}

open() {
this.modal.open();
}
}

interface PublishStatus{
unpublishedNotes: Array<TFile>;
publishedNotes: Array<TFile>;
changedNotes: Array<TFile>;
deletedNotePaths: Array<string>;
}
59 changes: 38 additions & 21 deletions Publisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import { Base64 } from "js-base64";
import { Octokit } from "@octokit/core";
import { arrayBufferToBase64, generateUrlPath } from "utils";
import { vallidatePublishFrontmatter } from "Validator";
import slugify from "@sindresorhus/slugify";
import { title } from "process";
import { excaliDrawBundle, excalidraw } from "./constants";


export interface IPublisher{
export interface IPublisher {
publish(file: TFile): Promise<boolean>;
getFilesMarkedForPublishing(): Promise<TFile[]>;
generateMarkdown(file: TFile): Promise<string>;
Expand Down Expand Up @@ -192,6 +191,7 @@ export default class Publisher {
let transcludedText = text;
const transcludedRegex = /!\[\[(.*?)\]\]/g;
const transclusionMatches = text.match(transcludedRegex);
let numberOfExcaliDraws = 0;
if (transclusionMatches) {
for (let i = 0; i < transclusionMatches.length; i++) {
try {
Expand All @@ -200,22 +200,37 @@ export default class Publisher {
const tranclusionFilePath = getLinkpath(tranclusionFileName);
const linkedFile = this.metadataCache.getFirstLinkpathDest(tranclusionFilePath, filePath);

if (linkedFile.extension !== "md") {
continue;
}
if (linkedFile.name.endsWith(".excalidraw.md")) {
let fileText = await this.vault.cachedRead(linkedFile);
const start = fileText.indexOf('```json') + "```json".length;
const end = fileText.lastIndexOf('```')
const excaliDrawJson = JSON.parse(fileText.slice(start, end));

const drawingId = linkedFile.name.split(" ").join("_").replace(".", "") + numberOfExcaliDraws;
let excaliDrawCode = "";
if(++numberOfExcaliDraws === 1){
excaliDrawCode += excaliDrawBundle;
}

excaliDrawCode += excalidraw(JSON.stringify(excaliDrawJson), drawingId);

transcludedText = transcludedText.replace(transclusionMatch, excaliDrawCode);

}else if (linkedFile.extension === "md") {

let fileText = await this.vault.cachedRead(linkedFile);
let fileText = await this.vault.cachedRead(linkedFile);

//Remove frontmatter from transclusion
fileText = fileText.replace(/^---\n([\s\S]*?)\n---/g, "");
//Remove frontmatter from transclusion
fileText = fileText.replace(/^---\n([\s\S]*?)\n---/g, "");

const header = this.generateTransclusionHeader(headerName, linkedFile);
const header = this.generateTransclusionHeader(headerName, linkedFile);

const headerSection = header ? `${header}\n` : '';
const headerSection = header ? `${header}\n` : '';

fileText = `\n<div class="transclusion">\n\n` + headerSection + fileText + '\n</div>\n'
//This should be recursive up to a certain depth
transcludedText = transcludedText.replace(transclusionMatch, fileText);
fileText = `\n<div class="transclusion">\n\n` + headerSection + fileText + '\n</div>\n'
//This should be recursive up to a certain depth
transcludedText = transcludedText.replace(transclusionMatch, fileText);
}
} catch {
continue;
}
Expand All @@ -228,18 +243,20 @@ export default class Publisher {

async createBase64Images(text: string, filePath: string): Promise<string> {
let imageText = text;
const imageRegex = /!\[\[(.*?)(\.(png|jpg|jpeg|gif))\]\]/g;
const imageRegex = /!\[\[(.*?)(\.(png|jpg|jpeg|gif))\|(.*?)\]\]|!\[\[(.*?)(\.(png|jpg|jpeg|gif))\]\]/g;
const imageMatches = text.match(imageRegex);
if (imageMatches) {
for (let i = 0; i < imageMatches.length; i++) {
try {
const imageMatch = imageMatches[i];
const imageName = imageMatch.substring(imageMatch.indexOf('[') + 2, imageMatch.indexOf(']'));

let [imageName, size] = imageMatch.substring(imageMatch.indexOf('[') + 2, imageMatch.indexOf(']')).split("|");
const imagePath = getLinkpath(imageName);
const linkedFile = this.metadataCache.getFirstLinkpathDest(imagePath, filePath);
const image = await this.vault.readBinary(linkedFile);
const imageBase64 = arrayBufferToBase64(image)
const imageMarkdown = `![${imageName}](data:image/${linkedFile.extension};base64,${imageBase64})`;
const name = size ? `${imageName}|${size}` : imageName;
const imageMarkdown = `![${name}](data:image/${linkedFile.extension};base64,${imageBase64})`;
imageText = imageText.replace(imageMatch, imageMarkdown);
} catch {
continue;
Expand All @@ -251,15 +268,15 @@ export default class Publisher {
}

generateTransclusionHeader(headerName: string, transcludedFile: TFile) {
if(!headerName) {
if (!headerName) {
return headerName;
}

const titleVariable = "{{title}}";
if (headerName && headerName.indexOf(titleVariable) > -1) {
headerName = headerName.replace(titleVariable, transcludedFile.basename);
}

//Defaults to h1
if (headerName && !headerName.startsWith("#")) {
headerName = "# " + headerName;
Expand All @@ -269,7 +286,7 @@ export default class Publisher {
if (!headerParts.last().startsWith(" ")) {
headerName = headerName.replace(headerParts.last(), " " + headerParts.last());
}

}
return headerName;
}
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ In the future you will be notified with a visual cue whenever there is an update
The plugin currently supports rendering of these types of note contents:
* Basic Markdown Syntax
* Links to other notes
* Embedded/Transcluded Excalidraw drawings
* Code Blocks
* Admonitions
* MathJax
Expand Down
10 changes: 10 additions & 0 deletions constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const seedling = `<g style="pointer-events:all"><title style="pointer-events: none" opacity="0.33">Layer 1</title><g id="hair" style="pointer-events: none" opacity="0.33"></g><g id="skin" style="pointer-events: none" opacity="0.33"></g><g id="skin-shadow" style="pointer-events: none" opacity="0.33"></g><g id="line"><path fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M47.71119,35.9247" id="svg_3"></path><polyline fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="49.813106536865234,93.05191133916378 49.813106536865234,69.57996462285519 40.03312683105469,26.548054680228233 " id="svg_4"></polyline><line x1="49.81311" x2="59.59309" y1="69.57996" y2="50.02" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" id="svg_5"></line><path fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M27.99666,14.21103C35.9517,16.94766 39.92393,26.36911 39.92393,26.36911S30.99696,31.3526 23.04075,28.61655S11.11348,16.45847 11.11348,16.45847S20.04456,11.4789 27.99666,14.21103z" id="svg_6"></path><path fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M76.46266698455811,45.61669603088379 C84.67706698455811,47.43146603088379 89.6945869845581,56.34024603088379 89.6945869845581,56.34024603088379 S81.3917769845581,62.30603603088379 73.17639698455811,60.492046030883785 S59.94447698455811,49.768496030883796 59.94447698455811,49.768496030883796 S68.2515869845581,43.80622603088379 76.46266698455811,45.61669603088379 z" id="svg_7"></path></g></g>`
export const excaliDrawBundle = `<style>
.container {font-family: sans-serif; text-align: center;}
.button-wrapper button {z-index: 1;height: 40px; width: 100px; margin: 10px;padding: 5px;}
.excalidraw .App-menu_top .buttonList { display: flex;}
.excalidraw-wrapper { height: 800px; margin: 50px; position: relative;}
:root[dir="ltr"] .excalidraw .layer-ui__wrapper .zen-mode-transition.App-menu_bottom--transition-left {transform: none;}
</style><script src="https://unpkg.com/react@17/umd/react.production.min.js"></script><script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script><script type="text/javascript" src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.production.min.js"></script>`;

export let excalidraw = (excaliDrawJson:string , drawingId:string):string => `<div id="${drawingId}"></div><script>(function(){const InitialData=${excaliDrawJson};InitialData.scrollToContent=true;App=()=>{const e=React.useRef(null),t=React.useRef(null),[n,i]=React.useState({width:void 0,height:void 0});return React.useEffect(()=>{i({width:t.current.getBoundingClientRect().width,height:t.current.getBoundingClientRect().height});const e=()=>{i({width:t.current.getBoundingClientRect().width,height:t.current.getBoundingClientRect().height})};return window.addEventListener("resize",e),()=>window.removeEventListener("resize",e)},[t]),React.createElement(React.Fragment,null,React.createElement("div",{className:"excalidraw-wrapper",ref:t},React.createElement(Excalidraw.default,{ref:e,width:n.width,height:n.height,initialData:InitialData,viewModeEnabled:!0,zenModeEnabled:!0,gridModeEnabled:!1})))},excalidrawWrapper=document.getElementById("${drawingId}");ReactDOM.render(React.createElement(App),excalidrawWrapper);})();</script>`;
1 change: 0 additions & 1 deletion icons.ts

This file was deleted.

4 changes: 2 additions & 2 deletions main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import DigitalGardenSettings from 'DigitalGardenSettings';
import DigitalGardenSiteManager from 'DigitalGardenSiteManager';
import SettingView from 'SettingView';
import { PublishStatusBar } from 'PublishStatusBar';
import { seedling } from 'icons';
import { seedling } from './constants';
import { PublishModal } from 'PublishModal';

const DEFAULT_SETTINGS: DigitalGardenSettings = {
Expand All @@ -22,7 +22,7 @@ export default class DigitalGarden extends Plugin {
publishModal: PublishModal;

async onload() {
this.appVersion = "2.4.0";
this.appVersion = "2.5.0";

console.log("Initializing DigitalGarden plugin v" + this.appVersion);
await this.loadSettings();
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "digitalgarden",
"name": "Digital Garden",
"version": "2.4.0",
"version": "2.5.0",
"minAppVersion": "0.12.0",
"description": "Publish your notes to a digital garden for others to enjoy.",
"author": "Ole Eskild Steensen",
Expand Down
1 change: 1 addition & 0 deletions versions.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"2.5.0": "0.12.0",
"2.4.0": "0.12.0",
"2.3.1": "0.12.0",
"2.3.0": "0.12.0",
Expand Down

0 comments on commit 438f118

Please sign in to comment.