Personal noctalia plugins collection

Import upstream weekly-calendar from noctalia-plugins

Source: https://github.com/noctalia-dev/noctalia-plugins/tree/main/weekly-calendar
Version: 1.0.4

Dzming Li bbe89845

+2890
+892
registry.json
··· 1 + { 2 + "version": 1, 3 + "plugins": [ 4 + { 5 + "id": "activate-linux", 6 + "name": "Activate Linux Watermark", 7 + "version": "1.1.0", 8 + "official": false, 9 + "author": "Preston Corless (pgattic)", 10 + "description": "Adds a watermark to the bottom-right of the screen similar to the one found on unactivated Windows 10/11", 11 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 12 + "license": "MIT", 13 + "tags": [ 14 + "Desktop", 15 + "Fun" 16 + ], 17 + "lastUpdated": "2026-02-04T23:08:01-07:00" 18 + }, 19 + { 20 + "id": "assistant-panel", 21 + "name": "Assistant Panel", 22 + "version": "2.1.3", 23 + "official": false, 24 + "author": "j-1in", 25 + "description": "AI Chat and Translation panel", 26 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 27 + "minNoctaliaVersion": "4.1.2", 28 + "license": "MIT", 29 + "tags": [ 30 + "Bar", 31 + "Panel", 32 + "AI", 33 + "Productivity", 34 + "Network" 35 + ], 36 + "lastUpdated": "2026-02-05T21:36:32+11:00" 37 + }, 38 + { 39 + "id": "battery-actions", 40 + "name": "Battery Actions", 41 + "version": "1.0.1", 42 + "official": false, 43 + "author": "itscrystalline", 44 + "description": "A plugin that allows you to run custom actions when battery state changes.", 45 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 46 + "minNoctaliaVersion": "4.4.0", 47 + "license": "MIT", 48 + "tags": [ 49 + "System" 50 + ], 51 + "lastUpdated": "2026-02-26T19:55:06+01:00" 52 + }, 53 + { 54 + "id": "battery-threshold", 55 + "name": "Battery Threshold Control", 56 + "version": "1.2.0", 57 + "official": false, 58 + "author": "Wilfred Mallawa", 59 + "description": "Set the battery threshold for laptop batteries to extend battery lifespan", 60 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 61 + "minNoctaliaVersion": "3.6.0", 62 + "license": "MIT", 63 + "tags": [ 64 + "Bar", 65 + "Panel", 66 + "System" 67 + ], 68 + "lastUpdated": "2026-01-31T13:58:49+07:00" 69 + }, 70 + { 71 + "id": "calibre-provider", 72 + "name": "Calibre Provider", 73 + "version": "1.2.0", 74 + "official": false, 75 + "author": "Krendil", 76 + "description": "Search for books in your Calibre library", 77 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 78 + "minNoctaliaVersion": "4.4.1", 79 + "license": "MIT", 80 + "tags": [ 81 + "Launcher" 82 + ], 83 + "lastUpdated": "2026-02-23T12:55:52+10:00" 84 + }, 85 + { 86 + "id": "catwalk", 87 + "name": "Catwalk", 88 + "version": "1.1.6", 89 + "official": false, 90 + "author": "MannuVilasara", 91 + "description": "A cute animated cat for your bar.", 92 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 93 + "minNoctaliaVersion": "3.6.0", 94 + "license": "MIT", 95 + "tags": [ 96 + "Bar", 97 + "Desktop", 98 + "Panel", 99 + "Fun" 100 + ], 101 + "lastUpdated": "2026-02-04T20:40:31-05:00" 102 + }, 103 + { 104 + "id": "clipper", 105 + "name": "Clipper", 106 + "version": "2.1.0", 107 + "official": false, 108 + "author": "blackbartblues", 109 + "description": "Advanced clipboard manager with history, search, keyboard navigation, ToDo integration, pinned items, notecards/sticky notes, and selection-to-notecard feature. Fully translated with comprehensive i18n support.", 110 + "repository": "https://github.com/blackbartblues/noctalia-clipper", 111 + "minNoctaliaVersion": "4.1.2", 112 + "license": "MIT", 113 + "tags": [ 114 + "Utility", 115 + "Bar", 116 + "Panel" 117 + ], 118 + "lastUpdated": "2026-02-23T20:48:07-05:00" 119 + }, 120 + { 121 + "id": "color-scheme-creator", 122 + "name": "Color Scheme Creator", 123 + "version": "1.0.1", 124 + "official": true, 125 + "author": "Noctalia Team", 126 + "description": "Create and manage custom predefined color schemes with a visual color editor.", 127 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 128 + "minNoctaliaVersion": "4.5.1", 129 + "license": "MIT", 130 + "tags": [ 131 + "Theming", 132 + "Utility" 133 + ], 134 + "lastUpdated": "2026-02-24T17:49:23-05:00" 135 + }, 136 + { 137 + "id": "currency-exchange", 138 + "name": "Currency Exchange", 139 + "version": "1.0.2", 140 + "official": false, 141 + "author": "balor", 142 + "description": "Currency conversion utility via launcher and bar widget", 143 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 144 + "minNoctaliaVersion": "4.4.1", 145 + "license": "MIT", 146 + "tags": [ 147 + "Launcher", 148 + "Utility" 149 + ], 150 + "lastUpdated": "2026-02-10T10:12:36-05:00" 151 + }, 152 + { 153 + "id": "dns-switcher", 154 + "name": "DNS Switcher", 155 + "version": "1.2.0", 156 + "official": false, 157 + "author": "Ronin-CK", 158 + "description": "DNS Manager with custom server support for Noctalia.", 159 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 160 + "minNoctaliaVersion": "4.0.0", 161 + "license": "MIT", 162 + "tags": [ 163 + "Bar", 164 + "Network" 165 + ], 166 + "lastUpdated": "2026-02-25T15:40:48+05:30" 167 + }, 168 + { 169 + "id": "fancy-audiovisualizer", 170 + "name": "Fancy Audiovisualizer", 171 + "version": "1.0.8", 172 + "official": true, 173 + "author": "Noctalia Team", 174 + "description": "Lemmy's fancy circular audio visualizer.", 175 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 176 + "minNoctaliaVersion": "3.7.2", 177 + "license": "MIT", 178 + "tags": [ 179 + "Desktop", 180 + "Audio" 181 + ], 182 + "lastUpdated": "2026-02-17T14:33:04-05:00" 183 + }, 184 + { 185 + "id": "file-search", 186 + "name": "File Search", 187 + "version": "1.0.0", 188 + "official": false, 189 + "author": "ericbreh", 190 + "description": "File search using fd, integrated into the launcher", 191 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 192 + "minNoctaliaVersion": "4.1.2", 193 + "license": "MIT", 194 + "tags": [ 195 + "Launcher", 196 + "Productivity" 197 + ], 198 + "lastUpdated": "2026-02-10T12:24:50-08:00" 199 + }, 200 + { 201 + "id": "github-feed", 202 + "name": "GitHub Feed", 203 + "version": "1.2.0", 204 + "official": false, 205 + "author": "linuxmobile", 206 + "description": "Display GitHub activity from users you follow and activity on your repos", 207 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 208 + "minNoctaliaVersion": "3.6.0", 209 + "license": "MIT", 210 + "tags": [ 211 + "Bar", 212 + "Panel", 213 + "Development", 214 + "Network" 215 + ], 216 + "lastUpdated": "2026-02-23T00:37:30+01:00" 217 + }, 218 + { 219 + "id": "hello-world", 220 + "name": "Hello World", 221 + "version": "1.1.1", 222 + "official": true, 223 + "author": "Noctalia Team", 224 + "description": "A simple Hello World plugin demonstrating functionalities. For developers only.", 225 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 226 + "minNoctaliaVersion": "4.1.2", 227 + "license": "MIT", 228 + "tags": [ 229 + "Development" 230 + ], 231 + "lastUpdated": "2026-02-07T19:13:14+01:00" 232 + }, 233 + { 234 + "id": "hyprland-steam-overlay", 235 + "name": "Steam Overlay (Hyprland)", 236 + "version": "2.1.1", 237 + "official": false, 238 + "author": "blacku", 239 + "description": "Steam overlay with automatic window management for Hyprland. Automatically moves all Steam windows (except fullscreen games) to overlay workspace as floating windows. Main 3 windows (Friends, Client, Chat) are positioned in a row. Additional windows are brought to top. If new windows don't appear on top, toggle overlay off/on (Super+S twice). Fully responsive with percentage-based layout. Requires Hyprland window manager.", 240 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 241 + "minNoctaliaVersion": "4.1.2", 242 + "license": "MIT", 243 + "tags": [], 244 + "lastUpdated": "2026-01-29T22:32:10+01:00" 245 + }, 246 + { 247 + "id": "ideapad-battery-health", 248 + "name": "Ideapad Battery Health", 249 + "version": "1.0.0", 250 + "official": false, 251 + "author": "Christophe Branchereau", 252 + "description": "Set the battery charging mode for ideapad laptop batteries, to extend battery lifespan", 253 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 254 + "minNoctaliaVersion": "3.6.0", 255 + "license": "MIT", 256 + "tags": [ 257 + "Bar", 258 + "Panel", 259 + "System" 260 + ], 261 + "lastUpdated": "2026-02-17T15:50:12+01:00" 262 + }, 263 + { 264 + "id": "ip-monitor", 265 + "name": "IP Monitor", 266 + "version": "0.0.1", 267 + "official": false, 268 + "author": "deepalpha", 269 + "description": "Plugin showing current external IP", 270 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 271 + "minNoctaliaVersion": "4.4.3", 272 + "license": "MIT", 273 + "tags": [ 274 + "Development" 275 + ], 276 + "lastUpdated": "2026-02-12T08:50:25+01:00" 277 + }, 278 + { 279 + "id": "kagi-quick-search", 280 + "name": "Kagi Quick Search", 281 + "version": "1.0.1", 282 + "official": false, 283 + "author": "Kainoa Kanter", 284 + "description": "Get AI answers from Kagi in your launcher", 285 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 286 + "minNoctaliaVersion": "4.4.1", 287 + "license": "MIT", 288 + "tags": [ 289 + "Launcher", 290 + "Productivity" 291 + ], 292 + "lastUpdated": "2026-02-10T10:12:36-05:00" 293 + }, 294 + { 295 + "id": "kaomoji-provider", 296 + "name": "Kaomoji Provider", 297 + "version": "1.0.3", 298 + "official": true, 299 + "author": "Noctalia Team", 300 + "description": "Browse and search kaomoji emoticons from the launcher", 301 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 302 + "minNoctaliaVersion": "4.4.1", 303 + "license": "MIT", 304 + "tags": [ 305 + "Launcher", 306 + "Fun" 307 + ], 308 + "lastUpdated": "2026-02-10T10:12:36-05:00" 309 + }, 310 + { 311 + "id": "kde-connect", 312 + "name": "KDE Connect", 313 + "version": "1.0.2", 314 + "official": false, 315 + "author": "WerWolv", 316 + "description": "A Plugin integrating your mobile devices into a panel using KDEConnect", 317 + "repository": "https://github.com/WerWolv/noctalia-kde-connect", 318 + "minNoctaliaVersion": "4.4.0", 319 + "license": "GPLv2", 320 + "tags": [ 321 + "Bar", 322 + "Panel", 323 + "Utility", 324 + "System" 325 + ], 326 + "lastUpdated": "2026-02-20T20:36:40+01:00" 327 + }, 328 + { 329 + "id": "keybind-cheatsheet", 330 + "name": "Keybind Cheatsheet", 331 + "version": "3.2.2", 332 + "official": false, 333 + "author": "blacku", 334 + "description": "Universal keyboard shortcuts keymap that automatically detects and displays keybindings for Hyprland or Niri compositors.", 335 + "minNoctaliaVersion": "3.6.0", 336 + "license": "MIT", 337 + "tags": [], 338 + "lastUpdated": "2026-02-08T21:44:29+01:00" 339 + }, 340 + { 341 + "id": "mangowc-layout-switcher", 342 + "name": "Mangowc Layout Switcher", 343 + "version": "1.1.4", 344 + "official": false, 345 + "author": "atheeq-rhxn", 346 + "description": "Switch between different mangowc layouts", 347 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 348 + "minNoctaliaVersion": "3.6.0", 349 + "license": "MIT", 350 + "tags": [ 351 + "Bar", 352 + "Panel", 353 + "MangoWC" 354 + ], 355 + "lastUpdated": "2026-02-28T01:32:51+05:30" 356 + }, 357 + { 358 + "id": "mawaqit", 359 + "name": "Mawaqit", 360 + "version": "1.2.1", 361 + "official": false, 362 + "author": "User", 363 + "description": "Shows prayer times and a countdown to the next prayer. Plays azan at prayer time.", 364 + "minNoctaliaVersion": "4.1.0", 365 + "license": "MIT", 366 + "tags": [ 367 + "Bar", 368 + "Panel" 369 + ], 370 + "lastUpdated": "2026-02-27T21:53:04+01:00" 371 + }, 372 + { 373 + "id": "mini-docker", 374 + "name": "Mini Docker", 375 + "version": "2.1.2", 376 + "official": false, 377 + "author": "MannuVilasara", 378 + "description": "Manage Docker containers, images, volumes & networks", 379 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 380 + "minNoctaliaVersion": "3.6.0", 381 + "license": "MIT", 382 + "tags": [ 383 + "Bar", 384 + "Panel", 385 + "Development" 386 + ], 387 + "lastUpdated": "2026-01-26T23:59:29-05:00" 388 + }, 389 + { 390 + "id": "model-usage", 391 + "name": "Model Usage", 392 + "version": "0.2.0", 393 + "official": false, 394 + "author": "cmptr", 395 + "description": "Shows AI coding assistant usage stats in the bar with detail panel (Claude Code, Codex, OpenRouter, Zen)", 396 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 397 + "minNoctaliaVersion": "3.6.0", 398 + "license": "MIT", 399 + "tags": [ 400 + "Bar", 401 + "Productivity", 402 + "AI" 403 + ], 404 + "lastUpdated": "2026-02-25T19:27:50-06:00" 405 + }, 406 + { 407 + "id": "mpris-lyric", 408 + "name": "MPRIS Lyric", 409 + "version": "1.0.2", 410 + "official": false, 411 + "author": "DBeidachazi", 412 + "description": "Display synchronized lyrics from MPRIS-compatible music players (e.g., go-musicfox) in the bar.", 413 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 414 + "minNoctaliaVersion": "3.8.0", 415 + "license": "MIT", 416 + "tags": [ 417 + "Bar", 418 + "Music" 419 + ], 420 + "lastUpdated": "2026-02-23T20:48:07-05:00" 421 + }, 422 + { 423 + "id": "mpvpaper", 424 + "name": "Mpvpaper", 425 + "version": "1.6.2", 426 + "official": false, 427 + "author": "Spyridon Siarapis", 428 + "description": "A plugin that allows having video wallpapers using mpvpaper.", 429 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 430 + "minNoctaliaVersion": "3.6.0", 431 + "license": "MIT", 432 + "tags": [ 433 + "Bar", 434 + "Panel", 435 + "Fun", 436 + "System" 437 + ], 438 + "lastUpdated": "2026-02-09T20:15:29+01:00" 439 + }, 440 + { 441 + "id": "netbird", 442 + "name": "NetBird", 443 + "version": "1.0.0", 444 + "official": false, 445 + "author": "Cleboost", 446 + "description": "Show NetBird VPN status in the menu bar.", 447 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 448 + "minNoctaliaVersion": "4.0.0", 449 + "license": "MIT", 450 + "tags": [ 451 + "Bar", 452 + "Network", 453 + "Panel" 454 + ], 455 + "lastUpdated": "2026-02-13T15:08:22+01:00" 456 + }, 457 + { 458 + "id": "network-indicator", 459 + "name": "Network Indicator", 460 + "version": "1.0.5", 461 + "official": false, 462 + "author": "tonigineer", 463 + "description": "A `lively` network traffic indicator.", 464 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 465 + "minNoctaliaVersion": "3.7.5", 466 + "license": "MIT", 467 + "tags": [ 468 + "Bar", 469 + "Network", 470 + "Indicator" 471 + ], 472 + "lastUpdated": "2026-01-26T23:59:29-05:00" 473 + }, 474 + { 475 + "id": "network-manager-vpn", 476 + "name": "Network Manager VPN", 477 + "version": "1.1.0", 478 + "official": false, 479 + "author": "nZO", 480 + "description": "Connect to VPN via NetworkManager", 481 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 482 + "minNoctaliaVersion": "3.6.0", 483 + "license": "MIT", 484 + "tags": [ 485 + "Bar", 486 + "Panel", 487 + "Network" 488 + ], 489 + "lastUpdated": "2026-02-23T22:04:26+01:00" 490 + }, 491 + { 492 + "id": "news", 493 + "name": "News Bar", 494 + "version": "1.2.2", 495 + "official": false, 496 + "author": "LazarH", 497 + "description": "Display scrolling news headlines from various sources using NewsAPI.org", 498 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 499 + "minNoctaliaVersion": "3.6.0", 500 + "license": "MIT", 501 + "tags": [ 502 + "Bar", 503 + "Panel", 504 + "Network", 505 + "Fun" 506 + ], 507 + "lastUpdated": "2026-02-22T10:29:46+02:00" 508 + }, 509 + { 510 + "id": "niri-overview-launcher", 511 + "name": "Niri Overview Launcher", 512 + "version": "1.0.0", 513 + "official": false, 514 + "author": "eliahreeves", 515 + "description": "Opens the launcher when typing in niri's overview mode", 516 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 517 + "minNoctaliaVersion": "4.1.2", 518 + "license": "MIT", 519 + "tags": [ 520 + "System" 521 + ], 522 + "lastUpdated": "2026-02-05T11:41:54-08:00" 523 + }, 524 + { 525 + "id": "noctalia-supergfxctl", 526 + "name": "SuperGFX Control", 527 + "version": "0.0.3", 528 + "official": false, 529 + "author": "cod3d <cod3ddot@proton.me>", 530 + "description": "GPU control with supergfxctl for noctalia.", 531 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 532 + "minNoctaliaVersion": "3.8.2", 533 + "license": "MIT", 534 + "tags": [ 535 + "Bar", 536 + "Panel", 537 + "System" 538 + ], 539 + "lastUpdated": "2026-02-23T20:48:07-05:00" 540 + }, 541 + { 542 + "id": "notes-scratchpad", 543 + "name": "Notes Scratchpad", 544 + "version": "1.1.3", 545 + "official": false, 546 + "author": "apt-get", 547 + "description": "A simple scratchpad for quick notes.", 548 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 549 + "minNoctaliaVersion": "3.7.1", 550 + "license": "MIT", 551 + "tags": [ 552 + "Bar", 553 + "Panel", 554 + "Productivity" 555 + ], 556 + "lastUpdated": "2026-02-26T13:39:10-05:00" 557 + }, 558 + { 559 + "id": "openhue", 560 + "name": "Openhue widget", 561 + "version": "1.0.1", 562 + "official": false, 563 + "author": "alxndrv", 564 + "description": "Plugin for controlling Philips Hue lights from the taskbar", 565 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 566 + "minNoctaliaVersion": "3.6.0", 567 + "license": "MIT", 568 + "tags": [ 569 + "Bar", 570 + "Panel" 571 + ], 572 + "lastUpdated": "2026-01-26T23:59:29-05:00" 573 + }, 574 + { 575 + "id": "polkit-agent", 576 + "name": "Polkit Agent", 577 + "version": "1.0.1", 578 + "official": true, 579 + "author": "Noctalia Team <team@noctalia.dev>", 580 + "description": "Provides a Polkit authentication agent (requires quickshell-git).", 581 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 582 + "minNoctaliaVersion": "4.4.3", 583 + "license": "MIT", 584 + "tags": [ 585 + "System", 586 + "Security" 587 + ], 588 + "lastUpdated": "2026-02-15T22:18:12+01:00" 589 + }, 590 + { 591 + "id": "pomodoro", 592 + "name": "Pomodoro", 593 + "version": "1.2.0", 594 + "official": false, 595 + "author": "notprayasmitra", 596 + "description": "A pomodoro timer plugin to help boost productivity.", 597 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 598 + "minNoctaliaVersion": "4.0.0", 599 + "license": "MIT", 600 + "tags": [ 601 + "Bar", 602 + "Productivity" 603 + ], 604 + "lastUpdated": "2026-02-01T21:52:23+05:30" 605 + }, 606 + { 607 + "id": "privacy-indicator", 608 + "name": "Privacy Indicator", 609 + "version": "1.2.5", 610 + "official": true, 611 + "author": "Noctalia Team", 612 + "description": "A privacy indicator widget that shows when microphone, camera or screen sharing is active.", 613 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 614 + "minNoctaliaVersion": "3.6.0", 615 + "license": "MIT", 616 + "tags": [ 617 + "Bar", 618 + "Privacy", 619 + "Indicator" 620 + ], 621 + "lastUpdated": "2026-02-26T19:19:12+01:00" 622 + }, 623 + { 624 + "id": "ramadan-iftar", 625 + "name": "Ramadan Iftar", 626 + "version": "1.0.2", 627 + "official": false, 628 + "author": "User", 629 + "description": "Shows prayer times and a countdown to Iftar during Ramadan.", 630 + "minNoctaliaVersion": "4.1.0", 631 + "license": "MIT", 632 + "tags": [ 633 + "Bar", 634 + "Panel" 635 + ], 636 + "lastUpdated": "2026-02-19T09:33:50-05:00" 637 + }, 638 + { 639 + "id": "rss-feed", 640 + "name": "RSS Feed Reader", 641 + "version": "1.0.3", 642 + "official": false, 643 + "author": "Lokize", 644 + "description": "Monitor and read RSS/Atom feeds directly from your desktop", 645 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 646 + "minNoctaliaVersion": "3.6.0", 647 + "license": "MIT", 648 + "tags": [], 649 + "lastUpdated": "2026-01-26T23:59:29-05:00" 650 + }, 651 + { 652 + "id": "screen-recorder", 653 + "name": "Screen Recorder", 654 + "version": "1.1.7", 655 + "official": true, 656 + "author": "Noctalia Team", 657 + "description": "Hardware-accelerated screen recording using gpu-screen-recorder with customizable video and audio settings", 658 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 659 + "minNoctaliaVersion": "3.6.0", 660 + "license": "MIT", 661 + "tags": [ 662 + "Bar", 663 + "Utility" 664 + ], 665 + "lastUpdated": "2026-02-10T01:17:08+03:00" 666 + }, 667 + { 668 + "id": "screenshot", 669 + "name": "Screenshot", 670 + "version": "1.0.1", 671 + "official": false, 672 + "author": "Cleboost", 673 + "description": "Quick screenshot button in bar for Hyprland, Sway, and Niri", 674 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 675 + "minNoctaliaVersion": "3.6.0", 676 + "license": "MIT", 677 + "tags": [ 678 + "Bar", 679 + "Utility" 680 + ], 681 + "lastUpdated": "2026-02-19T17:33:45-05:00" 682 + }, 683 + { 684 + "id": "simple-notes", 685 + "name": "Simple Notes", 686 + "version": "1.0.3", 687 + "official": false, 688 + "author": "fusuyfusuy", 689 + "description": "A simple note taking plugin for Noctalia Shell.", 690 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 691 + "minNoctaliaVersion": "3.7.1", 692 + "license": "MIT", 693 + "tags": [ 694 + "Bar", 695 + "Panel", 696 + "Productivity" 697 + ], 698 + "lastUpdated": "2026-01-26T23:59:29-05:00" 699 + }, 700 + { 701 + "id": "steam-price-watcher", 702 + "name": "Steam Price Watcher", 703 + "version": "1.0.5", 704 + "official": false, 705 + "author": "Lokize", 706 + "description": "Monitor Steam game prices and get notified when they reach your target price", 707 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 708 + "minNoctaliaVersion": "3.6.0", 709 + "license": "MIT", 710 + "tags": [], 711 + "lastUpdated": "2026-01-27T07:21:12-03:00" 712 + }, 713 + { 714 + "id": "sticky-notes", 715 + "name": "Sticky Notes", 716 + "version": "0.0.1", 717 + "official": false, 718 + "author": "sapjax", 719 + "description": "A local quick notes plugin with Markdown support.", 720 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 721 + "minNoctaliaVersion": "3.6.0", 722 + "license": "MIT", 723 + "tags": [ 724 + "Bar", 725 + "Panel", 726 + "Productivity" 727 + ], 728 + "lastUpdated": "2026-02-23T15:20:02+08:00" 729 + }, 730 + { 731 + "id": "tailscale", 732 + "name": "Tailscale", 733 + "version": "1.1.2", 734 + "official": false, 735 + "author": "nineluj", 736 + "description": "Show Tailscale status in the menu bar.", 737 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 738 + "minNoctaliaVersion": "4.0.0", 739 + "license": "MIT", 740 + "tags": [ 741 + "Bar", 742 + "Network", 743 + "Panel" 744 + ], 745 + "lastUpdated": "2026-02-14T20:25:29+01:00" 746 + }, 747 + { 748 + "id": "timer", 749 + "name": "Timer", 750 + "version": "1.1.0", 751 + "official": true, 752 + "author": "Noctalia Team", 753 + "description": "A timer and stopwatch plugin for the bar & control center.", 754 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 755 + "minNoctaliaVersion": "4.4.4", 756 + "license": "MIT", 757 + "tags": [ 758 + "Bar", 759 + "Utility" 760 + ], 761 + "lastUpdated": "2026-02-17T08:29:13-05:00" 762 + }, 763 + { 764 + "id": "todo", 765 + "name": "Todo List", 766 + "version": "1.9.4", 767 + "official": false, 768 + "author": "lonerOrz <lonerOrz@qq.com>", 769 + "description": "A simple todo list manager plugin for Noctalia Shell.", 770 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 771 + "minNoctaliaVersion": "3.7.1", 772 + "license": "MIT", 773 + "tags": [ 774 + "Bar", 775 + "Desktop", 776 + "Panel", 777 + "Productivity" 778 + ], 779 + "lastUpdated": "2026-02-21T20:24:43+08:00" 780 + }, 781 + { 782 + "id": "translator", 783 + "name": "Translator", 784 + "version": "1.2.3", 785 + "official": false, 786 + "author": "Cleboost", 787 + "description": "Quick translate in launcher", 788 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 789 + "minNoctaliaVersion": "4.4.1", 790 + "license": "MIT", 791 + "tags": [ 792 + "Launcher" 793 + ], 794 + "lastUpdated": "2026-02-10T10:12:36-05:00" 795 + }, 796 + { 797 + "id": "unicode-picker", 798 + "name": "Unicode Picker", 799 + "version": "1.0.2", 800 + "official": false, 801 + "author": "Daddeffe", 802 + "description": "Browse and search Unicode characters from the launcher", 803 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 804 + "minNoctaliaVersion": "4.4.1", 805 + "license": "MIT", 806 + "tags": [ 807 + "Launcher", 808 + "Utility" 809 + ], 810 + "lastUpdated": "2026-02-10T10:12:36-05:00" 811 + }, 812 + { 813 + "id": "update-count", 814 + "name": "Update Count", 815 + "version": "1.0.12", 816 + "official": false, 817 + "author": "BukoMoon", 818 + "description": "Checks for system updates and shows the update count. Click to run update command in a terminal.", 819 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 820 + "minNoctaliaVersion": "3.6.0", 821 + "license": "GPLv3", 822 + "tags": [ 823 + "Bar", 824 + "System" 825 + ], 826 + "lastUpdated": "2026-02-05T21:01:51-05:00" 827 + }, 828 + { 829 + "id": "video-wallpaper", 830 + "name": "Video Wallpaper", 831 + "version": "2.0.2", 832 + "official": false, 833 + "author": "Spyridon Siarapis", 834 + "description": "A plugin that uses a backend renderer to play videos as your wallpaper.", 835 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 836 + "minNoctaliaVersion": "3.6.0", 837 + "license": "MIT", 838 + "tags": [ 839 + "Bar", 840 + "Panel", 841 + "Fun", 842 + "System" 843 + ], 844 + "lastUpdated": "2026-02-20T12:04:37+01:00" 845 + }, 846 + { 847 + "id": "weather-indicator", 848 + "name": "Weather Indicator", 849 + "version": "1.0.9", 850 + "official": false, 851 + "author": "Sovereign", 852 + "description": "Shows the current weather condition and tempurature.", 853 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 854 + "minNoctaliaVersion": "4.3.0", 855 + "license": "MIT", 856 + "tags": [ 857 + "Bar" 858 + ], 859 + "lastUpdated": "2026-02-25T18:02:47-05:00" 860 + }, 861 + { 862 + "id": "weekly-calendar", 863 + "name": "Weekly Calendar", 864 + "version": "1.0.4", 865 + "official": false, 866 + "author": "dodaars", 867 + "description": "A weekly calendar plugin complementing Noctalia's inbuilt calendar service.", 868 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 869 + "minNoctaliaVersion": "4.2.3", 870 + "license": "MIT", 871 + "tags": [ 872 + "Bar", 873 + "Panel", 874 + "Productivity" 875 + ], 876 + "lastUpdated": "2026-02-05T11:19:10-05:00" 877 + }, 878 + { 879 + "id": "world-clock", 880 + "name": "World Clock", 881 + "version": "1.0.4", 882 + "official": false, 883 + "author": "Lokize", 884 + "description": "Display time from multiple timezones around the world with automatic rotation.", 885 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 886 + "minNoctaliaVersion": "3.6.0", 887 + "license": "MIT", 888 + "tags": [], 889 + "lastUpdated": "2026-01-27T08:47:06-05:00" 890 + } 891 + ] 892 + }
+149
weekly-calendar/BarWidget.qml
··· 1 + import QtQuick 2 + import Quickshell 3 + import qs.Commons 4 + import qs.Widgets 5 + import qs.Modules.Bar.Extras 6 + import qs.Services.UI 7 + 8 + Item { 9 + id: root 10 + 11 + property QtObject pluginApi: null 12 + readonly property QtObject pluginCore: pluginApi?.mainInstance 13 + 14 + property ShellScreen screen 15 + property string widgetId: "" 16 + property string section: "" 17 + property int sectionWidgetIndex: -1 18 + property int sectionWidgetsCount: 0 19 + 20 + property string tooltipContent: "" 21 + 22 + Timer { 23 + id: updateTimer 24 + interval: 1000 25 + running: true 26 + repeat: true 27 + onTriggered: updateTooltip() 28 + } 29 + 30 + function updateTooltip() { 31 + if (!pluginCore || !pluginCore.eventsModel) { 32 + tooltipContent = ""; 33 + return; 34 + } 35 + 36 + var events = pluginCore.eventsModel; 37 + var now = new Date(); 38 + 39 + // Get start and end of today 40 + var todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()); 41 + var todayEnd = new Date(todayStart); 42 + todayEnd.setDate(todayEnd.getDate() + 1); 43 + 44 + var currentEv = null; 45 + var nextEv = null; 46 + var hasEventsToday = false; 47 + 48 + for (var i = 0; i < events.count; i++) { 49 + var event = events.get(i); 50 + var eventStart = event.startTime; 51 + var eventEnd = event.endTime; 52 + 53 + // Only consider events that occur today 54 + var occursToday = (eventStart >= todayStart && eventStart < todayEnd) || 55 + (eventEnd > todayStart && eventEnd <= todayEnd) || 56 + (eventStart < todayStart && eventEnd > todayEnd); 57 + 58 + if (!occursToday) { 59 + continue; 60 + } 61 + 62 + hasEventsToday = true; 63 + 64 + // Check if event is currently happening 65 + if (now >= eventStart && now <= eventEnd) { 66 + currentEv = event; 67 + } 68 + 69 + // Check for next event (starting later today) 70 + if (!nextEv && eventStart > now && eventStart < todayEnd) { 71 + nextEv = event; 72 + } 73 + 74 + if (currentEv && nextEv) break; 75 + } 76 + 77 + var tip = ""; 78 + 79 + if (currentEv) { 80 + tip = pluginApi.tr("bar_widget.now") + ": " + currentEv.title + "\n" + 81 + pluginCore.formatTimeRangeForDisplay(currentEv); 82 + 83 + // If there's a current event and a next event 84 + if (nextEv) { 85 + tip += "\n\n" + pluginApi.tr("bar_widget.next") + ": " + nextEv.title + "\n" + 86 + pluginCore.formatTimeRangeForDisplay(nextEv); 87 + } 88 + } 89 + 90 + if (nextEv && !currentEv) { 91 + // If no current event but there's a next event today 92 + tip = pluginApi.tr("bar_widget.next") + ": " + nextEv.title + "\n" + 93 + pluginCore.formatTimeRangeForDisplay(nextEv); 94 + } 95 + 96 + // If all events have passed today 97 + if (!tip && hasEventsToday) { 98 + tip = pluginApi.tr("bar_widget.no_more_events_today"); 99 + } 100 + 101 + tooltipContent = tip; 102 + } 103 + 104 + Connections { 105 + target: pluginCore 106 + function onEventsModelChanged() { updateTooltip(); } 107 + function onCurrentDateChanged() { updateTooltip(); } 108 + } 109 + 110 + implicitWidth: pill.width 111 + implicitHeight: pill.height 112 + 113 + BarPill { 114 + id: pill 115 + screen: root.screen 116 + oppositeDirection: BarService.getPillDirection(root) 117 + forceClose: true 118 + 119 + icon: "calendar" 120 + tooltipText: tooltipContent 121 + 122 + onClicked: root.pluginApi?.openPanel(root.screen) 123 + 124 + onRightClicked: { 125 + PanelService.showContextMenu(contextMenu, pill, root.screen); 126 + } 127 + } 128 + 129 + NPopupContextMenu { 130 + id: contextMenu 131 + model: [ 132 + { 133 + "label": pluginApi.tr("bar_widget.settings"), 134 + "action": "widget-settings", 135 + "icon": "settings", 136 + "enabled": true 137 + } 138 + ] 139 + 140 + onTriggered: action => { 141 + contextMenu.close(); 142 + PanelService.closeContextMenu(root.screen); 143 + 144 + if (action === "widget-settings") { 145 + BarService.openPluginSettings(screen, pluginApi.manifest); 146 + } 147 + } 148 + } 149 + }
+461
weekly-calendar/Main.qml
··· 1 + import QtQuick 2 + import Quickshell 3 + import qs.Commons 4 + import qs.Services.Location 5 + import qs.Services.UI 6 + import Quickshell.Io 7 + import qs.Services.System 8 + 9 + Item { 10 + id: root 11 + property var pluginApi: null 12 + property date currentDate: new Date() 13 + property ListModel eventsModel: ListModel {} 14 + property ListModel allDayEventsModel: ListModel {} 15 + property var overlappingEventsData: ({}) 16 + property bool isLoading: false 17 + property bool hasLoadedOnce: false 18 + property string syncStatus: "" 19 + 20 + property real dayColumnWidth: 120 * Style.uiScaleRatio 21 + property real allDaySectionHeight: 0 * Style.uiScaleRatio 22 + property var allDayEventsWithLayout: [] 23 + 24 + property date weekStart: calculateWeekStart(currentDate, firstDayOfWeek) 25 + property date weekEnd: calculateWeekEnd(weekStart) 26 + property var weekDates: calculateWeekDates(weekStart) 27 + property string monthRangeText: formatMonthRangeText(weekDates) 28 + 29 + // Settings 30 + property string panelModeSetting: pluginApi?.pluginSettings?.panelMode || "attached" 31 + property string weekStartSetting: pluginApi?.pluginSettings?.weekStart || "1" 32 + property string timeFormatSetting: pluginApi?.pluginSettings?.timeFormat || "24h" 33 + property string lineColorTypeSetting: pluginApi?.pluginSettings?.lineColorType || "mOutline" 34 + property real hourLineOpacitySetting: pluginApi?.pluginSettings?.hourLineOpacity ?? 0.5 35 + property real dayLineOpacitySetting: pluginApi?.pluginSettings?.dayLineOpacity ?? 0.9 36 + 37 + readonly property int firstDayOfWeek: weekStartSetting === "0" ? 0 : 38 + weekStartSetting === "1" ? 1 : 39 + weekStartSetting === "6" ? 6 : I18n.locale.firstDayOfWeek 40 + 41 + readonly property bool use12hourFormat: timeFormatSetting === "12h" ? true : 42 + timeFormatSetting === "24h" ? false : 43 + Settings.data.location.use12hourFormat 44 + 45 + readonly property color lineColor: lineColorTypeSetting === "mOnSurfaceVariant" ? Color.mOnSurfaceVariant : Color.mOutline 46 + 47 + onWeekStartSettingChanged: if (hasLoadedOnce) Qt.callLater(loadEvents) 48 + onTimeFormatSettingChanged: eventsModelChanged() 49 + onCurrentDateChanged: Qt.callLater(loadEvents) 50 + onLineColorTypeSettingChanged: eventsModelChanged() 51 + onHourLineOpacitySettingChanged: eventsModelChanged() 52 + onDayLineOpacitySettingChanged: eventsModelChanged() 53 + 54 + // IPC 55 + IpcHandler { 56 + target: "plugin:weekly-calendar" 57 + function togglePanel() { pluginApi?.withCurrentScreen(s => pluginApi.togglePanel(s)) } 58 + } 59 + 60 + Component.onCompleted: initializePluginSettings() 61 + 62 + function initializePluginSettings() { 63 + if (!pluginApi) return 64 + if (!pluginApi.pluginSettings.weekStart) { 65 + pluginApi.pluginSettings = { 66 + weekStart: "1", 67 + timeFormat: "24h", 68 + lineColorType: "mOutline", 69 + hourLineOpacity: 0.5, 70 + dayLineOpacity: 1.0 71 + } 72 + pluginApi.saveSettings() 73 + } 74 + } 75 + 76 + function initializePlugin() { loadEvents() } 77 + 78 + // Fetch events 79 + function loadEvents() { 80 + if (!CalendarService.available || isLoading) return 81 + 82 + isLoading = true 83 + syncStatus = pluginApi.tr("panel.loading") 84 + 85 + var now = new Date() 86 + var start = new Date(weekStart), end = new Date(weekEnd) 87 + start.setDate(start.getDate() - 7) 88 + end.setDate(end.getDate() + 14) 89 + 90 + CalendarService.loadEvents( 91 + Math.max(0, Math.ceil((end - now) / 86400000)), 92 + Math.max(0, Math.ceil((now - start) / 86400000)) 93 + ) 94 + 95 + hasLoadedOnce = true 96 + updateEventsFromService() 97 + } 98 + 99 + function updateEventsFromService() { 100 + clearEventModels() 101 + 102 + if (!CalendarService.available) { 103 + syncStatus = pluginApi.tr("panel.no_service") 104 + } else if (!CalendarService.events?.length) { 105 + syncStatus = pluginApi.tr("panel.no_events") 106 + } else { 107 + var stats = processCalendarEvents(CalendarService.events) 108 + syncStatus = stats.timedCount === 1 109 + ? `${stats.timedCount} ${pluginApi.tr("panel.event")}, ${stats.allDayCount} ${pluginApi.tr("panel.allday")}` 110 + : `${stats.timedCount} ${pluginApi.tr("panel.events")}, ${stats.allDayCount} ${pluginApi.tr("panel.allday")}` 111 + } 112 + 113 + isLoading = false 114 + } 115 + 116 + // Events generation & layout 117 + function processCalendarEvents(events) { 118 + var uniqueEvents = {}, uniqueAllDayEvents = {} 119 + var timedCount = 0, allDayCount = 0 120 + var newEvents = [], newAllDayEvents = [] 121 + var weekStartDate = new Date(weekStart), weekEndDate = new Date(weekEnd) 122 + 123 + for (var i = 0; i < events.length; i++) { 124 + var event = events[i], eventObj = createEventObject(event, i) 125 + var eventStart = new Date(eventObj.startTime), eventEnd = new Date(eventObj.endTime) 126 + var overlapsWeek = eventStart < weekEndDate && eventEnd > weekStartDate 127 + 128 + if (overlapsWeek) { 129 + var key = event.uid + "-" + event.start + "-" + event.end + i 130 + if (eventObj.allDay) { 131 + if (!uniqueAllDayEvents[key]) { 132 + uniqueAllDayEvents[key] = true 133 + allDayCount++ 134 + newAllDayEvents.push(eventObj) 135 + } 136 + } else if (!uniqueEvents[key]) { 137 + uniqueEvents[key] = true 138 + timedCount++ 139 + processTimedEventIntoArray(eventObj, newEvents) 140 + } 141 + } 142 + } 143 + 144 + eventsModel.clear() 145 + allDayEventsModel.clear() 146 + newEvents.forEach(e => eventsModel.append(e)) 147 + newAllDayEvents.forEach(e => allDayEventsModel.append(e)) 148 + 149 + calculateAllDayEventLayout() 150 + updateOverlappingEvents() 151 + eventsModel.layoutChanged() 152 + allDayEventsModel.layoutChanged() 153 + 154 + return {timedCount: timedCount, allDayCount: allDayCount} 155 + } 156 + 157 + function clearEventModels() { eventsModel.clear(); allDayEventsModel.clear() } 158 + 159 + function processTimedEventIntoArray(eventObj, target) { 160 + var start = new Date(eventObj.startTime), end = new Date(eventObj.endTime) 161 + var startDay = new Date(start.getFullYear(), start.getMonth(), start.getDate()) 162 + var endDay = new Date(end.getFullYear(), end.getMonth(), end.getDate()) 163 + 164 + if (startDay.getTime() === endDay.getTime()) { 165 + if (start < weekEnd && end > weekStart) target.push(createEventPart(eventObj, 0, start, end, startDay, 0, 1)) 166 + } else { 167 + var firstEnd = new Date(startDay); firstEnd.setHours(24, 0, 0, 0) 168 + var secondStart = new Date(endDay); secondStart.setHours(0, 0, 0, 0) 169 + if (start < weekEnd && firstEnd > weekStart) target.push(createEventPart(eventObj, 0, start, firstEnd, startDay, 0, 2)) 170 + if (secondStart < weekEnd && end > weekStart) target.push(createEventPart(eventObj, 1, secondStart, end, endDay, 1, 2)) 171 + } 172 + } 173 + 174 + function createEventObject(event, idx) { 175 + var start = new Date(event.start * 1000), end = new Date(event.end * 1000) 176 + var allDay = isAllDayEvent(event), multiDay = isMultiDayEvent(event) 177 + var daySpan = calculateDaySpan(start, end, multiDay || allDay) 178 + var endsMidnight = end.getHours() === 0 && end.getMinutes() === 0 && end.getSeconds() === 0 179 + var id = event.uid + "-" + event.start + "-" + event.end + idx 180 + 181 + return { 182 + id: id, title: event.summary || "Untitled Event", description: event.description || "", 183 + location: event.location || "", startTime: start, endTime: end, allDay: allDay, multiDay: multiDay, 184 + daySpan: daySpan, rawStart: event.start, rawEnd: event.end, duration: (event.end - event.start) / 3600, 185 + endsAtMidnight: endsMidnight 186 + } 187 + } 188 + 189 + function createEventPart(event, partIdx, start, end, day, partNum, total) { 190 + return { 191 + id: event.id + "-part-" + partIdx, title: event.title, description: event.description, 192 + location: event.location, startTime: start, endTime: end, allDay: false, multiDay: true, 193 + daySpan: 1, fullStartTime: event.startTime, fullEndTime: event.endTime, isPart: true, 194 + partDay: new Date(day), partIndex: partNum, totalParts: total 195 + } 196 + } 197 + 198 + function getDayIndexForDate(date) { 199 + if (!date || isNaN(date.getTime())) return -1 200 + var diff = Math.floor((date - weekStart) / 86400000) 201 + return diff >= 0 && diff < 7 ? diff : -1 202 + } 203 + function getDisplayDayIndexForDate(date) { return getDayIndexForDate(date) } 204 + 205 + function calculateAllDaySpanForWeek(event) { 206 + var start = new Date(event.startTime), end = new Date(event.endTime) 207 + var endsMidnight = end.getHours() === 0 && end.getMinutes() === 0 && end.getSeconds() === 0 208 + var adjEnd = endsMidnight ? new Date(end.getTime() - 1) : end 209 + var startIdx = Math.max(0, getDayIndexForDate(start)) 210 + var endIdx = Math.min(6, Math.floor((Math.min(adjEnd, weekEnd) - weekStart) / 86400000)) 211 + return Math.max(1, endIdx - startIdx + 1) 212 + } 213 + 214 + function findAvailableLane(occupied, start, end) { 215 + var lane = 0, found = false 216 + while (!found) { 217 + var conflict = false 218 + for (var d = start; d <= end; d++) { 219 + if (occupied[d]?.includes(lane)) { conflict = true; break } 220 + } 221 + if (!conflict) found = true 222 + else lane++ 223 + } 224 + return lane 225 + } 226 + 227 + function calculateAllDayEventLayout() { 228 + 229 + var occupied = [[], [], [], [], [], [], []] 230 + var eventsWithLayout = [], maxLanes = 0 231 + var weekStartDate = new Date(weekStart), weekEndDate = new Date(weekEnd) 232 + 233 + for (var i = 0; i < allDayEventsModel.count; i++) { 234 + var event = allDayEventsModel.get(i) 235 + var start = new Date(event.startTime), end = new Date(event.endTime) 236 + var startDay = new Date(start.getFullYear(), start.getMonth(), start.getDate()) 237 + var endDay = new Date(end.getFullYear(), end.getMonth(), end.getDate()) 238 + 239 + if (startDay < weekStartDate && endDay >= weekStartDate) { 240 + var span = calculateAllDaySpanForWeek(event) 241 + if (span > 0) { 242 + var lane = findAvailableLane(occupied, 0, span - 1) 243 + for (var d = 0; d < span && d < 7; d++) { if (!occupied[d]) occupied[d] = []; occupied[d].push(lane) } 244 + maxLanes = Math.max(maxLanes, lane + 1) 245 + eventsWithLayout.push(createLayoutEvent(event, 0, span, lane, true)) 246 + } 247 + } else if (startDay >= weekStartDate && startDay < weekEndDate) { 248 + var startIdx = getDayIndexForDate(start) 249 + var span = calculateAllDaySpanForWeek(event) 250 + if (span > 0) { 251 + var lane = findAvailableLane(occupied, startIdx, startIdx + span - 1) 252 + for (var d = startIdx; d < startIdx + span && d < 7; d++) { if (!occupied[d]) occupied[d] = []; occupied[d].push(lane) } 253 + maxLanes = Math.max(maxLanes, lane + 1) 254 + eventsWithLayout.push(createLayoutEvent(event, startIdx, span, lane, false)) 255 + } 256 + } 257 + } 258 + 259 + eventsWithLayout.sort((a,b) => a.lane !== b.lane ? a.lane - b.lane : a.startDay - b.startDay) 260 + allDayEventsWithLayout = eventsWithLayout 261 + allDaySectionHeight = maxLanes === 0 ? 0 : maxLanes === 1 ? 25 : Math.max(30, maxLanes * 25) 262 + 263 + return maxLanes 264 + } 265 + 266 + function createLayoutEvent(event, startDay, spanDays, lane, isCont) { 267 + return { 268 + id: event.id, title: event.title, description: event.description, location: event.location, 269 + startTime: event.startTime, endTime: event.endTime, allDay: event.allDay, multiDay: event.multiDay, 270 + daySpan: event.daySpan, rawStart: event.rawStart, rawEnd: event.rawEnd, duration: event.duration, 271 + endsAtMidnight: event.endsAtMidnight, fullStartTime: event.fullStartTime, fullEndTime: event.fullEndTime, 272 + startDay: startDay, spanDays: spanDays, lane: lane, isContinuation: isCont 273 + } 274 + } 275 + 276 + function updateOverlappingEvents() { 277 + var overlapData = {} 278 + for (var day = 0; day < 7; day++) processDayEventsWithLanes(day, overlapData) 279 + overlappingEventsData = overlapData 280 + } 281 + 282 + function processDayEventsWithLanes(day, data) { 283 + var events = [] 284 + for (var i = 0; i < eventsModel.count; i++) { 285 + var e = eventsModel.get(i) 286 + if (getDisplayDayIndexForDate(e.startTime) === day) { 287 + events.push({index: i, start: e.startTime.getTime(), end: e.endTime.getTime()}) 288 + } 289 + } 290 + if (events.length === 0) return 291 + 292 + events.sort((a,b) => a.start === b.start ? (b.end - b.start) - (a.end - a.start) : a.start - b.start) 293 + var groups = [], current = [], endTime = -1 294 + 295 + events.forEach(e => { 296 + if (e.start >= endTime) { 297 + if (current.length > 0) groups.push({events: current, endTime: endTime}) 298 + current = [e]; endTime = e.end 299 + } else { 300 + current.push(e) 301 + if (e.end > endTime) endTime = e.end 302 + } 303 + }) 304 + if (current.length > 0) groups.push({events: current, endTime: endTime}) 305 + groups.forEach(g => assignLanesToGroup(g.events, data)) 306 + } 307 + 308 + function assignLanesToGroup(group, data) { 309 + if (group.length === 0) return 310 + var laneEnds = [] 311 + group.forEach(e => { 312 + var placed = false 313 + for (var lane = 0; lane < laneEnds.length; lane++) { 314 + if (e.start >= laneEnds[lane]) { 315 + laneEnds[lane] = e.end 316 + e.lane = lane 317 + placed = true 318 + break 319 + } 320 + } 321 + if (!placed) { e.lane = laneEnds.length; laneEnds.push(e.end) } 322 + }) 323 + 324 + var total = laneEnds.length 325 + group.forEach(e => { 326 + data[e.index] = { 327 + xOffset: (e.lane / total) * (dayColumnWidth +1), 328 + width: (dayColumnWidth+1) / total, 329 + lane: e.lane, 330 + totalLanes: total 331 + } 332 + }) 333 + } 334 + 335 + // Range & formatting of calendar 336 + function calculateWeekStart(date, firstDay) { 337 + var d = new Date(date) 338 + var day = d.getDay() 339 + var diff = (day - firstDay + 7) % 7 340 + d.setDate(d.getDate() - diff) 341 + d.setHours(0, 0, 0, 0) 342 + return d 343 + } 344 + 345 + function calculateWeekDates(startDate) { 346 + var dates = [] 347 + var start = new Date(startDate) 348 + 349 + for (var i = 0; i < 7; i++) { 350 + var d = new Date(start) 351 + d.setDate(start.getDate() + i) 352 + dates.push(d) 353 + } 354 + 355 + return dates 356 + } 357 + 358 + function calculateWeekEnd(startDate) { 359 + var end = new Date(startDate) 360 + end.setDate(end.getDate() + 7) 361 + end.setHours(0, 0, 0, 0) 362 + return end 363 + } 364 + 365 + function isSameDay(date1, date2) { 366 + return date1.getDate() === date2.getDate() && 367 + date1.getMonth() === date2.getMonth() && 368 + date1.getFullYear() === date2.getFullYear() 369 + } 370 + 371 + function isToday(date) { 372 + var today = new Date() 373 + return isSameDay(date, today) 374 + } 375 + 376 + function isDateInRange(date, startDate, endDate) { 377 + return date >= startDate && date < endDate 378 + } 379 + 380 + function formatMonthRangeText(dates) { 381 + if (!dates || dates.length === 0) return "" 382 + var start = dates[0], end = dates[6], locale = I18n.locale 383 + return locale.toString(start, "yyyy-MM") === locale.toString(end, "yyyy-MM") 384 + ? locale.toString(start, "MMM yyyy") 385 + : start.getFullYear() === end.getFullYear() 386 + ? locale.toString(start, "MMM") + " – " + locale.toString(end, "MMM") + " " + start.getFullYear() 387 + : locale.toString(start, "MMM yyyy") + " – " + locale.toString(end, "MMM yyyy") 388 + } 389 + 390 + function isAllDayEvent(event) { 391 + var dur = event.end - event.start 392 + var start = new Date(event.start * 1000), end = new Date(event.end * 1000) 393 + var startsMidnight = start.getHours() === 0 && start.getMinutes() === 0 && start.getSeconds() === 0 394 + var endsMidnight = end.getHours() === 0 && end.getMinutes() === 0 && end.getSeconds() === 0 395 + return (dur === 86400 && startsMidnight) || (dur >= 86400 && endsMidnight) || dur >= 86400 396 + } 397 + 398 + function isMultiDayEvent(event) { 399 + var start = new Date(event.start * 1000), end = new Date(event.end * 1000) 400 + var endsMidnight = end.getHours() === 0 && end.getMinutes() === 0 && end.getSeconds() === 0 401 + var startDay = new Date(start.getFullYear(), start.getMonth(), start.getDate()) 402 + var endDay = endsMidnight ? new Date(end.getFullYear(), end.getMonth(), end.getDate() - 1) : 403 + new Date(end.getFullYear(), end.getMonth(), end.getDate()) 404 + return startDay.getTime() !== endDay.getTime() 405 + } 406 + 407 + function calculateDaySpan(start, end, isMultiDay) { 408 + if (!isMultiDay) return 1 409 + var startDay = new Date(start.getFullYear(), start.getMonth(), start.getDate()) 410 + var endDay = new Date(end.getFullYear(), end.getMonth(), end.getDate()) 411 + var diff = Math.floor((endDay - startDay) / 86400000) 412 + var endsMidnight = end.getHours() === 0 && end.getMinutes() === 0 && end.getSeconds() === 0 413 + return Math.max(1, endsMidnight ? diff : diff + 1) 414 + } 415 + 416 + function formatTime(date) { 417 + if (!date || isNaN(date.getTime())) return "" 418 + return use12hourFormat ? I18n.locale.toString(date, "h:mm AP") : I18n.locale.toString(date, "HH:mm") 419 + } 420 + 421 + function formatDateTime(date) { 422 + if (!date || isNaN(date.getTime())) return "" 423 + return I18n.locale.monthName(date.getMonth(), Locale.ShortFormat) + ' ' + 424 + date.getDate() + ', ' + date.getFullYear() + ' ' + formatTime(date) 425 + } 426 + 427 + function formatTimeRangeForDisplay(event) { 428 + var start = event.fullStartTime || event.startTime 429 + var end = event.fullEndTime || event.endTime 430 + return formatTime(start) + " - " + formatTime(end) 431 + } 432 + 433 + // Interaction functions 434 + function getEventTooltip(event) { 435 + var start = event.fullStartTime || event.startTime 436 + var end = event.fullEndTime || event.endTime 437 + var tip = event.title + "\n" + formatDateTime(start) + " - " + formatDateTime(end) 438 + if (event.location) tip += "\n⚲ " + event.location 439 + if (event.description) tip += "\n🛈 " + event.description 440 + return tip 441 + } 442 + 443 + function navigateWeek(days) { 444 + var d = new Date(currentDate) 445 + d.setDate(d.getDate() + days) 446 + currentDate = d 447 + } 448 + 449 + function handleEventClick(event) { 450 + const date = event.startTime || new Date(); 451 + const month = date.getMonth() + 1; 452 + const day = date.getDate(); 453 + const year = date.getFullYear(); 454 + const dateWithSlashes = `${month.toString().padStart(2, '0')}/${day.toString().padStart(2, '0')}/${year.toString().substring(2)}`; 455 + if (ProgramCheckerService.gnomeCalendarAvailable) { 456 + Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes]); 457 + } 458 + } 459 + 460 + function goToToday() { currentDate = new Date() } 461 + }
+516
weekly-calendar/Panel.qml
··· 1 + import QtQuick 2 + import QtQuick.Controls 3 + import QtQuick.Layouts 4 + import Quickshell 5 + import qs.Commons 6 + import qs.Services.Location 7 + import qs.Services.UI 8 + import qs.Widgets 9 + 10 + Item { 11 + id: root 12 + property var pluginApi: null 13 + readonly property var mainInstance: pluginApi?.mainInstance 14 + readonly property var geometryPlaceholder: panelContainer 15 + property real contentPreferredWidth: 950 * Style.uiScaleRatio 16 + property real contentPreferredHeight: 700 * Style.uiScaleRatio 17 + property real topHeaderHeight: 60 * Style.uiScaleRatio 18 + readonly property bool allowAttach: mainInstance ? mainInstance.panelModeSetting === "attached" : false 19 + readonly property bool panelAnchorHorizontalCenter: mainInstance ? mainInstance.panelModeSetting === "centered" : false 20 + readonly property bool panelAnchorVerticalCenter: mainInstance ? mainInstance.panelModeSetting === "centered" : false 21 + anchors.fill: parent 22 + 23 + property real hourHeight: 50 * Style.uiScaleRatio 24 + property real timeColumnWidth: 65 * Style.uiScaleRatio 25 + property real daySpacing: 1 * Style.uiScaleRatio 26 + 27 + // Attempt at live syncing 28 + Connections { 29 + target: CalendarService 30 + enabled: root.visible 31 + function onEventsChanged() { 32 + if (mainInstance) { 33 + Qt.callLater(() => { 34 + mainInstance.updateEventsFromService() 35 + mainInstance.calculateAllDayEventLayout() 36 + }) 37 + } 38 + } 39 + } 40 + 41 + Component.onCompleted: mainInstance?.initializePlugin() 42 + onVisibleChanged: if (visible && mainInstance) { 43 + mainInstance.updateEventsFromService() 44 + mainInstance.goToToday() 45 + Qt.callLater(root.scrollToCurrentTime) 46 + } 47 + 48 + // Scroll to time indicator position 49 + function scrollToCurrentTime() { 50 + if (!mainInstance || !calendarFlickable) return 51 + var now = new Date(), today = new Date(now.getFullYear(), now.getMonth(), now.getDate()) 52 + var weekStart = new Date(mainInstance.weekStart) 53 + var weekEnd = new Date(weekStart.getFullYear(), weekStart.getMonth(), weekStart.getDate() + 7) 54 + 55 + if (today >= weekStart && today < weekEnd) { 56 + var currentHour = now.getHours() + now.getMinutes() / 60 57 + var scrollPos = (currentHour * hourHeight) - (calendarFlickable.height / 2) 58 + var maxScroll = Math.max(0, (24 * hourHeight) - calendarFlickable.height) 59 + scrollAnim.targetY = Math.max(0, Math.min(scrollPos, maxScroll)) 60 + scrollAnim.start() 61 + } 62 + } 63 + 64 + // UI 65 + Rectangle { 66 + id: panelContainer 67 + anchors.fill: parent 68 + color: "transparent" 69 + 70 + ColumnLayout { 71 + anchors.fill: parent 72 + anchors.margins: Style.marginM 73 + spacing: Style.marginM 74 + 75 + //Header Section 76 + Rectangle { 77 + id: header 78 + Layout.fillWidth: true 79 + Layout.preferredHeight: topHeaderHeight 80 + color: Color.mSurfaceVariant 81 + radius: Style.radiusM 82 + 83 + RowLayout { 84 + anchors.margins: Style.marginM 85 + anchors.fill: parent 86 + 87 + NIcon { icon: "calendar-week"; pointSize: Style.fontSizeXXL; color: Color.mPrimary } 88 + 89 + ColumnLayout { 90 + Layout.fillHeight: true 91 + spacing: 0 92 + NText { 93 + text: pluginApi.tr("panel.header") 94 + font.pointSize: Style.fontSizeL; font.weight: Font.Bold; color: Color.mOnSurface 95 + } 96 + RowLayout { 97 + spacing: Style.marginS 98 + NText { 99 + text: mainInstance?.monthRangeText || "" 100 + font.pointSize: Style.fontSizeS; font.weight: Font.Medium; color: Color.mOnSurfaceVariant 101 + } 102 + Rectangle { 103 + Layout.preferredWidth: 8; Layout.preferredHeight: 8; radius: 4 104 + color: mainInstance?.isLoading ? Color.mError : 105 + mainInstance?.syncStatus?.includes("No") ? Color.mError : Color.mOnSurfaceVariant 106 + } 107 + NText { 108 + text: mainInstance?.syncStatus || "" 109 + font.pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant 110 + } 111 + } 112 + } 113 + 114 + Item { Layout.fillWidth: true } 115 + 116 + RowLayout { 117 + spacing: Style.marginS 118 + NIconButton { 119 + icon: "chevron-left" 120 + onClicked: { mainInstance?.navigateWeek(-7); mainInstance?.loadEvents() } 121 + } 122 + NIconButton { 123 + icon: "calendar"; tooltipText: pluginApi.tr("panel.today") 124 + onClicked: { mainInstance?.goToToday(); mainInstance?.loadEvents(); Qt.callLater(root.scrollToCurrentTime) } 125 + } 126 + NIconButton { 127 + icon: "chevron-right" 128 + onClicked: { mainInstance?.navigateWeek(7); mainInstance?.loadEvents() } 129 + } 130 + NIconButton { 131 + icon: "refresh"; tooltipText: I18n.tr("common.refresh") 132 + onClicked: mainInstance?.loadEvents() 133 + enabled: mainInstance ? !mainInstance.isLoading : false 134 + } 135 + NIconButton { 136 + icon: "close"; tooltipText: I18n.tr("common.close") 137 + onClicked: pluginApi.closePanel(pluginApi.panelOpenScreen) 138 + } 139 + } 140 + } 141 + } 142 + 143 + // Calendar 144 + Rectangle { 145 + Layout.fillWidth: true 146 + Layout.fillHeight: true 147 + color: Color.mSurfaceVariant 148 + radius: Style.radiusM 149 + clip: true 150 + 151 + Column { 152 + anchors.fill: parent 153 + spacing: 0 154 + 155 + //Day Headers 156 + Rectangle { 157 + id: dayHeaders 158 + width: parent.width 159 + height: 56 160 + color: Color.mSurfaceVariant 161 + radius: Style.radiusM 162 + 163 + Row { 164 + anchors.fill: parent 165 + anchors.leftMargin: root.timeColumnWidth 166 + spacing: root.daySpacing 167 + 168 + Repeater { 169 + model: 7 170 + Rectangle { 171 + width: mainInstance?.dayColumnWidth 172 + height: parent.height 173 + color: "transparent" 174 + property date dayDate: mainInstance?.weekDates?.[index] || new Date() 175 + property bool isToday: { 176 + var today = new Date() 177 + return dayDate.getDate() === today.getDate() && 178 + dayDate.getMonth() === today.getMonth() && 179 + dayDate.getFullYear() === today.getFullYear() 180 + } 181 + Rectangle { 182 + anchors.fill: parent 183 + anchors.margins: 4 184 + color: Color.mSurfaceVariant 185 + border.color: isToday ? Color.mPrimary : "transparent" 186 + border.width: 2 187 + radius: Style.radiusM 188 + Column { 189 + anchors.centerIn: parent 190 + spacing: 2 191 + NText { 192 + anchors.horizontalCenter: parent.horizontalCenter 193 + text: dayDate ? I18n.locale.dayName(dayDate.getDay(), Locale.ShortFormat).toUpperCase() : "" 194 + color: isToday ? Color.mPrimary : Color.mOnSurface 195 + font.pointSize: Style.fontSizeS; font.weight: Font.Medium 196 + } 197 + NText { 198 + anchors.horizontalCenter: parent.horizontalCenter 199 + text: dayDate ? ((dayDate.getDate() < 10 ? "0" : "") + dayDate.getDate()) : "" 200 + color: isToday ? Color.mPrimary : Color.mOnSurface 201 + font.pointSize: Style.fontSizeM; font.weight: Font.Bold 202 + } 203 + } 204 + } 205 + } 206 + } 207 + } 208 + } 209 + // All-day row 210 + Rectangle { 211 + id: allDayEventsSection 212 + width: parent.width 213 + height: mainInstance ? Math.round(mainInstance.allDaySectionHeight * Style.uiScaleRatio) : 0 214 + color: Color.mSurfaceVariant 215 + visible: height > 0 216 + 217 + Item { 218 + id: allDayEventsContainer 219 + anchors.fill: parent 220 + anchors.leftMargin: root.timeColumnWidth 221 + 222 + Repeater { 223 + model: 6 224 + delegate: Rectangle { 225 + width: 1; height: parent.height 226 + x: (index + 1) * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) - ((root.daySpacing) / 2) 227 + color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.dayLineOpacitySetting || 0.9) 228 + } 229 + } 230 + 231 + Repeater { 232 + model: mainInstance?.allDayEventsWithLayout || [] 233 + delegate: Item { 234 + property var eventData: modelData 235 + x: eventData.startDay * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) 236 + y: eventData.lane * 25 237 + width: (eventData.spanDays * ((mainInstance?.dayColumnWidth) + (root.daySpacing))) - (root.daySpacing) 238 + height: 24 239 + 240 + Rectangle { 241 + anchors.fill: parent 242 + color: Color.mTertiary 243 + radius: Style.radiusS 244 + NText { 245 + anchors.fill: parent; anchors.margins: 4 246 + text: eventData.title 247 + color: Color.mOnTertiary 248 + font.pointSize: Style.fontSizeXXS; font.weight: Font.Medium 249 + elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter 250 + } 251 + } 252 + MouseArea { 253 + anchors.fill: parent 254 + hoverEnabled: true 255 + cursorShape: Qt.PointingHandCursor 256 + onEntered: { 257 + var tip = mainInstance?.getEventTooltip(eventData) || "" 258 + TooltipService.show(parent, tip, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed) 259 + } 260 + onClicked: mainInstance?.handleEventClick(eventData) 261 + onExited: TooltipService.hide() 262 + } 263 + } 264 + } 265 + } 266 + } 267 + // Calendar flickable 268 + Rectangle { 269 + width: parent.width 270 + height: parent.height - dayHeaders.height - (allDayEventsSection.visible ? allDayEventsSection.height : 0) 271 + color: Color.mSurfaceVariant 272 + radius: Style.radiusM 273 + clip: true 274 + 275 + Flickable { 276 + id: calendarFlickable 277 + anchors.fill: parent 278 + clip: true 279 + contentHeight: 24 * (root.hourHeight) 280 + boundsBehavior: Flickable.DragOverBounds 281 + 282 + Component.onCompleted: { 283 + calendarFlickable.forceActiveFocus() 284 + } 285 + 286 + // Keyboard interaction 287 + Keys.onPressed: function(event) { 288 + if (event.key === Qt.Key_Up || event.key === Qt.Key_Down) { 289 + var step = root.hourHeight 290 + var targetY = event.key === Qt.Key_Up ? Math.max(0, contentY - step) : 291 + Math.min(Math.max(0, contentHeight - height), contentY + step) 292 + scrollAnim.targetY = targetY 293 + scrollAnim.start() 294 + event.accepted = true 295 + } else if (event.key === Qt.Key_Left || event.key === Qt.Key_Right) { 296 + if (mainInstance) { 297 + mainInstance.navigateWeek(event.key === Qt.Key_Left ? -7 : 7) 298 + mainInstance.loadEvents() 299 + } 300 + event.accepted = true 301 + } 302 + } 303 + 304 + NumberAnimation { 305 + id: scrollAnim 306 + target: calendarFlickable; property: "contentY"; duration: 100 307 + easing.type: Easing.OutCubic; property real targetY: 0; to: targetY 308 + } 309 + 310 + ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } 311 + 312 + Row { 313 + width: parent.width 314 + height: parent.height 315 + 316 + // Time Column 317 + Column { 318 + width: root.timeColumnWidth 319 + height: parent.height 320 + Repeater { 321 + model: 23 322 + Rectangle { 323 + width: root.timeColumnWidth 324 + height: root.hourHeight 325 + color: "transparent" 326 + NText { 327 + text: { 328 + var hour = index + 1 329 + if (mainInstance?.use12hourFormat) { 330 + var d = new Date(); d.setHours(hour, 0, 0, 0) 331 + return mainInstance.formatTime(d) 332 + } 333 + return (hour < 10 ? "0" : "") + hour + ':00' 334 + } 335 + anchors.right: parent.right 336 + anchors.rightMargin: Style.marginS 337 + anchors.verticalCenter: parent.top 338 + anchors.verticalCenterOffset: root.hourHeight 339 + font.pointSize: Style.fontSizeXS; color: Color.mOnSurfaceVariant 340 + } 341 + } 342 + } 343 + } 344 + 345 + // Hour Rectangles 346 + Item { 347 + width: 7 * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) 348 + height: parent.height 349 + 350 + Row { 351 + anchors.fill: parent 352 + spacing: root.daySpacing 353 + Repeater { 354 + model: 7 355 + Column { 356 + width: mainInstance?.dayColumnWidth 357 + height: parent.height 358 + Repeater { 359 + model: 24 360 + Rectangle { width: parent.width; height: 1; color: Color.mSurfaceVariant } 361 + } 362 + } 363 + } 364 + } 365 + // Hour Lines 366 + Repeater { 367 + model: 24 368 + Rectangle { 369 + width: parent.width; height: 1 370 + y: index * (root.hourHeight) 371 + color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.hourLineOpacitySetting || 0.5) 372 + } 373 + } 374 + // Day Lines 375 + Repeater { 376 + model: 6 377 + Rectangle { 378 + width: 1; height: parent.height 379 + x: (index + 1) * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) - ((root.daySpacing) / 2) 380 + color: Qt.alpha(mainInstance?.lineColor || Color.mOutline, mainInstance?.dayLineOpacitySetting || 0.9) 381 + } 382 + } 383 + 384 + // Event positioning 385 + Repeater { 386 + model: mainInstance?.eventsModel 387 + delegate: Item { 388 + property var eventData: model 389 + property int dayIndex: mainInstance?.getDisplayDayIndexForDate(model.startTime) ?? -1 390 + property real startHour: model.startTime.getHours() + model.startTime.getMinutes() / 60 391 + property real endHour: model.endTime.getHours() + model.endTime.getMinutes() / 60 392 + property real duration: Math.max(0, (model.endTime - model.startTime) / 3600000) 393 + 394 + property real exactHeight: Math.max(1, duration * (mainInstance?.hourHeight || 50) - 1) 395 + property bool isCompact: exactHeight < 40 396 + property var overlapInfo: mainInstance?.overlappingEventsData?.[index] ?? { 397 + xOffset: 0, width: (mainInstance?.dayColumnWidth) - 8, lane: 0, totalLanes: 1 398 + } 399 + property real eventWidth: overlapInfo.width - 1 400 + property real eventXOffset: overlapInfo.xOffset 401 + 402 + visible: dayIndex >= 0 && dayIndex < 7 && duration > 0 403 + width: eventWidth 404 + height: exactHeight 405 + x: dayIndex * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) + eventXOffset 406 + y: startHour * (mainInstance?.hourHeight || 50) 407 + z: 100 + overlapInfo.lane 408 + 409 + Rectangle { 410 + anchors.fill: parent 411 + color: Color.mPrimary 412 + radius: Style.radiusS 413 + opacity: 0.9 414 + clip: true 415 + Rectangle { 416 + visible: exactHeight < 5 && overlapInfo.lane > 0 417 + anchors.fill: parent 418 + color: "transparent" 419 + radius: parent.radius 420 + border.width: 1 421 + border.color: Color.mPrimary 422 + } 423 + Loader { 424 + anchors.fill: parent 425 + anchors.margins: exactHeight < 10 ? 1 : Style.marginS 426 + anchors.leftMargin: exactHeight < 10 ? 1 : Style.marginS + 3 427 + sourceComponent: isCompact ? compactLayout : normalLayout 428 + } 429 + } 430 + 431 + Component { 432 + id: normalLayout 433 + Column { 434 + spacing: 2 435 + width: parent.width - 3 436 + NText { 437 + visible: exactHeight >= 20 438 + text: model.title 439 + color: Color.mOnPrimary 440 + font.pointSize: Style.fontSizeXS; font.weight: Font.Medium 441 + elide: Text.ElideRight; width: parent.width 442 + } 443 + NText { 444 + visible: exactHeight >= 30 445 + text: mainInstance?.formatTimeRangeForDisplay(model) || "" 446 + color: Color.mOnPrimary 447 + font.pointSize: Style.fontSizeXXS; opacity: 0.9 448 + elide: Text.ElideRight; width: parent.width 449 + } 450 + } 451 + } 452 + 453 + Component { 454 + id: compactLayout 455 + NText { 456 + text: exactHeight < 15 ? model.title : 457 + model.title + " • " + (mainInstance?.formatTimeRangeForDisplay(model) || "") 458 + color: Color.mOnPrimary 459 + font.pointSize: exactHeight < 15 ? Style.fontSizeXXS : Style.fontSizeXS 460 + font.weight: Font.Medium 461 + elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter 462 + width: parent.width - 3 463 + } 464 + } 465 + 466 + MouseArea { 467 + anchors.fill: parent 468 + hoverEnabled: true 469 + cursorShape: Qt.PointingHandCursor 470 + onEntered: { 471 + var tip = mainInstance?.getEventTooltip(model) || "" 472 + TooltipService.show(parent, tip, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed) 473 + } 474 + onClicked: mainInstance?.handleEventClick(eventData) 475 + onExited: TooltipService.hide() 476 + } 477 + } 478 + } 479 + 480 + // Time Indicator 481 + Rectangle { 482 + property var now: new Date() 483 + property date today: new Date(now.getFullYear(), now.getMonth(), now.getDate()) 484 + property date weekStartDate: mainInstance?.weekStart ?? new Date() 485 + property date weekEndDate: mainInstance ? 486 + new Date(mainInstance.weekStart.getFullYear(), mainInstance.weekStart.getMonth(), mainInstance.weekStart.getDate() + 7) : new Date() 487 + property bool inCurrentWeek: today >= weekStartDate && today < weekEndDate 488 + property int currentDay: mainInstance?.getDayIndexForDate(now) ?? -1 489 + property real currentHour: now.getHours() + now.getMinutes() / 60 490 + 491 + visible: inCurrentWeek && currentDay >= 0 492 + width: mainInstance?.dayColumnWidth 493 + height: 2 494 + x: currentDay * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) 495 + y: currentHour * (root.hourHeight) 496 + color: Color.mError 497 + radius: 1 498 + z: 1000 499 + Rectangle { 500 + width: 8; height: 8; radius: 4; color: Color.mError 501 + anchors.verticalCenter: parent.verticalCenter; x: -4 502 + } 503 + Timer { 504 + interval: 60000; running: true; repeat: true 505 + onTriggered: parent.now = new Date() 506 + } 507 + } 508 + } 509 + } 510 + } 511 + } 512 + } 513 + } 514 + } 515 + } 516 + }
+43
weekly-calendar/README.md
··· 1 + # Weekly Calendar Plugin 2 + 3 + A weekly calendar plugin that makes use of Noctalia's [CalendarService.qml](https://github.com/noctalia-dev/noctalia-shell/blob/main/Services/Location/CalendarService.qml) to display events. 4 + ## Features 5 + 6 + - **Bar Widget**: Show a quick tooltip to see current and upcoming events on hover (updates occur on click); 7 + - **Panel**: Complete weekly calendar interface supporting both all-day and normal events with a native-like interface; 8 + - **Keyboard Navigation**: Use arrow keys to navigate through your events; 9 + - **Settings**: Choose time format, first day of the week, grid line colour and opacity. 10 + 11 + ## Installation 12 + 13 + This plugin is part of the [noctalia-plugins](https://github.com/noctalia-dev/noctalia-plugins) repository, installation can either be done by: 14 + 1. adding it as a source in the plugins section; 15 + 2. cloning this into `~/.config/noctalia/plugins`. 16 + 17 + ## Configuration 18 + 19 + - **First day of week**: Choose which day starts the week in the weekly calendar (Monday, Sunday or Saturday). 20 + - **Use 12-hour format**: Display time in 12-hour format (AM/PM) instead of 24-hour format. 21 + - **Grid Lines**: Depending on your theming, choose the color and opacity used for the grid lines for better visibility. 22 + 23 + ## IPC Commands 24 + 25 + Currently there is `1` IPC command: 26 + 27 + ```bash 28 + # Toggle the panel 29 + qs -c noctalia-shell ipc call plugin:weekly-calendar togglePanel 30 + ``` 31 + 32 + ## Usage 33 + 34 + As a result of relying on Noctalia's [CalendarService.qml](https://github.com/noctalia-dev/noctalia-shell/blob/main/Services/Location/CalendarService.qml), this plugin supports any calendar that works with `evolution-data-server`, covering all major calendar services (e.g. NextCloud, Google Calendar, and CalDAV / WebDAV servers). 35 + 36 + The most straightforward ways to start is by either downloading `evolution` or `gnome-calendar` and set calendars through them. 37 + 38 + ## Minimum Requirements 39 + 40 + - **Noctalia Shell**: 4.2.3 or later. 41 + - `evolution-data-server` (also needed for Noctalia's month calendar to display events) 42 + - **Python packages**: EDataServer, ECal, and ICalGLib. (see [calendar-events.py](https://github.com/noctalia-dev/noctalia-shell/blob/main/Scripts/python/src/calendar/calendar-events.py)) 43 +
+185
weekly-calendar/Settings.qml
··· 1 + import QtQuick 2 + import QtQuick.Layouts 3 + import qs.Commons 4 + import qs.Widgets 5 + 6 + ColumnLayout { 7 + id: root 8 + 9 + property var pluginApi: null 10 + 11 + property string weekStart: "1" 12 + property string timeFormat: "24h" 13 + property string lineColorType: "mOutline" 14 + property string panelMode: "attached" 15 + property real hourLineOpacity: 0.5 16 + property real dayLineOpacity: 1.0 17 + 18 + spacing: Style.marginL 19 + 20 + Component.onCompleted: { 21 + Logger.i("WeeklyCalendar", "Settings UI loaded") 22 + 23 + if (pluginApi?.pluginSettings) { 24 + panelMode = pluginApi.pluginSettings.panelMode || "attached" 25 + weekStart = pluginApi.pluginSettings.weekStart || "1" 26 + timeFormat = pluginApi.pluginSettings.timeFormat || "24h" 27 + lineColorType = pluginApi.pluginSettings.lineColorType || "mOutline" 28 + hourLineOpacity = pluginApi.pluginSettings.hourLineOpacity || 0.5 29 + dayLineOpacity = pluginApi.pluginSettings.dayLineOpacity || 0.9 30 + } 31 + } 32 + 33 + // Time Format toggle 34 + NToggle { 35 + label: pluginApi.tr("settings.timeFormat") 36 + description: pluginApi.tr("settings.timeFormat_description") 37 + checked: root.timeFormat === pluginApi.tr("settings.12h") 38 + onToggled: checked => root.timeFormat = checked ? pluginApi.tr("settings.12h") : pluginApi.tr("settings.24h") 39 + } 40 + 41 + // Week Start selector 42 + NComboBox { 43 + Layout.fillWidth: true 44 + label: pluginApi.tr("settings.weekStart") 45 + description: pluginApi.tr("settings.weekStart_description") 46 + model: [ 47 + {"key": "1", "name": pluginApi.tr("settings.monday")}, 48 + {"key": "0", "name": pluginApi.tr("settings.sunday")}, 49 + {"key": "6", "name": pluginApi.tr("settings.saturday")} 50 + ] 51 + currentKey: root.weekStart 52 + onSelected: key => root.weekStart = key 53 + } 54 + 55 + NDivider { 56 + Layout.fillWidth: true 57 + Layout.topMargin: Style.marginM 58 + Layout.bottomMargin: Style.marginM 59 + } 60 + 61 + // Line color type selector 62 + NComboBox { 63 + Layout.fillWidth: true 64 + label: pluginApi.tr("settings.gridColor") 65 + description: pluginApi.tr("settings.gridColor_description") 66 + model: [ 67 + {"key": "mOutline", "name": pluginApi.tr("settings.colorOutline")}, 68 + {"key": "mOnSurfaceVariant", "name": pluginApi.tr("settings.colorOnSurfaceVariant")} 69 + ] 70 + currentKey: root.lineColorType 71 + onSelected: key => root.lineColorType = key 72 + } 73 + 74 + // Hour line opacity slider 75 + ColumnLayout { 76 + Layout.fillWidth: true 77 + spacing: Style.marginS 78 + NLabel { 79 + label: pluginApi.tr("settings.hourLineOpacity") 80 + } 81 + RowLayout { 82 + Layout.fillWidth: true 83 + NText { 84 + text: pluginApi.tr("settings.hourLineOpacity_description") 85 + font.pointSize: Style.fontSizeS 86 + color: Color.mOnSurfaceVariant 87 + wrapMode: Text.WordWrap 88 + Layout.fillWidth: true 89 + } 90 + Item { Layout.fillWidth: true } 91 + NText { 92 + text: Math.round(root.hourLineOpacity * 100) + "%" 93 + font.pointSize: Style.fontSizeS 94 + color: Color.mOnSurfaceVariant 95 + } 96 + } 97 + 98 + NSlider { 99 + Layout.fillWidth: true 100 + from: 0.1 101 + to: 1 102 + stepSize: 0.1 103 + value: root.hourLineOpacity 104 + onValueChanged: root.hourLineOpacity = value 105 + } 106 + } 107 + 108 + // Day line opacity slider 109 + ColumnLayout { 110 + Layout.fillWidth: true 111 + spacing: Style.marginS 112 + NLabel { 113 + label: pluginApi.tr("settings.dayLineOpacity") 114 + } 115 + RowLayout { 116 + Layout.fillWidth: true 117 + NText { 118 + text: pluginApi.tr("settings.dayLineOpacity_description") 119 + font.pointSize: Style.fontSizeS 120 + color: Color.mOnSurfaceVariant 121 + wrapMode: Text.WordWrap 122 + Layout.fillWidth: true 123 + } 124 + Item { Layout.fillWidth: true } 125 + NText { 126 + text: Math.round(root.dayLineOpacity * 100) + "%" 127 + font.pointSize: Style.fontSizeS 128 + color: Color.mOnSurfaceVariant 129 + } 130 + } 131 + 132 + NSlider { 133 + Layout.fillWidth: true 134 + from: 0.1 135 + to: 1 136 + stepSize: 0.1 137 + value: root.dayLineOpacity 138 + onValueChanged: root.dayLineOpacity = value 139 + } 140 + 141 + } 142 + 143 + NDivider { 144 + Layout.fillWidth: true 145 + Layout.topMargin: Style.marginM 146 + Layout.bottomMargin: Style.marginM 147 + } 148 + 149 + NComboBox { 150 + Layout.fillWidth: true 151 + label: pluginApi.tr("settings.panelMode") 152 + description: pluginApi.tr("settings.panelMode_description") 153 + model: [ 154 + {"key": "attached", "name": pluginApi.tr("settings.panelAttach")}, 155 + {"key": "centered", "name": pluginApi.tr("settings.panelCenter")}, 156 + ] 157 + currentKey: root.panelMode 158 + onSelected: key => root.panelMode = key 159 + } 160 + 161 + function saveSettings() { 162 + if (!pluginApi) { 163 + Logger.e("WeeklyCalendar", "Cannot save settings: pluginApi is null") 164 + return 165 + } 166 + 167 + if (!pluginApi.pluginSettings) { 168 + pluginApi.pluginSettings = {} 169 + } 170 + pluginApi.pluginSettings.panelMode = panelMode 171 + pluginApi.pluginSettings.weekStart = weekStart 172 + pluginApi.pluginSettings.timeFormat = timeFormat 173 + pluginApi.pluginSettings.lineColorType = lineColorType 174 + pluginApi.pluginSettings.hourLineOpacity = hourLineOpacity 175 + pluginApi.pluginSettings.dayLineOpacity = dayLineOpacity 176 + pluginApi.saveSettings() 177 + 178 + Logger.i("WeeklyCalendar", "Settings saved: weekStart=" + weekStart + 179 + ", timeFormat=" + timeFormat + 180 + ", panelMode=" + panelMode + 181 + ", lineColorType=" + lineColorType + 182 + ", hourLineOpacity=" + hourLineOpacity + 183 + ", dayLineOpacity=" + dayLineOpacity) 184 + } 185 + }
+39
weekly-calendar/i18n/de.json
··· 1 + { 2 + "bar_widget": { 3 + "settings": "Widget-Einstellungen", 4 + "now": "Jetzt", 5 + "next": "Nächste", 6 + "no_more_events_today": "Heute keine weiteren Ereignisse" 7 + }, 8 + "panel": { 9 + "header": "Wochenkalender", 10 + "today": "Heute", 11 + "event": "Ereignis", 12 + "events": "Ereignisse", 13 + "allday": "ganztätig", 14 + "no_service": "Kein Kalenderdienst", 15 + "loading": "Lädt...", 16 + "no_events": "Keine Ereignisse" 17 + }, 18 + "settings": { 19 + "weekStart": "Erster Tag der Woche", 20 + "weekStart_description": "Wählen Sie, welcher Tag die Woche im Kalender beginnen soll.", 21 + "monday": "Montag", 22 + "sunday": "Sonntag", 23 + "saturday": "Samstag", 24 + "timeFormat": "12-Stunden-Zeitformat benutzen", 25 + "timeFormat_description": "Zeit im 12‑Stunden‑Format (AM/PM) statt im 24‑Stunden‑Format anzeigen.", 26 + "gridColor": "Rasterfarbe", 27 + "gridColor_description": "Wähle die Farbe für die Kalender-Rasterlinien.", 28 + "colorOutline": "Outline", 29 + "colorOnSurfaceVariant": "OnSurfaceVariant", 30 + "hourLineOpacity": "Opazität der Stundenlinien", 31 + "hourLineOpacity_description": "Passe die Deckkraft der Stundenlinien an.", 32 + "dayLineOpacity": "Opazität der Tageslinien", 33 + "dayLineOpacity_description": "Passe die Deckkraft der Trennlinien zwischen Tagen an.", 34 + "panelMode": "Panel-Modus", 35 + "panelMode_description": "Wählen Sie das Layout (möglicherweise ist ein Neustart erforderlich)", 36 + "panelAttach": "Panel an der Leiste angeheftet", 37 + "panelCenter": "Zentriertes Panel" 38 + } 39 + }
+39
weekly-calendar/i18n/en.json
··· 1 + { 2 + "bar_widget": { 3 + "settings": "Widget Settings", 4 + "now": "Now", 5 + "next": "Next", 6 + "no_more_events_today": "No events remaining today" 7 + }, 8 + "panel": { 9 + "header": "Weekly Calendar", 10 + "today": "Today", 11 + "event": "event", 12 + "events": "events", 13 + "allday": "all-day", 14 + "no_service": "No calendar service", 15 + "loading": "Loading...", 16 + "no_events": "No events" 17 + }, 18 + "settings": { 19 + "weekStart": "First day of week", 20 + "weekStart_description": "Choose which day starts the week in the calendar.", 21 + "monday": "Monday", 22 + "sunday": "Sunday", 23 + "saturday": "Saturday", 24 + "timeFormat": "Use 12-hour time format", 25 + "timeFormat_description": "Display time in 12-hour format (AM/PM) instead of 24-hour format.", 26 + "gridColor": "Grid Color", 27 + "gridColor_description": "Choose the color used for calendar grid lines.", 28 + "colorOutline": "Outline", 29 + "colorOnSurfaceVariant": "OnSurfaceVariant", 30 + "hourLineOpacity": "Hour Line Opacity", 31 + "hourLineOpacity_description": "Adjust the opacity of hour lines.", 32 + "dayLineOpacity": "Day Line Opacity", 33 + "dayLineOpacity_description": "Adjust the opacity of day separator lines.", 34 + "panelMode": "Panel Mode", 35 + "panelMode_description": "Choose layout (may require reopening).", 36 + "panelAttach": "Panel attached to bar", 37 + "panelCenter": "Centered panel" 38 + } 39 + }
+39
weekly-calendar/i18n/es.json
··· 1 + { 2 + "bar_widget": { 3 + "settings": "Configuratión del widget", 4 + "now": "Ahora", 5 + "next": "Siguiente", 6 + "no_more_events_today": "No quedan eventos hoy" 7 + }, 8 + "panel": { 9 + "header": "Calendario semanal", 10 + "today": "Hoy", 11 + "event": "evento", 12 + "events": "eventos", 13 + "allday": "todo el día", 14 + "no_service": "Sin servicio de calendario", 15 + "loading": "Cargando...", 16 + "no_events": "Sin eventos" 17 + }, 18 + "settings": { 19 + "weekStart": "Primer día de la semana", 20 + "weekStart_description": "Elige qué día empieza la semana en el calendario.", 21 + "monday": "Lunes", 22 + "sunday": "Domingo", 23 + "saturday": "Sábado", 24 + "timeFormat": "Utilice el formato de hora de 12 horas", 25 + "timeFormat_description": "Mostrar la hora en formato de 12 horas (AM/PM) en lugar de 24 horas.", 26 + "gridColor": "Color de la cuadrícula" , 27 + "gridColor_description": "Elige el color usado para las líneas de la cuadrícula del calendario.", 28 + "colorOutline": "Outline", 29 + "colorOnSurfaceVariant": "OnSurfaceVariant", 30 + "hourLineOpacity": "Opacidad de las líneas de hora", 31 + "hourLineOpacity_description": "Ajusta la opacidad de las líneas de hora.", 32 + "dayLineOpacity": "Opacidad de las líneas diarias", 33 + "dayLineOpacity_description": "Ajusta la opacidad de las líneas separadoras de día.", 34 + "panelMode": "Modo del panel", 35 + "panelMode_description": "Elegir diseño (puede requerir reapertura).", 36 + "panelAttach": "Panel adjunto a la barra", 37 + "panelCenter": "Panel centrado" 38 + } 39 + }
+39
weekly-calendar/i18n/fr.json
··· 1 + { 2 + "bar_widget": { 3 + "settings": "Paramètres du widget", 4 + "now": "Maintenant", 5 + "next": "Suivant", 6 + "no_more_events_today": "Plus d'événements aujourd'hui" 7 + }, 8 + "panel": { 9 + "header": "Calendrier hebdomadaire", 10 + "today": "Aujourd'hui", 11 + "event": "événement", 12 + "events": "événements", 13 + "allday": "toute la journée", 14 + "no_service": "Pas de service de calendrier", 15 + "loading": "Chargement...", 16 + "no_events": "Aucun événement" 17 + }, 18 + "settings": { 19 + "weekStart": "Début de la semaine", 20 + "weekStart_description": "Choisissez quel jour commence la semaine dans le calendrier.", 21 + "monday": "Lundi", 22 + "sunday": "Dimanche", 23 + "saturday": "Samedi", 24 + "timeFormat": "Utiliser le format horaire de 12 heures", 25 + "timeFormat_description": "Afficher l'heure au format 12 heures (AM/PM) au lieu du format 24 heures.", 26 + "gridColor": "Couleur de la grille", 27 + "gridColor_description": "Choisissez la couleur utilisée pour les lignes de la grille du calendrier.", 28 + "colorOutline": "Outline", 29 + "colorOnSurfaceVariant": "OnSurfaceVariant", 30 + "hourLineOpacity": "Opacité des lignes horaires", 31 + "hourLineOpacity_description": "Ajustez l'opacité des lignes horaires.", 32 + "dayLineOpacity": "Opacité des lignes journalières", 33 + "dayLineOpacity_description": "Ajustez l'opacité des lignes séparatrices de jour.", 34 + "panelMode": "Mode du panneau", 35 + "panelMode_description": "Choisir la disposition (peut nécessiter une réouverture).", 36 + "panelAttach": "Panneau attaché à la barre", 37 + "panelCenter": "Panneau centré" 38 + } 39 + }
+39
weekly-calendar/i18n/hu.json
··· 1 + { 2 + "bar_widget": { 3 + "settings": "Widget beállítások", 4 + "now": "Most", 5 + "next": "Következő", 6 + "no_more_events_today": "Ma nincs több esemény" 7 + }, 8 + "panel": { 9 + "header": "Heti naptár", 10 + "today": "Ma", 11 + "event": "esemény", 12 + "events": "események", 13 + "allday": "egész nap", 14 + "no_service": "Nincs naptárszolgáltatás", 15 + "loading": "Betöltés...", 16 + "no_events": "Nincs esemény" 17 + }, 18 + "settings": { 19 + "weekStart": "A hét első napja", 20 + "weekStart_description": "Válassza ki, melyik nap kezdje a hetet a naptárban.", 21 + "monday": "Hétfő", 22 + "sunday": "Vasárnap", 23 + "saturday": "Szombat", 24 + "timeFormat": "12 órás időformátum használata", 25 + "timeFormat_description": "Az idő megjelenítése 12 órás formátumban (DE/DU) a 24 órás helyett.", 26 + "gridColor": "Rácsszín", 27 + "gridColor_description": "Válassza ki a naptár rácsvonalaihoz használt színt.", 28 + "colorOutline": "Outline", 29 + "colorOnSurfaceVariant": "OnSurfaceVariant", 30 + "hourLineOpacity": "Óravonal átlátszósága", 31 + "hourLineOpacity_description": "Állítsa be az óravonalak átlátszóságát.", 32 + "dayLineOpacity": "Napi vonal átlátszósága", 33 + "dayLineOpacity_description": "Állítsa be a napelválasztó vonalak átlátszóságát.", 34 + "panelMode": "Panel mód", 35 + "panelMode_description": "Válasszon elrendezést (újraindításra lehet szükség).", 36 + "panelAttach": "Panel a sávhoz rögzítve", 37 + "panelCenter": "Középre igazított panel" 38 + } 39 + }
+39
weekly-calendar/i18n/it.json
··· 1 + { 2 + "bar_widget": { 3 + "settings": "Impostazioni Widget", 4 + "now": "Ora", 5 + "next": "Prossimo", 6 + "no_more_events_today": "Nessun evento rimanente per oggi" 7 + }, 8 + "panel": { 9 + "header": "Calendario settimanale", 10 + "today": "Oggi", 11 + "event": "evento", 12 + "events": "eventi", 13 + "allday": "giornata intera", 14 + "no_service": "Nessun servizio per il calendario", 15 + "loading": "Caricamento in corso...", 16 + "no_events": "Nessun evento" 17 + }, 18 + "settings": { 19 + "weekStart": "Primo giorno della settimana", 20 + "weekStart_description": "Scegli il giorno che fa iniziare la settimana.", 21 + "monday": "Lunedì", 22 + "sunday": "Domenica", 23 + "saturday": "Sabato", 24 + "timeFormat": "Usa sistema orario a 12 ore", 25 + "timeFormat_description": "Mostra il tempo con il sistema orario a 12 ore (AM/PM) invece del sistema a 24 ore.", 26 + "gridColor": "Colore griglia", 27 + "gridColor_description": "Scegli il colore usato per le linee della griglia del calendario.", 28 + "colorOutline": "Outline", 29 + "colorOnSurfaceVariant": "OnSurfaceVariant", 30 + "hourLineOpacity": "Opacità delle linee orarie", 31 + "hourLineOpacity_description": "Regola l'opacità della linee orarie.", 32 + "dayLineOpacity": "Opacità delle linee giornaliere", 33 + "dayLineOpacity_description": "Regola l'opacità delle linee separatrici dei giorni.", 34 + "panelMode": "Modalità del pannello", 35 + "panelMode_description": "Scegli il layout (potrebbe essere necessario riaprirlo).", 36 + "panelAttach": "Pannello attacato alla barra", 37 + "panelCenter": "Pannello centrato" 38 + } 39 + }
+39
weekly-calendar/i18n/ja.json
··· 1 + { 2 + "bar_widget": { 3 + "settings": "ウィジェット設定", 4 + "now": "現在", 5 + "next": "次へ", 6 + "no_more_events_today": "今日の予定はありません" 7 + }, 8 + "panel": { 9 + "header": "週間カレンダー", 10 + "today": "今日", 11 + "event": "イベント", 12 + "events": "イベント", 13 + "allday": "終日", 14 + "no_service": "カレンダーサービスがありません", 15 + "loading": "読み込み中...", 16 + "no_events": "予定はありません" 17 + }, 18 + "settings": { 19 + "weekStart": "週の始まり", 20 + "weekStart_description": "カレンダーパネルにイベント(予定)を表示します。", 21 + "monday": "月曜日", 22 + "sunday": "日曜日", 23 + "saturday": "土曜日", 24 + "timeFormat": "12時間表記を使用", 25 + "timeFormat_description": "時刻を表示する24時間形式ではなく、12時間形式(AM/PM)で入力してください。", 26 + "gridColor": "グリッドの色", 27 + "gridColor_description": "カレンダーのグリッド線に使用する色を選択します。", 28 + "colorOutline": "Outline", 29 + "colorOnSurfaceVariant": "OnSurfaceVariant", 30 + "hourLineOpacity": "時間線の不透明度", 31 + "hourLineOpacity_description": "時間線の不透明度を調整します。", 32 + "dayLineOpacity": "日線の不透明度", 33 + "dayLineOpacity_description": "日付境界線の不透明度を調整します。", 34 + "panelMode": "パネル表示モード", 35 + "panelMode_description": "画面の表示形式を選択します (再起動が必要な場合があります)。", 36 + "panelAttach": "バーに吸着", 37 + "panelCenter": "中央に配置" 38 + } 39 + }
+39
weekly-calendar/i18n/ku.json
··· 1 + { 2 + "bar_widget": { 3 + "settings": "Mîhengên widget", 4 + "now": "Niha", 5 + "next": "Piştî", 6 + "no_more_events_today": "Îro bêtir bûyer tune" 7 + }, 8 + "panel": { 9 + "header": "Salnameya hefteyê", 10 + "today": "Îro", 11 + "event": "bûyer", 12 + "events": "bûyerên", 13 + "allday": "tîrojê", 14 + "no_service": "Xizmeta salnameyê nîne", 15 + "loading": "Bar dike...", 16 + "no_events": "Bûyer tune" 17 + }, 18 + "settings": { 19 + "weekStart": "Roja yekem a hefteyê", 20 + "weekStart_description": "Roja kîjanê di salnameyê de dest bi hefteyê dike hilbijêre.", 21 + "monday": "Duşem", 22 + "sunday": "Yekşem", 23 + "saturday": "Şemî", 24 + "timeFormat": "Formata demê ya 12-demjimêr bi kar bîne", 25 + "timeFormat_description": "Demê di formata 12-demjimêr (BN/PN) de nîşan bide, ne di formata 24-demjimêr de.", 26 + "gridColor": "Rengê torê", 27 + "gridColor_description": "Rengê ku ji bo xêtên torê ya salnameyê tê bikar anîn hilbijêre.", 28 + "colorOutline": "Outline", 29 + "colorOnSurfaceVariant": "OnSurfaceVariant", 30 + "hourLineOpacity": "Şeffafiya xêta saetê", 31 + "hourLineOpacity_description": "Şeffafiya xêtên saetan saz bikin.", 32 + "dayLineOpacity": "Şeffafiya xêta rojê", 33 + "dayLineOpacity_description": "Şeffafiya xêtên jêderê rojê saz bikin.", 34 + "panelMode": "Rewşa panelê", 35 + "panelMode_description": "Hilbijêre şêweyê (dibe ku ji nû ve vekirinê hewce bike).", 36 + "panelAttach": "Panel bi bar ve girêdayî ye", 37 + "panelCenter": "Panela navendîkirî" 38 + } 39 + }
+39
weekly-calendar/i18n/nl.json
··· 1 + { 2 + "bar_widget": { 3 + "settings": "Widgetinstellingen", 4 + "now": "Nu", 5 + "next": "Volgende", 6 + "no_more_events_today": "Geen evenementen meer vandaag" 7 + }, 8 + "panel": { 9 + "header": "Wekelijkse kalender", 10 + "today": "Vandaag", 11 + "event": "evenement", 12 + "events": "evenementen", 13 + "allday": "de hele dag", 14 + "no_service": "Geen kalenderdienst", 15 + "loading": "Laden...", 16 + "no_events": "Geen evenementen" 17 + }, 18 + "settings": { 19 + "weekStart": "Eerste dag van de week", 20 + "weekStart_description": "Kies welke dag de week in de kalender begint.", 21 + "monday": "Maandag", 22 + "sunday": "Zondag", 23 + "saturday": "Zaterdag", 24 + "timeFormat": "12-uurs tijdnotatie gebruiken", 25 + "timeFormat_description": "Tijd weergeven in 12-uurs formaat (AM/PM) in plaats van 24-uurs formaat.", 26 + "gridColor": "Rasterkleur", 27 + "gridColor_description": "Kies de kleur die wordt gebruikt voor de rasterlijnen van de kalender.", 28 + "colorOutline": "Outline", 29 + "colorOnSurfaceVariant": "OnSurfaceVariant", 30 + "hourLineOpacity": "Ondoorzichtigheid van uurlijnen", 31 + "hourLineOpacity_description": "Pas de ondoorzichtigheid van uurlijnen aan.", 32 + "dayLineOpacity": "Ondoorzichtigheid van daglijnen", 33 + "dayLineOpacity_description": "Pas de ondoorzichtigheid van dag-scheidingslijnen aan.", 34 + "panelMode": "Paneelmodus", 35 + "panelMode_description": "Kies lay-out (mogelijk opnieuw openen vereist).", 36 + "panelAttach": "Paneel bevestigd aan de balk", 37 + "panelCenter": "Gecentreerd paneel" 38 + } 39 + }
+39
weekly-calendar/i18n/pl.json
··· 1 + { 2 + "bar_widget": { 3 + "settings": "Ustawienia widżetu", 4 + "now": "Teraz", 5 + "next": "Następne", 6 + "no_more_events_today": "Dziś brak dalszych wydarzeń" 7 + }, 8 + "panel": { 9 + "header": "Kalendarz tygodniowy", 10 + "today": "Dziś", 11 + "event": "wydarzenie", 12 + "events": "wydarzenia", 13 + "allday": "cały dzień", 14 + "no_service": "Brak usługi kalendarza", 15 + "loading": "Ładowanie...", 16 + "no_events": "Brak wydarzeń" 17 + }, 18 + "settings": { 19 + "weekStart": "Pierwszy dzień tygodnia", 20 + "weekStart_description": "Wybierz, który dzień zaczyna tydzień w kalendarzu.", 21 + "monday": "Poniedziałek", 22 + "sunday": "Niedziela", 23 + "saturday": "Sobota", 24 + "timeFormat": "Użyj formatu 12-godzinnego", 25 + "timeFormat_description": "Wyświetlaj czas w formacie 12-godzinnym (AM/PM) zamiast 24-godzinnego.", 26 + "gridColor": "Kolor siatki", 27 + "gridColor_description": "Wybierz kolor używany dla linii siatki w kalendarzu.", 28 + "colorOutline": "Outline", 29 + "colorOnSurfaceVariant": "OnSurfaceVariant", 30 + "hourLineOpacity": "Przezroczystość linii godzin", 31 + "hourLineOpacity_description": "Dopasuj przezroczystość linii godzin.", 32 + "dayLineOpacity": "Przezroczystość linii dni", 33 + "dayLineOpacity_description": "Dopasuj przezroczystość linii oddzielających dni.", 34 + "panelMode": "Tryb panelu", 35 + "panelMode_description": "Wybierz układ (może wymagać ponownego otwarcia).", 36 + "panelAttach": "Panel przypięty do paska", 37 + "panelCenter": "Panel wyśrodkowany" 38 + } 39 + }
+39
weekly-calendar/i18n/pt.json
··· 1 + { 2 + "bar_widget": { 3 + "settings": "Configurações do widget", 4 + "now": "Agora", 5 + "next": "Próximo", 6 + "no_more_events_today": "Sem mais eventos hoje" 7 + }, 8 + "panel": { 9 + "header": "Calendário semanal", 10 + "today": "Hoje", 11 + "event": "evento", 12 + "events": "eventos", 13 + "allday": "o dia todo", 14 + "no_service": "Sem serviço de calendário", 15 + "loading": "Carregando...", 16 + "no_events": "Sem eventos" 17 + }, 18 + "settings": { 19 + "weekStart": "Primeiro dia da semana", 20 + "weekStart_description": "Escolha qual dia comença a semana no calendário.", 21 + "monday": "Segunda-feira", 22 + "sunday": "Domingo", 23 + "saturday": "Sábado", 24 + "timeFormat": "Usar formato de 12 horas", 25 + "timeFormat_description": "Exibir a hora no formato de 12 horas (AM/PM) em vez do formato de 24 horas.", 26 + "gridColor": "Cor da grade", 27 + "gridColor_description": "Escolha a cor usada para as linhas da grade do calendário.", 28 + "colorOutline": "Outline", 29 + "colorOnSurfaceVariant": "OnSurfaceVariant", 30 + "hourLineOpacity": "Opacidade das linhas de hora", 31 + "hourLineOpacity_description": "Ajuste a opacidade das linhas de hora.", 32 + "dayLineOpacity": "Opacidade das linhas diárias", 33 + "dayLineOpacity_description": "Ajuste a opacidade das linhas separadoras de dia.", 34 + "panelMode": "Modo do painel", 35 + "panelMode_description": "Escolha o layout (pode ser necessário reabrir).", 36 + "panelAttach": "Painel anexado à barra", 37 + "panelCenter": "Painel centralizado" 38 + } 39 + }
+39
weekly-calendar/i18n/ru.json
··· 1 + { 2 + "bar_widget": { 3 + "settings": "Настройки виджета", 4 + "now": "Сейчас", 5 + "next": "Далее", 6 + "no_more_events_today": "Сегодня больше нет событий" 7 + }, 8 + "panel": { 9 + "header": "Еженедельный календарь", 10 + "today": "Сегодня", 11 + "event": "событие", 12 + "events": "события", 13 + "allday": "весь день", 14 + "no_service": "Сервис календаря недоступен", 15 + "loading": "Загрузка...", 16 + "no_events": "Нет событий" 17 + }, 18 + "settings": { 19 + "weekStart": "Первый день недели", 20 + "weekStart_description": "Выберите, с какого дня начинается неделя в календаре.", 21 + "monday": "Понедельник", 22 + "sunday": "Воскресенье", 23 + "saturday": "Суббота", 24 + "timeFormat": "Использовать 12-часовой формат времени", 25 + "timeFormat_description": "Отображать время в 12-часовом формате (AM/PM) вместо 24-часового.", 26 + "gridColor": "Цвет сетки", 27 + "gridColor_description": "Выберите цвет, используемый для линий сетки календаря.", 28 + "colorOutline": "Outline", 29 + "colorOnSurfaceVariant": "OnSurfaceVariant", 30 + "hourLineOpacity": "Непрозрачность часовых линий", 31 + "hourLineOpacity_description": "Отрегулируйте непрозрачность часовых линий.", 32 + "dayLineOpacity": "Непрозрачность дневных линий", 33 + "dayLineOpacity_description": "Отрегулируйте непрозрачность линий-разделителей дней.", 34 + "panelMode": "Режим панели", 35 + "panelMode_description": "Выберите раскладку (может потребоваться перезапуск).", 36 + "panelAttach": "Панель прикреплена к панели", 37 + "panelCenter": "Центрированная панель" 38 + } 39 + }
+39
weekly-calendar/i18n/tr.json
··· 1 + { 2 + "bar_widget": { 3 + "settings": "Araç takımı ayarları", 4 + "now": "Şimdi", 5 + "next": "Sonraki", 6 + "no_more_events_today": "Bugün başka etkinlik yok" 7 + }, 8 + "panel": { 9 + "header": "Haftalık Takvim", 10 + "today": "Bugün", 11 + "event": "etkinlik", 12 + "events": "etkinlikler", 13 + "allday": "tüm gün", 14 + "no_service": "Takvim servisi yok", 15 + "loading": "Yükleniyor...", 16 + "no_events": "Etkinlik yok" 17 + }, 18 + "settings": { 19 + "weekStart": "Haftanın ilk günü", 20 + "weekStart_description": "Takvimde haftanın hangi günü başladığını seçin.", 21 + "monday": "Pazartesi", 22 + "sunday": "Pazar", 23 + "saturday": "Cumartesi", 24 + "timeFormat": "12 saatlik zaman formatını kullan", 25 + "timeFormat_description": "Zamanı 24 saatlik yerine 12 saatlik formatta (ÖÖ/ÖS) göster.", 26 + "gridColor": "Izgara Rengi", 27 + "gridColor_description": "Takvim ızgara çizgileri için kullanılacak rengi seçin.", 28 + "colorOutline": "Outline", 29 + "colorOnSurfaceVariant": "OnSurfaceVariant", 30 + "hourLineOpacity": "Saat Çizgisi Opaklığı", 31 + "hourLineOpacity_description": "Saat çizgilerinin opaklığını ayarlayın.", 32 + "dayLineOpacity": "Gün Çizgisi Opaklığı", 33 + "dayLineOpacity_description": "Gün ayırıcı çizgilerin opaklığını ayarlayın.", 34 + "panelMode": "Panel modu", 35 + "panelMode_description": "Düzeni seçin (yeniden açılması gerekebilir).", 36 + "panelAttach": "Panel çubuğa takılı", 37 + "panelCenter": "Ortalanmış panel" 38 + } 39 + }
+39
weekly-calendar/i18n/uk-UA.json
··· 1 + { 2 + "bar_widget": { 3 + "settings": "Параметри віджета", 4 + "now": "Зараз", 5 + "next": "Далі", 6 + "no_more_events_today": "Сьогодні більше немає подій" 7 + }, 8 + "panel": { 9 + "header": "Щотижневий календар", 10 + "today": "Сьогодні", 11 + "event": "подія", 12 + "events": "події", 13 + "allday": "цілий день", 14 + "no_service": "Сервіс календаря недоступний", 15 + "loading": "Завантаження...", 16 + "no_events": "Немає подій" 17 + }, 18 + "settings": { 19 + "weekStart": "Перший день тижня", 20 + "weekStart_description": "Виберіть, який день починає тиждень у календарі.", 21 + "monday": "Понеділок", 22 + "sunday": "Неділя", 23 + "saturday": "Субота", 24 + "timeFormat": "Використовувати 12-годинний формат часу", 25 + "timeFormat_description": "Відображати час у 12-годинному форматі (ДП/ПП) замість 24-годинного.", 26 + "gridColor": "Колір сітки", 27 + "gridColor_description": "Виберіть колір, що використовується для ліній сітки календаря.", 28 + "colorOutline": "Outline", 29 + "colorOnSurfaceVariant": "OnSurfaceVariant", 30 + "hourLineOpacity": "Непрозорість годинних ліній", 31 + "hourLineOpacity_description": "Налаштуйте непрозорість годинних ліній.", 32 + "dayLineOpacity": "Непрозорість ліній дня", 33 + "dayLineOpacity_description": "Налаштуйте непрозорість ліній-роздільників днів.", 34 + "panelMode": "Режим панелі", 35 + "panelMode_description": "Виберіть макет (може знадобитися перезапуск).", 36 + "panelAttach": "Панель прикріплена до панелі", 37 + "panelCenter": "Центрована панель" 38 + } 39 + }
+39
weekly-calendar/i18n/zh-CN.json
··· 1 + { 2 + "bar_widget": { 3 + "settings": "小部件设置", 4 + "now": "现在", 5 + "next": "接下来", 6 + "no_more_events_today": "今天没有剩下其他活动" 7 + }, 8 + "panel": { 9 + "header": "周历", 10 + "today": "今天", 11 + "event": "场活动", 12 + "events": "场活动", 13 + "allday": "场全天", 14 + "no_service": "缺少日历服务", 15 + "loading": "加载中…", 16 + "no_events": "没有活动" 17 + }, 18 + "settings": { 19 + "weekStart": "一周起始日", 20 + "weekStart_description": "选择日历中一周的起始日。", 21 + "monday": "周一", 22 + "sunday": "周日", 23 + "saturday": "周六", 24 + "timeFormat": "使用12小时制时间格式", 25 + "timeFormat_description": "以12小时制(上午/下午)而非24小时制显示时间。", 26 + "gridColor": "网格颜色", 27 + "gridColor_description": "选择用于周历网格线的颜色。", 28 + "colorOutline": "Outline", 29 + "colorOnSurfaceVariant": "OnSurfaceVariant", 30 + "hourLineOpacity": "小时线不透明度", 31 + "hourLineOpacity_description": "调整小时线的不透明度。", 32 + "dayLineOpacity": "日期分隔线不透明度", 33 + "dayLineOpacity_description": "调整日期分隔线的不透明度。", 34 + "panelMode": "面板模式", 35 + "panelMode_description": "选择面板的布局(可能需要重新打开面板才会生效)。", 36 + "panelAttach": "面板依附在工具列", 37 + "panelCenter": "居中面板" 38 + } 39 + }
+39
weekly-calendar/i18n/zh-TW.json
··· 1 + { 2 + "bar_widget": { 3 + "settings": "小部件設置", 4 + "now": "現在", 5 + "next": "接下來", 6 + "no_more_events_today": "今天沒有剩下其他活動" 7 + }, 8 + "panel": { 9 + "header": "周曆", 10 + "today": "今天", 11 + "event": "場活動", 12 + "events": "場活動", 13 + "allday": "場全天", 14 + "no_service": "缺少日曆服務", 15 + "loading": "加載中…", 16 + "no_events": "缺少活動" 17 + }, 18 + "settings": { 19 + "weekStart": "一週起始日", 20 + "weekStart_description": "選擇日曆中一週的起始日。", 21 + "monday": "週一", 22 + "sunday": "週日", 23 + "saturday": "週六", 24 + "timeFormat": "使用12小時制時間格式", 25 + "timeFormat_description": "以12小時制(上午/下午)而非24小時制顯示時間。", 26 + "gridColor": "網格顏色", 27 + "gridColor_description": "選擇用於周曆 網格線的顏色。", 28 + "colorOutline": "Outline", 29 + "colorOnSurfaceVariant": "OnSurfaceVariant", 30 + "hourLineOpacity": "小時閒步透明度", 31 + "hourLineOpacity_description": "調整小時線的不透明度。", 32 + "dayLineOpacity": "日期分隔不透明度", 33 + "dayLineOpacity_description": "調整日期分隔的不透明度。", 34 + "panelMode": "面板模式", 35 + "panelMode_description": "選擇面板的佈局(可能需要重新打開面板才會生效)。", 36 + "panelAttach": "面板依附在工具列", 37 + "panelCenter": "居中面板" 38 + } 39 + }
+20
weekly-calendar/manifest.json
··· 1 + { 2 + "id": "weekly-calendar", 3 + "name": "Weekly Calendar", 4 + "version": "1.0.4", 5 + "minNoctaliaVersion": "4.2.3", 6 + "author": "dodaars", 7 + "license": "MIT", 8 + "repository": "https://github.com/noctalia-dev/noctalia-plugins", 9 + "description": "A weekly calendar plugin complementing Noctalia's inbuilt calendar service.", 10 + "tags": ["Bar", "Panel", "Productivity"], 11 + "entryPoints": { 12 + "main": "Main.qml", 13 + "barWidget": "BarWidget.qml", 14 + "panel": "Panel.qml", 15 + "settings": "Settings.qml" 16 + }, 17 + "dependencies": { 18 + "plugins": [] 19 + } 20 + }
weekly-calendar/preview.png

This is a binary file and will not be displayed.