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 release action and remove build artifact

Grace Kind (May 20, 2026, 8:26 PM -0500) b49b28e6 8de002f2

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