Skip to content

Commit d0898f9

Browse files
zsviczianclaude
andcommitted
Add script autostart permission flow, UI, and duplicate-registration guard
Completes the script-autostart feature: ExcalidrawAutomate.registerAutostart() lets a script ask to be automatically re-run every time a new Excalidraw view opens, gated by a user-confirmed Allow/Deny/Ask-every-time prompt whose decision persists in the new autostartScripts setting. A fresh "allow" also immediately attaches the script to every other currently-open view, and tracks per-script autostart failures (autostartScriptFailures) without saving settings on every successful run. ScriptEngine.runAutostartScripts() re-runs allow-listed scripts on each new view open (wired from ExcalidrawRoot.ts's mount effect); renaming or deleting a script's file purges its stale permission/failure entries. Adds AutostartScriptsSettingsComponent, rendered as a table (reusing the existing AI-provider-table styling) directly in the Excalidraw Automate settings section and in a Command Palette modal ("Autostart scripts"), letting a script's Autostart / Manual start only / Ask every time decision be changed at any time - a "Manual start only" choice is not a dead end. Also fixes a duplicate-registration bug in registerElementActionProvider(): running an already-registered script again in the same view no longer creates a second action button. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent dbb9a7d commit d0898f9

16 files changed

Lines changed: 325 additions & 52 deletions

RefactorPlan.md

Lines changed: 11 additions & 4 deletions
Large diffs are not rendered by default.

src/core/managers/CommandManager.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import {
6666
FileId,
6767
} from "@zsviczian/excalidraw/types/element/src/types";
6868
import { ReleaseNotes } from "../../shared/Dialogs/ReleaseNotes";
69+
import { AutostartScriptsModal } from "../../shared/Dialogs/AutostartScriptsSettingsComponent";
6970
import { ScriptInstallPrompt } from "../../shared/Dialogs/ScriptInstallPrompt";
7071
import Taskbone from "../../shared/OCR/Taskbone";
7172
import {
@@ -2301,6 +2302,14 @@ export class CommandManager {
23012302
},
23022303
});
23032304

2305+
this.addCommand({
2306+
id: "view-autostart-scripts",
2307+
name: t("AUTOSTART_SCRIPTS_HEAD"),
2308+
callback: () => {
2309+
new AutostartScriptsModal(this.plugin).open();
2310+
},
2311+
});
2312+
23042313
this.addCommand({
23052314
id: "tray-mode",
23062315
name: t("UI_MODE"),

src/core/settings.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
import { getImageCache } from "src/shared/ImageCache";
3030
import { MultiOptionConfirmationPrompt } from "src/shared/Dialogs/Prompt";
3131
import { EmbeddalbeMDFileCustomDataSettingsComponent } from "src/shared/Dialogs/EmbeddableMDFileCustomDataSettingsComponent";
32+
import { AutostartScriptsSettingsComponent } from "src/shared/Dialogs/AutostartScriptsSettingsComponent";
3233
import { startupScript } from "src/constants/starutpscript";
3334
import { ModifierKeySettingsComponent } from "src/shared/Dialogs/ModifierKeySettings";
3435
import { EDITOR_FADEOUT } from "src/core/editor/EditorHandler";
@@ -3566,6 +3567,13 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
35663567
});
35673568
});
35683569

3570+
//-------------------------------------
3571+
//Autostart scripts
3572+
//-------------------------------------
3573+
new AutostartScriptsSettingsComponent(
3574+
detailsEl.createDiv(),
3575+
this.plugin,
3576+
).render();
35693577
}
35703578

35713579
private renderCompatibilitySection(): void {

src/core/settingsDefaults.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,13 @@ export interface ExcalidrawSettings {
188188
autostartScripts: {
189189
[scriptName: string]: "allow" | "deny" | "unknown";
190190
};
191+
//true for a script whose most recent autostart run threw an error; cleared
192+
//the next time that script's autostart run succeeds. Purely informational
193+
//(surfaced as a warning in settings) - never removes the script from
194+
//autostartScripts on its own, the user does that manually.
195+
autostartScriptFailures: {
196+
[scriptName: string]: boolean;
197+
};
191198
previousRelease: string;
192199
showReleaseNotes: boolean;
193200
excalidrawMasteryPromoCollapsed: boolean;
@@ -640,6 +647,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
640647
},
641648
scriptEngineSettings: {},
642649
autostartScripts: {},
650+
autostartScriptFailures: {},
643651
previousRelease: "0.0.0",
644652
showReleaseNotes: true,
645653
excalidrawMasteryPromoCollapsed: false,

src/lang/locale/en.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,9 +1222,17 @@ export default {
12221222
"to run any command in Obsidian and potentially transfer data to the internet. Only enable this if you trust the file and its source.",
12231223
AUTOSTART_SCRIPT_PROMPT:
12241224
"wants to run automatically every time you open an Excalidraw drawing. Do you want to allow this?",
1225-
AUTOSTART_SCRIPT_ALLOW: "Allow",
1226-
AUTOSTART_SCRIPT_DENY: "Deny",
1227-
AUTOSTART_SCRIPT_ASK_LATER: "Ask me later",
1225+
AUTOSTART_SCRIPT_PROMPT_MANAGE_HINT:
1226+
'You can change this later from the Command Palette ("Autostart scripts") or in Settings → Compatibility → Autostart scripts.',
1227+
AUTOSTART_SCRIPT_ALLOW: "Autostart",
1228+
AUTOSTART_SCRIPT_DENY: "Manual start only",
1229+
AUTOSTART_SCRIPT_ASK_LATER: "Ask every time",
1230+
AUTOSTART_SCRIPTS_HEAD: "Autostart scripts",
1231+
AUTOSTART_SCRIPTS_DESC:
1232+
"Scripts appear here once they ask for permission to run automatically every time a new Excalidraw view opens (running the script once, manually, is what triggers the request). Change a script's setting at any time.",
1233+
AUTOSTART_SCRIPTS_EMPTY: "No scripts have requested autostart permission yet.",
1234+
AUTOSTART_SCRIPT_FAILED_WARNING:
1235+
"This script failed the last time it tried to autostart.",
12281236
ENABLE_COMMAND_LINKS_NAME: "Enable command links (cmd://)",
12291237
ENABLE_COMMAND_LINKS_CONFIRMATION:
12301238
"This link triggers an Obsidian command via <code>cmd://</code>. Do you want to enable command links?",
@@ -1476,6 +1484,7 @@ export default {
14761484
PROMPT_BUTTON_NEVERMIND: "Nevermind",
14771485
PROMPT_BUTTON_OK: "OK",
14781486
PROMPT_BUTTON_CANCEL: "Cancel",
1487+
PROMPT_BUTTON_CLOSE: "Close",
14791488
PROMPT_BUTTON_INSERT_LINE: "Insert new line",
14801489
PROMPT_BUTTON_INSERT_SPACE: "Insert space",
14811490
PROMPT_BUTTON_INSERT_LINK: "Insert markdown link to file",

src/lang/locale/es.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,9 +1187,18 @@ export default {
11871187
"para ejecutar cualquier comando en Obsidian y potencialmente transferir datos a internet. Actívalo solo si confías en el archivo y en su origen.",
11881188
AUTOSTART_SCRIPT_PROMPT:
11891189
"quiere ejecutarse automáticamente cada vez que abras un dibujo de Excalidraw. ¿Deseas permitirlo?",
1190-
AUTOSTART_SCRIPT_ALLOW: "Permitir",
1191-
AUTOSTART_SCRIPT_DENY: "Denegar",
1192-
AUTOSTART_SCRIPT_ASK_LATER: "Preguntar más tarde",
1190+
AUTOSTART_SCRIPT_PROMPT_MANAGE_HINT:
1191+
'Puedes cambiar esto más tarde desde la paleta de comandos ("Scripts de inicio automático") o en Ajustes → Compatibilidad → Scripts de inicio automático.',
1192+
AUTOSTART_SCRIPT_ALLOW: "Inicio automático",
1193+
AUTOSTART_SCRIPT_DENY: "Solo manual",
1194+
AUTOSTART_SCRIPT_ASK_LATER: "Preguntar cada vez",
1195+
AUTOSTART_SCRIPTS_HEAD: "Scripts de inicio automático",
1196+
AUTOSTART_SCRIPTS_DESC:
1197+
"Los scripts aparecen aquí una vez que solicitan permiso para ejecutarse automáticamente cada vez que se abre un nuevo dibujo de Excalidraw (ejecutar el script una vez manualmente es lo que activa la solicitud). Puedes cambiar el ajuste de un script en cualquier momento.",
1198+
AUTOSTART_SCRIPTS_EMPTY:
1199+
"Ningún script ha solicitado permiso de inicio automático todavía.",
1200+
AUTOSTART_SCRIPT_FAILED_WARNING:
1201+
"Este script falló la última vez que intentó iniciarse automáticamente.",
11931202
ENABLE_COMMAND_LINKS_NAME: "Habilitar enlaces de comandos (cmd://)",
11941203
ENABLE_COMMAND_LINKS_CONFIRMATION:
11951204
"Este enlace ejecuta un comando de Obsidian mediante <code>cmd://</code>. ¿Quieres habilitar los enlaces de comandos?",
@@ -1434,6 +1443,7 @@ export default {
14341443
PROMPT_BUTTON_NEVERMIND: "No importa",
14351444
PROMPT_BUTTON_OK: "OK",
14361445
PROMPT_BUTTON_CANCEL: "Cancelar",
1446+
PROMPT_BUTTON_CLOSE: "Cerrar",
14371447
PROMPT_BUTTON_INSERT_LINE: "Insertar nueva línea",
14381448
PROMPT_BUTTON_INSERT_SPACE: "Insertar espacio",
14391449
PROMPT_BUTTON_INSERT_LINK: "Insertar enlace Markdown a un archivo",

src/lang/locale/ru.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,9 +1092,17 @@ export default {
10921092
"для запуска любой команды в Obsidian и потенциальной передачи данных в интернет. Включайте это только если доверяете файлу и его источнику.",
10931093
AUTOSTART_SCRIPT_PROMPT:
10941094
"хочет запускаться автоматически каждый раз, когда вы открываете рисунок Excalidraw. Разрешить это?",
1095-
AUTOSTART_SCRIPT_ALLOW: "Разрешить",
1096-
AUTOSTART_SCRIPT_DENY: "Запретить",
1097-
AUTOSTART_SCRIPT_ASK_LATER: "Спросить позже",
1095+
AUTOSTART_SCRIPT_PROMPT_MANAGE_HINT:
1096+
"Позже это можно изменить в командной палитре («Автозапуск скриптов») или в Настройках → Совместимость → Автозапуск скриптов.",
1097+
AUTOSTART_SCRIPT_ALLOW: "Автозапуск",
1098+
AUTOSTART_SCRIPT_DENY: "Только вручную",
1099+
AUTOSTART_SCRIPT_ASK_LATER: "Спрашивать каждый раз",
1100+
AUTOSTART_SCRIPTS_HEAD: "Автозапуск скриптов",
1101+
AUTOSTART_SCRIPTS_DESC:
1102+
"Скрипты появляются здесь после того, как они запрашивают разрешение автоматически запускаться каждый раз при открытии нового рисунка Excalidraw (запрос инициируется однократным ручным запуском скрипта). Изменить настройку скрипта можно в любое время.",
1103+
AUTOSTART_SCRIPTS_EMPTY: "Ни один скрипт ещё не запрашивал разрешение на автозапуск.",
1104+
AUTOSTART_SCRIPT_FAILED_WARNING:
1105+
"Этот скрипт завершился ошибкой при последнем автозапуске.",
10981106
ENABLE_COMMAND_LINKS_NAME: "Включить ссылки-команды (cmd://)",
10991107
ENABLE_COMMAND_LINKS_CONFIRMATION:
11001108
"Эта ссылка запускает команду Obsidian через <code>cmd://</code>. Включить ссылки-команды?",
@@ -1271,6 +1279,7 @@ export default {
12711279
PROMPT_BUTTON_NEVERMIND: "Неважно",
12721280
PROMPT_BUTTON_OK: "OK",
12731281
PROMPT_BUTTON_CANCEL: "Отменить",
1282+
PROMPT_BUTTON_CLOSE: "Закрыть",
12741283
PROMPT_BUTTON_INSERT_LINE: "Вставить новую строку",
12751284
PROMPT_BUTTON_INSERT_SPACE: "Вставить пробел",
12761285
PROMPT_BUTTON_INSERT_LINK: "Вставить ссылку на файл в формате markdown",

src/lang/locale/zh-cn.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,9 +1136,16 @@ export default {
11361136
"在 Obsidian 中运行任意命令,并且有可能将数据传输到互联网。只有在你信任该文件及其来源时才启用此功能。",
11371137
AUTOSTART_SCRIPT_PROMPT:
11381138
"希望在你每次打开 Excalidraw 绘图时自动运行。是否允许?",
1139-
AUTOSTART_SCRIPT_ALLOW: "允许",
1140-
AUTOSTART_SCRIPT_DENY: "拒绝",
1141-
AUTOSTART_SCRIPT_ASK_LATER: "稍后再问",
1139+
AUTOSTART_SCRIPT_PROMPT_MANAGE_HINT:
1140+
"你以后可以在命令面板(“自动启动脚本”)或设置 → 兼容性 → 自动启动脚本中更改此设置。",
1141+
AUTOSTART_SCRIPT_ALLOW: "自动启动",
1142+
AUTOSTART_SCRIPT_DENY: "仅手动启动",
1143+
AUTOSTART_SCRIPT_ASK_LATER: "每次都询问",
1144+
AUTOSTART_SCRIPTS_HEAD: "自动启动脚本",
1145+
AUTOSTART_SCRIPTS_DESC:
1146+
"脚本在请求权限以便每次打开新的 Excalidraw 绘图时自动运行后会出现在这里(手动运行一次脚本即可触发该请求)。你可以随时更改脚本的设置。",
1147+
AUTOSTART_SCRIPTS_EMPTY: "尚无脚本请求自动启动权限。",
1148+
AUTOSTART_SCRIPT_FAILED_WARNING: "该脚本上次自动启动时失败了。",
11421149
ENABLE_COMMAND_LINKS_NAME: "启用命令链接(cmd://)",
11431150
ENABLE_COMMAND_LINKS_CONFIRMATION:
11441151
"此链接会通过 <code>cmd://</code> 触发 Obsidian 命令。是否启用命令链接?",
@@ -1378,6 +1385,7 @@ export default {
13781385
PROMPT_BUTTON_NEVERMIND: "算了",
13791386
PROMPT_BUTTON_OK: "OK",
13801387
PROMPT_BUTTON_CANCEL: "取消",
1388+
PROMPT_BUTTON_CLOSE: "关闭",
13811389
PROMPT_BUTTON_INSERT_LINE: "插入一行",
13821390
PROMPT_BUTTON_INSERT_SPACE: "插入空格",
13831391
PROMPT_BUTTON_INSERT_LINK: "插入内部链接",

src/lang/locale/zh-tw.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,9 +1136,16 @@ export default {
11361136
"在 Obsidian 中執行任意命令,並且有可能將資料傳輸到網際網路。只有在你信任該檔案及其來源時才啟用此功能。",
11371137
AUTOSTART_SCRIPT_PROMPT:
11381138
"希望在你每次開啟 Excalidraw 繪圖時自動執行。是否允許?",
1139-
AUTOSTART_SCRIPT_ALLOW: "允許",
1140-
AUTOSTART_SCRIPT_DENY: "拒絕",
1141-
AUTOSTART_SCRIPT_ASK_LATER: "稍後再問",
1139+
AUTOSTART_SCRIPT_PROMPT_MANAGE_HINT:
1140+
"你之後可以在命令選擇區(「自動啟動指令碼」)或設定 → 相容性 → 自動啟動指令碼中變更此設定。",
1141+
AUTOSTART_SCRIPT_ALLOW: "自動啟動",
1142+
AUTOSTART_SCRIPT_DENY: "僅手動啟動",
1143+
AUTOSTART_SCRIPT_ASK_LATER: "每次都詢問",
1144+
AUTOSTART_SCRIPTS_HEAD: "自動啟動指令碼",
1145+
AUTOSTART_SCRIPTS_DESC:
1146+
"指令碼在請求權限以便每次開啟新的 Excalidraw 繪圖時自動執行後會出現在這裡(手動執行一次指令碼即可觸發該請求)。你可以隨時變更指令碼的設定。",
1147+
AUTOSTART_SCRIPTS_EMPTY: "尚無指令碼請求自動啟動權限。",
1148+
AUTOSTART_SCRIPT_FAILED_WARNING: "該指令碼上次自動啟動時失敗了。",
11421149
ENABLE_COMMAND_LINKS_NAME: "啟用命令連結(cmd://)",
11431150
ENABLE_COMMAND_LINKS_CONFIRMATION:
11441151
"此連結會透過 <code>cmd://</code> 觸發 Obsidian 命令。是否啟用命令連結?",
@@ -1378,6 +1385,7 @@ export default {
13781385
PROMPT_BUTTON_NEVERMIND: "算了",
13791386
PROMPT_BUTTON_OK: "OK",
13801387
PROMPT_BUTTON_CANCEL: "取消",
1388+
PROMPT_BUTTON_CLOSE: "關閉",
13811389
PROMPT_BUTTON_INSERT_LINE: "插入一行",
13821390
PROMPT_BUTTON_INSERT_SPACE: "插入空格",
13831391
PROMPT_BUTTON_INSERT_LINK: "插入內部連結",
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { Modal, Setting } from "obsidian";
2+
import ExcalidrawPlugin from "src/core/main";
3+
import { t } from "src/lang/helpers";
4+
import { fragWithHTML } from "src/utils/utils";
5+
6+
/**
7+
* Lists every script that has ever called `ExcalidrawAutomate.
8+
* registerAutostart()` (i.e. the user ran it at least once), each with an
9+
* editable Autostart / Manual start only / Ask every time control (reusing
10+
* the exact same labels as the permission prompt's buttons, so the two
11+
* surfaces speak one consistent vocabulary), and a warning if the script's
12+
* most recent autostart run failed (`settings.autostartScriptFailures`,
13+
* purely informational - it never changes the script's setting on its
14+
* own). A "Manual start only" (deny) decision is not a dead end: it can be
15+
* switched straight back to Autostart or Ask every time from here, without
16+
* needing to re-run the script to get prompted again.
17+
*
18+
* Rendered as a bordered table (header row + one row per script), reusing
19+
* the AI provider table's existing `.excalidraw-ai-provider-table` CSS (see
20+
* `renderAISettings()` in `settings.ts`) rather than adding near-duplicate
21+
* rules to `styles.css`, and rather than a foldable `<details>` section,
22+
* since this is a short, flat list rather than a large collapsible
23+
* settings group. Rendered both inline in Settings (under the Startup
24+
* script setting, in the ExcalidrawAutomate section) and inside
25+
* `AutostartScriptsModal` for the Command Palette entry point, matching
26+
* how `EmbeddalbeMDFileCustomDataSettingsComponent` is reused across
27+
* contexts.
28+
*/
29+
export class AutostartScriptsSettingsComponent {
30+
constructor(
31+
private contentEl: HTMLElement,
32+
private plugin: ExcalidrawPlugin,
33+
) {}
34+
35+
render(): void {
36+
this.contentEl.empty();
37+
this.contentEl.addClass("excalidraw-ai-provider-table");
38+
39+
const autostartScripts = this.plugin.settings.autostartScripts;
40+
const scriptNames = Object.keys(autostartScripts).sort();
41+
42+
const headerSetting = new Setting(this.contentEl)
43+
.setName(t("AUTOSTART_SCRIPTS_HEAD"))
44+
.setDesc(fragWithHTML(t("AUTOSTART_SCRIPTS_DESC")));
45+
headerSetting.settingEl.addClass("excalidraw-ai-provider-table__header");
46+
47+
if (scriptNames.length === 0) {
48+
const emptyRow = new Setting(this.contentEl).setDesc(
49+
t("AUTOSTART_SCRIPTS_EMPTY"),
50+
);
51+
emptyRow.settingEl.addClass("excalidraw-ai-provider-table__row");
52+
return;
53+
}
54+
55+
scriptNames.forEach((scriptName) => {
56+
const rowSetting = new Setting(this.contentEl)
57+
.setName(scriptName)
58+
.addDropdown((dropdown) =>
59+
dropdown
60+
.addOption("allow", t("AUTOSTART_SCRIPT_ALLOW"))
61+
.addOption("deny", t("AUTOSTART_SCRIPT_DENY"))
62+
.addOption("unknown", t("AUTOSTART_SCRIPT_ASK_LATER"))
63+
.setValue(autostartScripts[scriptName])
64+
.onChange(async (value) => {
65+
autostartScripts[scriptName] = value as
66+
| "allow"
67+
| "deny"
68+
| "unknown";
69+
await this.plugin.saveSettings();
70+
}),
71+
);
72+
rowSetting.settingEl.addClass("excalidraw-ai-provider-table__row");
73+
74+
if (this.plugin.settings.autostartScriptFailures[scriptName]) {
75+
rowSetting.setDesc(
76+
fragWithHTML(
77+
`<span style="color: var(--text-error);">${t("AUTOSTART_SCRIPT_FAILED_WARNING")}</span>`,
78+
),
79+
);
80+
}
81+
});
82+
}
83+
}
84+
85+
export class AutostartScriptsModal extends Modal {
86+
constructor(private plugin: ExcalidrawPlugin) {
87+
super(plugin.app);
88+
}
89+
90+
onOpen(): void {
91+
this.titleEl.setText(t("AUTOSTART_SCRIPTS_HEAD"));
92+
new AutostartScriptsSettingsComponent(
93+
this.contentEl,
94+
this.plugin,
95+
).render();
96+
97+
new Setting(this.contentEl).addButton((button) =>
98+
button.setButtonText(t("PROMPT_BUTTON_CLOSE")).onClick(() => this.close()),
99+
);
100+
}
101+
102+
onClose(): void {
103+
this.contentEl.empty();
104+
}
105+
}

0 commit comments

Comments
 (0)