Repository: excalidraw/excalidraw-desktop
Branch: master
Commit: d58887254548
Files: 25
Total size: 24.0 KB
Directory structure:
gitextract_o3swcrh1/
├── .eslintrc.json
├── .github/
│ └── workflows/
│ ├── build.yml
│ └── test.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── LICENSE
├── README.md
├── build/
│ └── icon.icns
├── electron-builder.json
├── package.json
├── src/
│ ├── constants.ts
│ ├── main.ts
│ ├── menu.ts
│ ├── pages/
│ │ └── about.html
│ ├── preload.ts
│ ├── renderer.ts
│ ├── types.ts
│ └── util/
│ ├── checkVersion.ts
│ └── metadata.ts
├── tasks/
│ ├── dist.js
│ └── download.js
├── test/
│ └── main.spec.js
├── tsconfig.json
└── webpack.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.json
================================================
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"extends": ["prettier"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2019,
"project": "./tsconfig.json",
"sourceType": "module"
},
"plugins": ["@typescript-eslint", "prettier"],
"rules": {
"curly": "error",
"no-else-return": "error",
"prettier/prettier": "error"
},
"ignorePatterns": ["dist", "*.json"]
}
================================================
FILE: .github/workflows/build.yml
================================================
name: Build
on:
push:
branches:
- master
pull_request:
jobs:
build:
strategy:
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{matrix.platform}}
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
version: 12
- run: |
yarn
yarn build
================================================
FILE: .github/workflows/test.yml
================================================
name: Test
on:
push:
branches:
- master
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
version: 12
- name: Cache node modules
uses: actions/cache@v1
env:
cache-name: cache-node-modules
with:
path: node_modules
key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn lint
test-linux:
needs: lint
runs-on: ubuntu-latest
steps:
- run: lsb_release -a
- run: uname -a
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
version: 12
- name: Cache node modules
uses: actions/cache@v1
env:
cache-name: cache-node-modules
with:
path: node_modules
key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn build:app
- run: sudo apt-get install xvfb
- run: |
export DISPLAY=:99.0
xvfb-run --auto-servernum yarn test:spec
# NOTE:
# Xvfb is an X server that can run on linux machines with no display hardware
# and no physical input devices. It emulates a dumb framebuffer using
# virtual memory.
test-macos:
needs: lint
runs-on: macos-latest
steps:
- run: uname -a
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
version: 12
- name: Cache node modules
uses: actions/cache@v1
env:
cache-name: cache-node-modules
with:
path: node_modules
key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn build:app
- run: export {no_proxy,NO_PROXY}="127.0.0.1,localhost" # ChromeDriver timesout if NO_PROXY env variable is not set.
- run: yarn test:spec
# !FIXME: Spectron tests fail on Windows, probably due to a configuration issue.
# test-windows:
# needs: lint
# runs-on: windows-latest
# steps:
# - run: systeminfo
# - run: git config --global core.autocrlf false
# - run: git config --global core.eol lf
# - uses: actions/checkout@v1
# - uses: actions/setup-node@v1
# with:
# version: 12
# - name: Cache node modules
# uses: actions/cache@v1
# env:
# cache-name: cache-node-modules
# with:
# path: node_modules
# key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock') }}
# - run: yarn install --frozen-lockfile --ignore-scripts
# - run: yarn build:app
# - run: SET no_proxy=localhost,127.0.0.1
# - run: SET NO_PROXY=localhost,127.0.0.1
# - run: yarn test:spec
================================================
FILE: .gitignore
================================================
.DS_Store
dist
excalidraw.asar
node_modules
logs
*.log
================================================
FILE: .prettierignore
================================================
dist
================================================
FILE: .prettierrc.json
================================================
{
"bracketSpacing": false,
"trailingComma": "all"
}
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Excalidraw
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Excalidraw Desktop (deprecated)
> Read more on why we deprecated this repo: [Deprecating Excalidraw Electron in favor of the Web version](https://blog.excalidraw.com/deprecating-excalidraw-electron/)
[](https://excalidraw.com/?id=5698913638023168)
## Develop
### Install dependencies
```
yarn
```
### Commands
| Command | Description |
| --------------- | ------------------------------------------ |
| `yarn start` | Start development application |
| `yarn fix` | Reformat all files with Prettier |
| `yarn test` | Run linting and tests |
| `yarn download` | Fetch latest excalidraw bundle |
| `yarn build` | Build artifacts for windows, mac and linux |
## Contributing
Pull requests are welcome. For major changes, please [open an issue](https://github.com/excalidraw/excalidraw-desktop/issues/new) first to discuss what you would like to change.
================================================
FILE: electron-builder.json
================================================
{
"appId": "com.excalidraw.Excalidraw",
"productName": "Excalidraw",
"directories": {
"output": "dist"
},
"files": [
"package.json",
"dist/main.js",
"dist/renderer.js",
"dist/preload.js",
"dist/pages/*.html",
"dist/client",
"build/icon.*"
],
"win": {
"target": [
{
"target": "zip",
"arch": ["x64", "ia32"]
}
]
},
"linux": {
"category": "Development",
"target": [
{
"target": "tar.gz",
"arch": ["x64", "ia32"]
}
]
},
"mac": {
"hardenedRuntime": true,
"target": "dmg"
},
"fileAssociations": [
{
"ext": "excalidraw",
"name": "Excalidraw",
"description": "Excalidraw file",
"role": "Editor",
"mimeType": "application/json"
}
]
}
================================================
FILE: package.json
================================================
{
"author": "Excalidraw Team",
"description": "Excalidraw Desktop",
"devDependencies": {
"@types/minimist": "1.2.0",
"@types/node": "14",
"@types/node-fetch": "^2.5.7",
"@typescript-eslint/eslint-plugin": "4.0.0",
"@typescript-eslint/parser": "3.10.1",
"asar": "3.0.3",
"electron": "8.5.2",
"electron-builder": "22.8.0",
"eslint": "7.13.0",
"eslint-config-prettier": "6.11.0",
"eslint-plugin-prettier": "3.1.4",
"execa": "4.0.3",
"husky": "4.3.0",
"lint-staged": "10.5.1",
"minimist": "1.2.5",
"mocha": "8.1.3",
"mri": "1.1.6",
"node-fetch": "2.6.1",
"prettier": "2.1.2",
"spectron": "10.0.1",
"ts-loader": "8.0.3",
"typescript": "4.0.5",
"webpack": "4.44.2",
"webpack-cli": "3.3.12"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,ts,tsx}": [
"eslint --fix"
],
"*.{json,md,yml}": [
"prettier --write"
]
},
"main": "dist/main.js",
"name": "excalidraw-desktop",
"scripts": {
"build": "yarn download && yarn build:app && yarn build:dist",
"build:app": "webpack",
"build:dist": "node ./tasks/dist.js",
"download": "node ./tasks/download.js",
"fix": "yarn fix:other && yarn fix:code",
"fix:code": "yarn lint:code --fix",
"fix:other": "yarn prettier --write",
"postinstall": "yarn download",
"preinstall": "npx mkdirp dist",
"prettier": "prettier \"**/*.{json,md,yml}\"",
"start": "yarn build:app && electron ./dist/main.js --devtools",
"test": "yarn lint && yarn test:spec",
"test:spec": "mocha --exit",
"lint": "yarn lint:code && yarn lint:other",
"lint:code": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
"lint:other": "yarn prettier --ignore-path .gitignore --list-different",
"watch": "webpack -w"
},
"version": "0.0.1",
"dependencies": {
"electron-store": "^5.2.0",
"html-webpack-plugin": "^4.3.0"
}
}
================================================
FILE: src/constants.ts
================================================
export const APP_NAME = "Excalidraw";
export const EXCALIDRAW_API = "https://excalidraw.com/version.json";
export const EXCALIDRAW_ASAR_SOURCE = "https://excalidraw.com/excalidraw.asar";
export const EXCALIDRAW_GITHUB_PACKAGE_JSON_URL =
"https://raw.githubusercontent.com/excalidraw/excalidraw-desktop/master/package.json";
================================================
FILE: src/main.ts
================================================
import {app, BrowserWindow, shell, globalShortcut} from "electron";
import * as minimist from "minimist";
import * as path from "path";
import * as url from "url";
import {setupMenu} from "./menu";
import checkVersion from "./util/checkVersion";
import {setMetadata, setAppName} from "./util/metadata";
import {APP_NAME} from "./constants";
let mainWindow: Electron.BrowserWindow;
const argv = minimist(process.argv.slice(1));
const EXCALIDRAW_BUNDLE = path.join(__dirname, "client", "index.html");
const APP_ICON_PATH = path.join(__dirname, "client", "logo-180x180.png");
function createWindow() {
mainWindow = new BrowserWindow({
show: false,
height: 600,
width: 800,
webPreferences: {
contextIsolation: true, // protect against prototype pollution
preload: `${__dirname}/preload.js`,
},
});
if (argv.devtools) {
mainWindow.webContents.openDevTools({mode: "detach"});
}
mainWindow.webContents.on("will-navigate", openExternalURLs);
mainWindow.webContents.on("new-window", openExternalURLs);
mainWindow.loadURL(
url.format({
pathname: EXCALIDRAW_BUNDLE,
protocol: "file",
slashes: true,
}),
);
mainWindow.on("closed", () => {
mainWindow = null;
});
// Enable Cmd+Q on mac to quit the application
if (process.platform === "darwin") {
globalShortcut.register("Command+Q", () => {
app.quit();
});
}
// calling.show after this event, ensure there's no visual flash
mainWindow.once("ready-to-show", async () => {
const versions = await checkVersion();
console.info("Current version", versions.local);
console.info("Needs update", versions.needsUpdate);
setAppName(APP_NAME);
setMetadata("versions", versions);
setMetadata("appIconPath", APP_ICON_PATH);
setupMenu(mainWindow);
mainWindow.show();
});
}
// Open external links in user's default browser instead of webview
function openExternalURLs(event: Electron.Event, url: string) {
if (url.startsWith("http")) {
event.preventDefault();
shell.openExternal(url);
}
}
app.on("ready", createWindow);
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
if (mainWindow === null) {
createWindow();
}
});
================================================
FILE: src/menu.ts
================================================
import {BrowserWindow, Menu, MenuItem} from "electron";
import * as path from "path";
import * as url from "url";
import {APP_NAME} from "./constants";
import {getAppVersions, getAppName, getMetadata} from "./util/metadata";
const ABOUT_PAGE_PATH = path.resolve(__dirname, "pages", "about.html");
const openAboutWindow = () => {
let aboutWindow = new BrowserWindow({
height: 320,
width: 320,
modal: true,
backgroundColor: "white",
show: false,
webPreferences: {
contextIsolation: true,
preload: `${__dirname}/preload.js`,
},
});
aboutWindow.loadURL(
url.format({
pathname: ABOUT_PAGE_PATH,
protocol: "file",
slashes: true,
}),
);
aboutWindow.setMenuBarVisibility(false);
aboutWindow.center();
aboutWindow.on("ready-to-show", () => aboutWindow.show());
aboutWindow.on("show", () => {
const aboutContent = {
appName: getAppName(),
iconPath: getMetadata("appIconPath"),
versions: getAppVersions(),
};
aboutWindow.webContents.send("show-about-contents", aboutContent);
});
};
export const setupMenu = (activeWindow: BrowserWindow, options = {}) => {
const isDarwin = process.platform === "darwin";
const defaultMenuItems: MenuItem[] = Menu.getApplicationMenu().items;
const menuTemplate = [];
if (isDarwin) {
defaultMenuItems.shift();
menuTemplate.push({
label: APP_NAME,
submenu: [
{
label: `About ${APP_NAME}`,
enabled: true,
click: () => openAboutWindow(),
},
],
});
menuTemplate.push(...defaultMenuItems);
} else {
defaultMenuItems.pop();
menuTemplate.push(...defaultMenuItems);
menuTemplate.push({
label: "Help",
submenu: [
{
label: `About ${APP_NAME}`,
enabled: true,
click: () => openAboutWindow(),
},
],
});
}
// TODO: Remove default menu items that aren't relevant
const appMenu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(appMenu);
};
================================================
FILE: src/pages/about.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes"
/>
<meta
http-equiv="Content-Security-Policy"
content="block-all-mixed-content; child-src 'none'; connect-src https: wss: filesystem:; default-src 'self'; font-src 'self' data: https: filesystem:; img-src 'self' data: https:; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https:;"
/>
<title>About This App</title>
<style>
body,
html {
width: 100%;
height: 100%;
-webkit-user-select: none;
user-select: none;
-webkit-app-region: drag;
}
body {
margin: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #1d1d1d;
background-color: #fafafa;
font-size: 12px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}
#app-icon {
-webkit-user-select: none;
user-select: none;
display: inline-block;
background: #ffffff;
border-radius: 50px;
box-shadow: 20px 20px 60px #d9d9d9, -20px -20px 60px #ffffff;
}
.versions {
margin: 0.3em;
}
.title {
margin-top: 0.6em;
margin-bottom: 0.6em;
}
.clickable {
cursor: pointer;
}
.description {
margin-bottom: 1em;
text-align: center;
}
.copyright {
color: #999;
margin-top: 1.5em;
}
.link {
cursor: pointer;
color: #80a0c2;
}
</style>
</head>
<body>
<img id="app-icon" alt="App icon" height="60" width="60" />
<h2 class="title"></h2>
<p id="app-version" class="versions"></p>
<p id="web-version" class="versions"></p>
<p class="copyright"></p>
<!-- https://github.com/electron/electron/issues/2863 -->
<script>
var exports = exports || {};
</script>
</body>
</html>
================================================
FILE: src/preload.ts
================================================
import {ipcRenderer, contextBridge, remote} from "electron";
import {IpcListener} from "./types";
contextBridge.exposeInMainWorld("ipcRenderer", {
send: (channel: string, data: any[]) => {
ipcRenderer.send(channel, data);
},
receive: (channel: string, func: IpcListener) => {
ipcRenderer.on(channel, (event, ...args: any[]) => func(event, ...args));
},
});
contextBridge.exposeInMainWorld("remote", {
getVersion: remote.app.getVersion,
});
================================================
FILE: src/renderer.ts
================================================
const rendererWindow: RendererWindow = window;
rendererWindow.ipcRenderer.receive("show-about-contents", (_, options: any) => {
const {appName, versions, iconPath} = options;
document.title = `About ${appName}`;
const $titleElement = document.querySelector(".title") as HTMLHeadingElement;
$titleElement.textContent = appName;
const $iconElement = document.getElementById("app-icon") as HTMLImageElement;
$iconElement.src = iconPath;
const $appVersionElement = document.getElementById(
"app-version",
) as HTMLParagraphElement;
$appVersionElement.textContent = `Version ${versions.app.remote}`;
const $webVersionElement = document.getElementById(
"web-version",
) as HTMLParagraphElement;
$webVersionElement.textContent = `${appName} for Web Version ${versions.web.remote}`;
const $copyRightElement = document.querySelector(
".copyright",
) as HTMLParagraphElement;
$copyRightElement.textContent = `Copyright (c) ${new Date().getFullYear()} ${appName}`;
});
================================================
FILE: src/types.ts
================================================
import {IpcRendererEvent, IpcRenderer} from "electron";
export type IpcListener = (event: IpcRendererEvent, ...args: any[]) => void;
export type CustomIpcSender = (channel: string, ...args: any[]) => null;
export interface CustomIpcRenderer extends IpcRenderer {
send: (channel: string, ...args: any[]) => null;
receive: (channel: string, listener: IpcListener) => null;
}
declare global {
interface RendererWindow extends Window {
ipcRenderer?: CustomIpcRenderer;
remote?: {
getVersion: () => string;
};
}
}
================================================
FILE: src/util/checkVersion.ts
================================================
import * as fs from "fs";
import * as path from "path";
import fetch from "node-fetch";
import {EXCALIDRAW_API, EXCALIDRAW_GITHUB_PACKAGE_JSON_URL} from "../constants";
const LOCAL_VERSION_PATH = path.resolve(__dirname, "client", "version.json");
interface CheckResponse {
local: string;
remote: string;
appVersion: string;
needsUpdate: boolean;
}
const _getLocalVersion = (): string => {
const raw = fs.readFileSync(LOCAL_VERSION_PATH).toString();
const contents = JSON.parse(raw);
return contents.version;
};
const _getRemoteVersion = async (): Promise<string> => {
const raw = await fetch(EXCALIDRAW_API);
const contents = await raw.json();
return contents.version;
};
const _getRemoteDesktopAppVersion = async (): Promise<string> => {
const raw = await fetch(EXCALIDRAW_GITHUB_PACKAGE_JSON_URL);
const contents = await raw.json();
return contents.version;
};
export default async function checkVersion(): Promise<CheckResponse> {
const localVersion = _getLocalVersion();
const remoteVersion = await _getRemoteVersion();
const appVersion = await _getRemoteDesktopAppVersion();
return {
local: localVersion,
remote: remoteVersion,
appVersion,
needsUpdate: localVersion < remoteVersion,
};
}
================================================
FILE: src/util/metadata.ts
================================================
import * as Store from "electron-store";
let metadataStore: Store;
if (!metadataStore) {
metadataStore = new Store({});
}
export const getMetadata = (key: string): Store => {
return metadataStore.get(`metadata.${key}`);
};
export const setMetadata = (key: string, value: any) => {
return metadataStore.set(`metadata.${key}`, value);
};
export const setAppName = (value: string) => {
return metadataStore.set(`metadata.appName`, value);
};
export const getAppName = () => {
return metadataStore.get(`metadata.appName`);
};
export const getAppVersions = () => {
const versions = metadataStore.get(`metadata.versions`);
const {
local: localVersion,
remote: remoteVersion,
needsUpdate,
appVersion,
} = versions;
return {
needsUpdate,
app: {
local: "",
remote: appVersion,
},
web: {
local: localVersion,
remote: remoteVersion,
},
};
};
================================================
FILE: tasks/dist.js
================================================
#!/usr/bin/env node
const argv = require("mri")(process.argv);
const exec = require("execa").sync;
const pkg = require("../package");
const {publish, config} = argv;
const artifactOptions = [
"-c.artifactName=${name}-${version}-${os}-${arch}.${ext}",
"-c.dmg.artifactName=${name}-${version}-${os}.${ext}",
"-c.nsis.artifactName=${name}-${version}-${os}-setup.${ext}",
"-c.nsisWeb.artifactName=${name}-${version}-${os}-web-setup.${ext}",
argv.compress === false && "-c.compression=store",
].filter((f) => f);
// interpret shorthand target options
// --win, --linux, --mac
const platforms = [
argv.win ? "win" : null,
argv.linux ? "linux" : null,
argv.mac ? "mac" : null,
].filter((f) => f);
const platformOptions = platforms.map((p) => `--${p}`);
const publishOptions =
typeof publish !== undefined
? [`--publish=${publish ? "always" : "never"}`].filter((f) => f)
: [];
const signingOptions = [`-c.forceCodeSigning=${false}`];
if (publish && (argv.ia32 || argv.x64)) {
console.error("Do not override arch; is manually pinned");
process.exit(1);
}
const archOptions = ["x64", "ia32"].filter((a) => argv[a]).map((a) => `--${a}`);
const args = [
...[config && `-c=${config}`].filter((f) => f),
...archOptions,
...signingOptions,
...platformOptions,
...publishOptions,
...artifactOptions,
];
console.log(`
Building ${pkg.name} distro
---
version: ${pkg.version}
platforms: [${(platforms.length && platforms) || "current"}]
publish: ${publish || false}
---
electron-builder ${args.join(" ")}
`);
exec("electron-builder", args, {
stdio: "inherit",
});
================================================
FILE: tasks/download.js
================================================
#!/usr/bin/env node
const https = require("https");
const fs = require("fs");
const asar = require("asar");
const DEST = "dist/excalidraw.asar";
const SOURCE = "https://excalidraw.com/excalidraw.asar";
const UNPACK = "dist/client";
const file = fs.createWriteStream(DEST);
const request = https
.get(SOURCE, (response) => {
response.pipe(file);
file.on("finish", async () => {
console.info(`${DEST} is downloaded`);
// unpack
await asar.extractAll(DEST, UNPACK);
file.close();
});
})
.on("error", (error) => {
fs.unlink(DEST, () => {});
console.error(error);
});
================================================
FILE: test/main.spec.js
================================================
const Application = require("spectron").Application;
const assert = require("assert");
const path = require("path");
const rootDir = path.resolve(__dirname, "..");
const isWindows = process.platform === "win32";
const electronBinary = isWindows ? "electron.cmd" : "electron";
const electronPath = path.join(rootDir, "node_modules", ".bin", electronBinary);
describe("Application launch", function () {
this.timeout(20000);
beforeEach(function () {
this.app = new Application({
path: electronPath,
args: [rootDir],
startTimeout: 10000,
waitTimeout: 10e3,
chromeDriverLogPath: "./chromedriver.log",
});
return this.app.start();
});
afterEach(function () {
if (this.app && this.app.isRunning()) {
return this.app.stop();
}
});
it("shows an initial window", function () {
return this.app.client.getWindowCount().then(function (count) {
assert.equal(count, 1);
});
});
});
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"*": ["node_modules/*"]
}
},
"include": ["src/**/*", "tasks", "test/**/*", "webpack.config.js"]
}
================================================
FILE: webpack.config.js
================================================
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const commonConfiguration = {
mode: "development",
devtool: "sourcemap",
module: {
rules: [
{
test: /\.ts$/,
include: /src/,
use: [{loader: "ts-loader"}],
},
],
},
resolve: {
extensions: [".ts", ".js", ".json"],
},
};
module.exports = [
{
...commonConfiguration,
target: "electron-main",
entry: "./src/main.ts",
output: {
path: path.resolve(__dirname, "dist"),
filename: "main.js",
},
node: {
__dirname: false,
},
},
{
...commonConfiguration,
target: "electron-renderer",
entry: "./src/preload.ts",
node: {__dirname: false, global: true},
output: {
path: path.resolve(__dirname, "dist"),
filename: "preload.js",
},
},
{
...commonConfiguration,
target: "electron-renderer",
entry: "./src/renderer.ts",
output: {
path: path.resolve(__dirname, "dist"),
filename: "renderer.js",
},
plugins: [
new HtmlWebpackPlugin({
filename: "pages/about.html",
template: "./src/pages/about.html",
}),
],
},
];
gitextract_o3swcrh1/ ├── .eslintrc.json ├── .github/ │ └── workflows/ │ ├── build.yml │ └── test.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── build/ │ └── icon.icns ├── electron-builder.json ├── package.json ├── src/ │ ├── constants.ts │ ├── main.ts │ ├── menu.ts │ ├── pages/ │ │ └── about.html │ ├── preload.ts │ ├── renderer.ts │ ├── types.ts │ └── util/ │ ├── checkVersion.ts │ └── metadata.ts ├── tasks/ │ ├── dist.js │ └── download.js ├── test/ │ └── main.spec.js ├── tsconfig.json └── webpack.config.js
SYMBOL INDEX (19 symbols across 6 files)
FILE: src/constants.ts
constant APP_NAME (line 1) | const APP_NAME = "Excalidraw";
constant EXCALIDRAW_API (line 2) | const EXCALIDRAW_API = "https://excalidraw.com/version.json";
constant EXCALIDRAW_ASAR_SOURCE (line 3) | const EXCALIDRAW_ASAR_SOURCE = "https://excalidraw.com/excalidraw.asar";
constant EXCALIDRAW_GITHUB_PACKAGE_JSON_URL (line 4) | const EXCALIDRAW_GITHUB_PACKAGE_JSON_URL =
FILE: src/main.ts
constant EXCALIDRAW_BUNDLE (line 12) | const EXCALIDRAW_BUNDLE = path.join(__dirname, "client", "index.html");
constant APP_ICON_PATH (line 13) | const APP_ICON_PATH = path.join(__dirname, "client", "logo-180x180.png");
function createWindow (line 15) | function createWindow() {
function openExternalURLs (line 69) | function openExternalURLs(event: Electron.Event, url: string) {
FILE: src/menu.ts
constant ABOUT_PAGE_PATH (line 7) | const ABOUT_PAGE_PATH = path.resolve(__dirname, "pages", "about.html");
FILE: src/types.ts
type IpcListener (line 3) | type IpcListener = (event: IpcRendererEvent, ...args: any[]) => void;
type CustomIpcSender (line 4) | type CustomIpcSender = (channel: string, ...args: any[]) => null;
type CustomIpcRenderer (line 6) | interface CustomIpcRenderer extends IpcRenderer {
type RendererWindow (line 12) | interface RendererWindow extends Window {
FILE: src/util/checkVersion.ts
constant LOCAL_VERSION_PATH (line 6) | const LOCAL_VERSION_PATH = path.resolve(__dirname, "client", "version.js...
type CheckResponse (line 8) | interface CheckResponse {
function checkVersion (line 33) | async function checkVersion(): Promise<CheckResponse> {
FILE: tasks/download.js
constant DEST (line 7) | const DEST = "dist/excalidraw.asar";
constant SOURCE (line 8) | const SOURCE = "https://excalidraw.com/excalidraw.asar";
constant UNPACK (line 9) | const UNPACK = "dist/client";
Condensed preview — 25 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (27K chars).
[
{
"path": ".eslintrc.json",
"chars": 447,
"preview": "{\n \"env\": {\n \"browser\": true,\n \"es6\": true,\n \"node\": true\n },\n \"extends\": [\"prettier\"],\n \"parser\": \"@typesc"
},
{
"path": ".github/workflows/build.yml",
"chars": 380,
"preview": "name: Build\n\non:\n push:\n branches:\n - master\n pull_request:\n\njobs:\n build:\n strategy:\n matrix:\n "
},
{
"path": ".github/workflows/test.yml",
"chars": 2933,
"preview": "name: Test\n\non:\n push:\n branches:\n - master\n pull_request:\n\njobs:\n lint:\n runs-on: ubuntu-latest\n\n step"
},
{
"path": ".gitignore",
"chars": 54,
"preview": ".DS_Store\ndist\nexcalidraw.asar\nnode_modules\nlogs\n*.log"
},
{
"path": ".prettierignore",
"chars": 4,
"preview": "dist"
},
{
"path": ".prettierrc.json",
"chars": 56,
"preview": "{\n \"bracketSpacing\": false,\n \"trailingComma\": \"all\"\n}\n"
},
{
"path": "LICENSE",
"chars": 1067,
"preview": "MIT License\n\nCopyright (c) 2020 Excalidraw\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
},
{
"path": "README.md",
"chars": 1060,
"preview": "\n\n# Excalidraw Desktop (deprecated)\n\n> Read more on why we deprecated this repo: [Deprecating Excalidraw Electron in fav"
},
{
"path": "electron-builder.json",
"chars": 807,
"preview": "{\n \"appId\": \"com.excalidraw.Excalidraw\",\n \"productName\": \"Excalidraw\",\n \"directories\": {\n \"output\": \"dist\"\n },\n "
},
{
"path": "package.json",
"chars": 1992,
"preview": "{\n \"author\": \"Excalidraw Team\",\n \"description\": \"Excalidraw Desktop\",\n \"devDependencies\": {\n \"@types/minimist\": \"1"
},
{
"path": "src/constants.ts",
"chars": 326,
"preview": "export const APP_NAME = \"Excalidraw\";\nexport const EXCALIDRAW_API = \"https://excalidraw.com/version.json\";\nexport const "
},
{
"path": "src/main.ts",
"chars": 2297,
"preview": "import {app, BrowserWindow, shell, globalShortcut} from \"electron\";\nimport * as minimist from \"minimist\";\nimport * as pa"
},
{
"path": "src/menu.ts",
"chars": 2060,
"preview": "import {BrowserWindow, Menu, MenuItem} from \"electron\";\nimport * as path from \"path\";\nimport * as url from \"url\";\nimport"
},
{
"path": "src/pages/about.html",
"chars": 2251,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <meta\n name=\"viewport\"\n content=\"width=device"
},
{
"path": "src/preload.ts",
"chars": 460,
"preview": "import {ipcRenderer, contextBridge, remote} from \"electron\";\nimport {IpcListener} from \"./types\";\n\ncontextBridge.exposeI"
},
{
"path": "src/renderer.ts",
"chars": 1009,
"preview": "const rendererWindow: RendererWindow = window;\n\nrendererWindow.ipcRenderer.receive(\"show-about-contents\", (_, options: a"
},
{
"path": "src/types.ts",
"chars": 538,
"preview": "import {IpcRendererEvent, IpcRenderer} from \"electron\";\n\nexport type IpcListener = (event: IpcRendererEvent, ...args: an"
},
{
"path": "src/util/checkVersion.ts",
"chars": 1252,
"preview": "import * as fs from \"fs\";\nimport * as path from \"path\";\nimport fetch from \"node-fetch\";\nimport {EXCALIDRAW_API, EXCALIDR"
},
{
"path": "src/util/metadata.ts",
"chars": 919,
"preview": "import * as Store from \"electron-store\";\n\nlet metadataStore: Store;\n\nif (!metadataStore) {\n metadataStore = new Store({"
},
{
"path": "tasks/dist.js",
"chars": 1612,
"preview": "#!/usr/bin/env node\nconst argv = require(\"mri\")(process.argv);\n\nconst exec = require(\"execa\").sync;\n\nconst pkg = require"
},
{
"path": "tasks/download.js",
"chars": 620,
"preview": "#!/usr/bin/env node\n\nconst https = require(\"https\");\nconst fs = require(\"fs\");\nconst asar = require(\"asar\");\n\nconst DEST"
},
{
"path": "test/main.spec.js",
"chars": 958,
"preview": "const Application = require(\"spectron\").Application;\nconst assert = require(\"assert\");\nconst path = require(\"path\");\n\nco"
},
{
"path": "tsconfig.json",
"chars": 270,
"preview": "{\n \"compilerOptions\": {\n \"module\": \"commonjs\",\n \"noImplicitAny\": true,\n \"sourceMap\": true,\n \"outDir\": \"dist"
},
{
"path": "webpack.config.js",
"chars": 1205,
"preview": "const path = require(\"path\");\nconst HtmlWebpackPlugin = require(\"html-webpack-plugin\");\n\nconst commonConfiguration = {\n "
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the excalidraw/excalidraw-desktop GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 25 files (24.0 KB), approximately 7.2k tokens, and a symbol index with 19 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.