Mirror of https://github.com/improsocial/impro-sample-plugin
0

Configure Feed

Select the types of activity you want to include in your feed.

update

Grace Kind (May 22, 2026, 9:24 PM -0500) f6074334 15922ecc

+569 -4
+1 -2
.gitignore
··· 1 1 node_modules 2 - .claude 3 - main.js 2 + .claude
+566
main.js
··· 1 + var __defProp = Object.defineProperty; 2 + var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 3 + var __getOwnPropNames = Object.getOwnPropertyNames; 4 + var __hasOwnProp = Object.prototype.hasOwnProperty; 5 + var __export = (target, all) => { 6 + for (var name in all) 7 + __defProp(target, name, { get: all[name], enumerable: true }); 8 + }; 9 + var __copyProps = (to, from, except, desc) => { 10 + if (from && typeof from === "object" || typeof from === "function") { 11 + for (let key of __getOwnPropNames(from)) 12 + if (!__hasOwnProp.call(to, key) && key !== except) 13 + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); 14 + } 15 + return to; 16 + }; 17 + var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); 18 + 19 + // src/main.js 20 + var main_exports = {}; 21 + __export(main_exports, { 22 + default: () => ImproSamplePlugin 23 + }); 24 + module.exports = __toCommonJS(main_exports); 25 + 26 + // ../impro/impro-plugin/main.js 27 + var SimpleUUID = class { 28 + constructor() { 29 + this._id = 0; 30 + } 31 + create() { 32 + return this._id++; 33 + } 34 + }; 35 + var uuid = new SimpleUUID(); 36 + var callHandlers = /* @__PURE__ */ new Map(); 37 + var pendingHostCalls = /* @__PURE__ */ new Map(); 38 + function hostCall(method, ...args) { 39 + const hostCallId = uuid.create(); 40 + return new Promise((resolve, reject) => { 41 + pendingHostCalls.set(hostCallId, { resolve, reject }); 42 + self.postMessage({ type: "hostCall", method, hostCallId, args }); 43 + }); 44 + } 45 + var eventListeners = /* @__PURE__ */ new Map(); 46 + function addEventListener(event, listener) { 47 + let listeners = eventListeners.get(event); 48 + if (!listeners) { 49 + listeners = /* @__PURE__ */ new Set(); 50 + eventListeners.set(event, listeners); 51 + const handlerId = uuid.create(); 52 + callHandlers.set(handlerId, async (...args) => { 53 + const menu = new Menu(); 54 + for (const eventListener of listeners) { 55 + try { 56 + await eventListener(menu, ...args); 57 + } catch (error) { 58 + console.error(`"${event}" listener threw:`, error); 59 + } 60 + } 61 + return menu._serialize(); 62 + }); 63 + self.postMessage({ 64 + type: "register", 65 + target: "eventListener", 66 + event, 67 + handlerId 68 + }); 69 + } 70 + listeners.add(listener); 71 + } 72 + var MenuItem = class { 73 + constructor() { 74 + this.title = ""; 75 + this.icon = null; 76 + this._callback = () => { 77 + }; 78 + } 79 + setTitle(title) { 80 + this.title = title; 81 + return this; 82 + } 83 + setIcon(icon) { 84 + this.icon = icon; 85 + return this; 86 + } 87 + onClick(callback) { 88 + this._callback = callback; 89 + return this; 90 + } 91 + }; 92 + var Menu = class { 93 + constructor() { 94 + this.items = []; 95 + } 96 + addItem(builder) { 97 + const item = new MenuItem(); 98 + builder(item); 99 + this.items.push(item); 100 + return this; 101 + } 102 + _serialize() { 103 + return this.items.map((item) => { 104 + const handlerId = uuid.create(); 105 + callHandlers.set(handlerId, item._callback); 106 + return { title: item.title, icon: item.icon, handlerId }; 107 + }); 108 + } 109 + }; 110 + var App = class { 111 + constructor() { 112 + this.currentUser = null; 113 + } 114 + on(event, listener) { 115 + addEventListener(event, listener); 116 + } 117 + refreshFeedFilters(feedURI = null) { 118 + return hostCall("refreshFeedFilters", feedURI); 119 + } 120 + }; 121 + var registered = false; 122 + var Plugin = class { 123 + constructor() { 124 + this.app = new App(); 125 + } 126 + addSidebarItem(icon, title, callback = () => { 127 + }) { 128 + const handlerId = uuid.create(); 129 + callHandlers.set(handlerId, callback); 130 + self.postMessage({ 131 + type: "register", 132 + target: "sidebarItem", 133 + icon, 134 + title, 135 + handlerId 136 + }); 137 + } 138 + async loadData() { 139 + return hostCall("loadData"); 140 + } 141 + async saveData(data) { 142 + await hostCall("saveData", { data }); 143 + } 144 + addSettingTab(tab) { 145 + tab.plugin = this; 146 + const displayHandlerId = uuid.create(); 147 + callHandlers.set(displayHandlerId, () => { 148 + tab.containerEl = new VirtualEl("div"); 149 + tab.display(); 150 + return tab.containerEl._serialize(); 151 + }); 152 + self.postMessage({ 153 + type: "register", 154 + target: "settingTab", 155 + name: tab.name ?? null, 156 + displayHandlerId 157 + }); 158 + this._settingTab = tab; 159 + } 160 + addFeedFilter(callback = () => { 161 + }) { 162 + const handlerId = uuid.create(); 163 + callHandlers.set(handlerId, callback); 164 + self.postMessage({ 165 + type: "register", 166 + target: "feedFilter", 167 + handlerId 168 + }); 169 + } 170 + onload() { 171 + } 172 + onunload() { 173 + } 174 + static register() { 175 + if (registered) return; 176 + registered = true; 177 + const instance = new this(); 178 + hostCall("getCurrentUser").then((user) => { 179 + instance.app.currentUser = user; 180 + return instance.onload(); 181 + }).then( 182 + () => self.postMessage({ type: "ready" }), 183 + (error) => self.postMessage({ 184 + type: "ready", 185 + error: error?.message ?? String(error) 186 + }) 187 + ); 188 + } 189 + }; 190 + var openModals = /* @__PURE__ */ new Map(); 191 + var Modal = class { 192 + constructor() { 193 + this._modalId = uuid.create(); 194 + this.contentEl = new VirtualEl("div"); 195 + this.titleEl = new VirtualEl("h2"); 196 + } 197 + open() { 198 + if (openModals.has(this._modalId)) return; 199 + openModals.set(this._modalId, this); 200 + this.onOpen(); 201 + self.postMessage({ 202 + type: "hostCall", 203 + method: "openModal", 204 + args: [ 205 + { 206 + modalId: this._modalId, 207 + title: this.titleEl._serialize(), 208 + content: this.contentEl._serialize() 209 + } 210 + ] 211 + }); 212 + } 213 + close() { 214 + if (!openModals.has(this._modalId)) return; 215 + openModals.delete(this._modalId); 216 + self.postMessage({ 217 + type: "hostCall", 218 + method: "closeModal", 219 + args: [{ modalId: this._modalId }] 220 + }); 221 + this.onClose(); 222 + } 223 + onOpen() { 224 + } 225 + onClose() { 226 + } 227 + }; 228 + var PluginSettingTab = class { 229 + constructor() { 230 + this.containerEl = new VirtualEl("div"); 231 + this.name = null; 232 + } 233 + setName(name) { 234 + this.name = name; 235 + return this; 236 + } 237 + display() { 238 + } 239 + refresh() { 240 + return hostCall("refreshSettingTab"); 241 + } 242 + }; 243 + var Setting = class { 244 + constructor(containerEl) { 245 + this.settingEl = containerEl.createDiv({ cls: "plugin-setting-item" }); 246 + this.infoEl = this.settingEl.createDiv({ cls: "plugin-setting-item-info" }); 247 + this.nameEl = this.infoEl.createDiv({ cls: "plugin-setting-item-name" }); 248 + this.descEl = this.infoEl.createDiv({ cls: "plugin-setting-item-desc" }); 249 + this.controlEl = this.settingEl.createDiv({ 250 + cls: "plugin-setting-item-control" 251 + }); 252 + } 253 + setName(text) { 254 + this.nameEl.setText(text); 255 + return this; 256 + } 257 + setDesc(text) { 258 + this.descEl.setText(text); 259 + return this; 260 + } 261 + addText(callback) { 262 + const component = new TextComponent(this.controlEl); 263 + callback(component); 264 + return this; 265 + } 266 + addToggle(callback) { 267 + const component = new ToggleComponent(this.controlEl); 268 + callback(component); 269 + return this; 270 + } 271 + addDropdown(callback) { 272 + const component = new DropdownComponent(this.controlEl); 273 + callback(component); 274 + return this; 275 + } 276 + addButton(callback) { 277 + const component = new ButtonComponent(this.controlEl); 278 + callback(component); 279 + return this; 280 + } 281 + }; 282 + var TextComponent = class { 283 + constructor(containerEl) { 284 + this.el = containerEl.createEl("input", { 285 + attr: { type: "text" }, 286 + cls: "plugin-setting-text-input" 287 + }); 288 + } 289 + setValue(value) { 290 + this.el.setAttr("value", value == null ? "" : String(value)); 291 + return this; 292 + } 293 + setPlaceholder(value) { 294 + this.el.setAttr("placeholder", value); 295 + return this; 296 + } 297 + onChange(callback) { 298 + this.el.onChange((event) => callback(event.target.value)); 299 + return this; 300 + } 301 + }; 302 + var ToggleComponent = class { 303 + constructor(containerEl) { 304 + this.el = containerEl.createEl("input", { 305 + attr: { type: "checkbox" }, 306 + cls: "plugin-setting-toggle" 307 + }); 308 + } 309 + setValue(value) { 310 + if (value) this.el.setAttr("checked", ""); 311 + else delete this.el.attrs.checked; 312 + return this; 313 + } 314 + onChange(callback) { 315 + this.el.onChange((event) => callback(event.target.checked)); 316 + return this; 317 + } 318 + }; 319 + var DropdownComponent = class { 320 + constructor(containerEl) { 321 + this.el = containerEl.createEl("select", { 322 + cls: "plugin-setting-dropdown" 323 + }); 324 + } 325 + addOption(value, label) { 326 + this.el.createEl("option", { text: label, attr: { value } }); 327 + return this; 328 + } 329 + addOptions(map) { 330 + for (const [value, label] of Object.entries(map)) { 331 + this.addOption(value, label); 332 + } 333 + return this; 334 + } 335 + setValue(value) { 336 + for (const child of this.el.children) { 337 + if (child.attrs?.value === value) { 338 + child.attrs.selected = ""; 339 + } else if (child.attrs) { 340 + delete child.attrs.selected; 341 + } 342 + } 343 + return this; 344 + } 345 + onChange(callback) { 346 + this.el.onChange((event) => callback(event.target.value)); 347 + return this; 348 + } 349 + }; 350 + var ButtonComponent = class { 351 + constructor(containerEl) { 352 + this.el = containerEl.createEl("button", { 353 + cls: "plugin-setting-button" 354 + }); 355 + } 356 + setButtonText(text) { 357 + this.el.setText(text); 358 + return this; 359 + } 360 + setCta() { 361 + this.el.addClass("primary-button"); 362 + return this; 363 + } 364 + onClick(callback) { 365 + this.el.onClick(callback); 366 + return this; 367 + } 368 + }; 369 + var VirtualEl = class _VirtualEl { 370 + constructor(tag) { 371 + this.tag = tag; 372 + this.attrs = {}; 373 + this.text = null; 374 + this.children = []; 375 + this.events = {}; 376 + } 377 + onClick(fn) { 378 + const handlerId = uuid.create(); 379 + callHandlers.set(handlerId, fn); 380 + this.events.click = handlerId; 381 + return this; 382 + } 383 + onChange(fn) { 384 + const handlerId = uuid.create(); 385 + callHandlers.set(handlerId, fn); 386 + this.events.change = handlerId; 387 + return this; 388 + } 389 + onInput(fn) { 390 + const handlerId = uuid.create(); 391 + callHandlers.set(handlerId, fn); 392 + this.events.input = handlerId; 393 + return this; 394 + } 395 + setText(text) { 396 + this.text = text; 397 + this.children = []; 398 + return this; 399 + } 400 + empty() { 401 + this.text = null; 402 + this.children = []; 403 + return this; 404 + } 405 + addClass(cls) { 406 + this.attrs.class = this.attrs.class ? `${this.attrs.class} ${cls}` : cls; 407 + return this; 408 + } 409 + setAttr(name, value) { 410 + this.attrs[name] = value === void 0 ? "" : value; 411 + return this; 412 + } 413 + createEl(tag, options = {}, callback) { 414 + const child = new _VirtualEl(tag); 415 + if (options.text != null) child.text = options.text; 416 + if (options.cls) { 417 + child.attrs.class = Array.isArray(options.cls) ? options.cls.join(" ") : options.cls; 418 + } 419 + if (options.attr) Object.assign(child.attrs, options.attr); 420 + this.children.push(child); 421 + if (typeof callback === "function") callback(child); 422 + return child; 423 + } 424 + createDiv(options = {}, callback) { 425 + return this.createEl("div", options, callback); 426 + } 427 + createSpan(options = {}, callback) { 428 + return this.createEl("span", options, callback); 429 + } 430 + _serialize() { 431 + return { 432 + tag: this.tag, 433 + attrs: this.attrs, 434 + text: this.text, 435 + children: this.children.map((child) => child._serialize()), 436 + events: this.events 437 + }; 438 + } 439 + }; 440 + self.onmessage = async (event) => { 441 + const message = event.data; 442 + if (!message || typeof message !== "object") return; 443 + if (message.type === "call") { 444 + const fn = callHandlers.get(message.handlerId); 445 + if (!fn) { 446 + self.postMessage({ 447 + type: "result", 448 + callId: message.callId, 449 + error: `unknown handler ${message.handlerId}` 450 + }); 451 + return; 452 + } 453 + try { 454 + const value = await fn(...message.args); 455 + self.postMessage({ type: "result", callId: message.callId, value }); 456 + } catch (error) { 457 + self.postMessage({ 458 + type: "result", 459 + callId: message.callId, 460 + error: error.message ?? String(error) 461 + }); 462 + } 463 + return; 464 + } 465 + if (message.type === "hostResult") { 466 + const pending = pendingHostCalls.get(message.hostCallId); 467 + if (!pending) return; 468 + pendingHostCalls.delete(message.hostCallId); 469 + if (message.error) pending.reject(new Error(message.error)); 470 + else pending.resolve(message.value); 471 + return; 472 + } 473 + if (message.type === "event") { 474 + switch (message.event) { 475 + case "modalDismissed": { 476 + const modal = openModals.get(message.data.modalId); 477 + if (modal) { 478 + openModals.delete(message.data.modalId); 479 + modal.onClose(); 480 + } 481 + return; 482 + } 483 + } 484 + return; 485 + } 486 + }; 487 + 488 + // src/main.js 489 + var DEFAULT_SETTINGS = { 490 + greeting: "Hello", 491 + loud: false, 492 + buttonStyle: "primary" 493 + }; 494 + var SampleModal = class extends Modal { 495 + constructor(settings) { 496 + super(); 497 + this.settings = settings; 498 + } 499 + onOpen() { 500 + this.titleEl.setText("Sample modal"); 501 + const message = this.settings.loud ? `${this.settings.greeting}!!!` : `${this.settings.greeting}.`; 502 + this.contentEl.createEl("p", { 503 + text: message, 504 + cls: "modal-dialog-message" 505 + }); 506 + const buttons = this.contentEl.createDiv({ cls: "modal-dialog-buttons" }); 507 + buttons.createEl("button", { 508 + text: "OK", 509 + cls: `modal-dialog-button ${this.settings.buttonStyle}-button` 510 + }).onClick(() => this.close()); 511 + } 512 + onClose() { 513 + this.titleEl.empty(); 514 + this.contentEl.empty(); 515 + } 516 + }; 517 + var SampleSettingTab = class extends PluginSettingTab { 518 + constructor() { 519 + super(); 520 + this.setName("Impro Sample Plugin"); 521 + } 522 + display() { 523 + new Setting(this.containerEl).setName("Greeting").setDesc("Text shown in the sample modal").addText( 524 + (text) => text.setPlaceholder("Hello").setValue(this.plugin.settings.greeting).onChange(async (value) => { 525 + this.plugin.settings.greeting = value; 526 + await this.plugin.saveData(this.plugin.settings); 527 + }) 528 + ); 529 + new Setting(this.containerEl).setName("Loud mode").setDesc("Adds exclamation marks to the greeting").addToggle( 530 + (toggle) => toggle.setValue(this.plugin.settings.loud).onChange(async (value) => { 531 + this.plugin.settings.loud = value; 532 + await this.plugin.saveData(this.plugin.settings); 533 + }) 534 + ); 535 + new Setting(this.containerEl).setName("Button style").setDesc("Modal button style").addDropdown( 536 + (dropdown) => dropdown.addOptions({ primary: "Primary", danger: "Danger" }).setValue(this.plugin.settings.buttonStyle).onChange(async (value) => { 537 + this.plugin.settings.buttonStyle = value; 538 + await this.plugin.saveData(this.plugin.settings); 539 + }) 540 + ); 541 + new Setting(this.containerEl).setName("Reset settings").setDesc("Reset all settings to defaults").addButton( 542 + (button) => button.setButtonText("Reset").onClick(async () => { 543 + this.plugin.settings = { ...DEFAULT_SETTINGS }; 544 + await this.plugin.saveData(this.plugin.settings); 545 + this.refresh(); 546 + }) 547 + ); 548 + } 549 + }; 550 + var ImproSamplePlugin = class extends Plugin { 551 + async onload() { 552 + const saved = await this.loadData(); 553 + this.settings = { ...DEFAULT_SETTINGS, ...saved ?? {} }; 554 + this.addSettingTab(new SampleSettingTab()); 555 + this.addSidebarItem("lightning-bolt-line", "Open modal", () => { 556 + new SampleModal(this.settings).open(); 557 + }); 558 + this.app.on("post-context-menu", (menu) => { 559 + menu.addItem( 560 + (item) => item.setTitle("Open modal").setIcon("lightning-bolt").onClick(() => { 561 + new SampleModal(this.settings).open(); 562 + }) 563 + ); 564 + }); 565 + } 566 + };
+1 -1
manifest.json
··· 1 1 { 2 2 "id": "impro-sample-plugin", 3 3 "name": "Impro Sample Plugin", 4 - "version": "0.1.1", 4 + "version": "0.1.4", 5 5 "author": "Impro", 6 6 "description": "Starter reference plugin demonstrating commands, modals, and the host bridge." 7 7 }
+1 -1
package.json
··· 1 1 { 2 2 "name": "impro-sample-plugin", 3 - "version": "0.1.3", 3 + "version": "0.1.4", 4 4 "type": "module", 5 5 "scripts": { 6 6 "start": "npm run watch",