calendar-notes-plugin 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.vscode/extensions.json +4 -0
  2. package/.vscode/launch.json +20 -0
  3. package/.vscode/mcp.json +9 -0
  4. package/.vscode/tasks.json +42 -0
  5. package/backend/policies/RLS.sql +1 -0
  6. package/backend/policies/delete.sql +6 -0
  7. package/backend/policies/insert.sql +6 -0
  8. package/backend/policies/select.sql +4 -0
  9. package/backend/policies/update.sql +8 -0
  10. package/backend/table.sql +9 -0
  11. package/calendar-notes-plugin/.editorconfig +17 -0
  12. package/calendar-notes-plugin/.hintrc +5 -0
  13. package/calendar-notes-plugin/.vscode/extensions.json +4 -0
  14. package/calendar-notes-plugin/.vscode/launch.json +20 -0
  15. package/calendar-notes-plugin/.vscode/mcp.json +9 -0
  16. package/calendar-notes-plugin/.vscode/tasks.json +42 -0
  17. package/calendar-notes-plugin/README.md +226 -0
  18. package/calendar-notes-plugin/angular.json +74 -0
  19. package/calendar-notes-plugin/package-lock.json +8917 -0
  20. package/calendar-notes-plugin/package.json +52 -0
  21. package/calendar-notes-plugin/public/favicon.ico +0 -0
  22. package/calendar-notes-plugin/public/widget.js +75 -0
  23. package/calendar-notes-plugin/src/app/app.config.ts +12 -0
  24. package/calendar-notes-plugin/src/app/app.css +0 -0
  25. package/calendar-notes-plugin/src/app/app.html +1 -0
  26. package/calendar-notes-plugin/src/app/app.routes.server.ts +8 -0
  27. package/calendar-notes-plugin/src/app/app.routes.ts +3 -0
  28. package/calendar-notes-plugin/src/app/app.spec.ts +23 -0
  29. package/calendar-notes-plugin/src/app/app.ts +17 -0
  30. package/calendar-notes-plugin/src/app/components/calender/calendar.css +73 -0
  31. package/calendar-notes-plugin/src/app/components/calender/calendar.html +26 -0
  32. package/calendar-notes-plugin/src/app/components/calender/calendar.spec.ts +23 -0
  33. package/calendar-notes-plugin/src/app/components/calender/calendar.ts +155 -0
  34. package/calendar-notes-plugin/src/app/components/floating-button/floating-button.css +17 -0
  35. package/calendar-notes-plugin/src/app/components/floating-button/floating-button.html +3 -0
  36. package/calendar-notes-plugin/src/app/components/floating-button/floating-button.spec.ts +23 -0
  37. package/calendar-notes-plugin/src/app/components/floating-button/floating-button.ts +25 -0
  38. package/calendar-notes-plugin/src/app/components/notes/notes.css +251 -0
  39. package/calendar-notes-plugin/src/app/components/notes/notes.html +107 -0
  40. package/calendar-notes-plugin/src/app/components/notes/notes.spec.ts +23 -0
  41. package/calendar-notes-plugin/src/app/components/notes/notes.ts +328 -0
  42. package/calendar-notes-plugin/src/app/components/panel/panel.css +28 -0
  43. package/calendar-notes-plugin/src/app/components/panel/panel.html +16 -0
  44. package/calendar-notes-plugin/src/app/components/panel/panel.spec.ts +23 -0
  45. package/calendar-notes-plugin/src/app/components/panel/panel.ts +46 -0
  46. package/calendar-notes-plugin/src/app/components/widget/widget.css +18 -0
  47. package/calendar-notes-plugin/src/app/components/widget/widget.html +17 -0
  48. package/calendar-notes-plugin/src/app/components/widget/widget.spec.ts +23 -0
  49. package/calendar-notes-plugin/src/app/components/widget/widget.ts +23 -0
  50. package/calendar-notes-plugin/src/app/services/api.spec.ts +16 -0
  51. package/calendar-notes-plugin/src/app/services/api.ts +39 -0
  52. package/calendar-notes-plugin/src/app/services/notification.spec.ts +16 -0
  53. package/calendar-notes-plugin/src/app/services/notification.ts +116 -0
  54. package/calendar-notes-plugin/src/backend/database/policies/delete.sql +6 -0
  55. package/calendar-notes-plugin/src/backend/database/policies/insert.sql +6 -0
  56. package/calendar-notes-plugin/src/backend/database/policies/select.sql +6 -0
  57. package/calendar-notes-plugin/src/backend/database/policies/update.sql +6 -0
  58. package/calendar-notes-plugin/src/backend/database/table.sql +13 -0
  59. package/calendar-notes-plugin/src/index.html +13 -0
  60. package/calendar-notes-plugin/src/main.ts +34 -0
  61. package/calendar-notes-plugin/src/styles.css +1 -0
  62. package/calendar-notes-plugin/tsconfig.app.json +17 -0
  63. package/calendar-notes-plugin/tsconfig.json +33 -0
  64. package/calendar-notes-plugin/tsconfig.spec.json +15 -0
  65. package/calendar-notes-plugin-1.0.0.tgz +0 -0
  66. package/package.json +29 -0
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "notes-plugin",
3
+ "version": "0.0.0",
4
+ "scripts": {
5
+ "ng": "ng",
6
+ "start": "ng serve",
7
+ "build": "ng build",
8
+ "watch": "ng build --watch --configuration development",
9
+ "test": "ng test",
10
+ "serve:ssr:notes_plugin": "node dist/notes_plugin/server/server.mjs"
11
+ },
12
+ "prettier": {
13
+ "printWidth": 100,
14
+ "singleQuote": true,
15
+ "overrides": [
16
+ {
17
+ "files": "*.html",
18
+ "options": {
19
+ "parser": "angular"
20
+ }
21
+ }
22
+ ]
23
+ },
24
+ "private": true,
25
+ "packageManager": "npm@11.6.2",
26
+ "dependencies": {
27
+ "@angular/common": "^21.1.0",
28
+ "@angular/compiler": "^21.1.0",
29
+ "@angular/core": "^21.1.0",
30
+ "@angular/forms": "^21.1.0",
31
+ "@angular/platform-browser": "^21.1.0",
32
+ "@angular/platform-server": "^21.1.0",
33
+ "@angular/router": "^21.1.0",
34
+ "@angular/ssr": "^21.1.0",
35
+ "@supabase/supabase-js": "^2.95.3",
36
+ "express": "^5.1.0",
37
+ "rxjs": "~7.8.0",
38
+ "tslib": "^2.3.0",
39
+ "uuid": "^13.0.0"
40
+ },
41
+ "devDependencies": {
42
+ "@angular/build": "^21.1.0",
43
+ "@angular/cli": "^21.1.0",
44
+ "@angular/compiler-cli": "^21.1.0",
45
+ "@angular/elements": "^21.1.4",
46
+ "@types/express": "^5.0.1",
47
+ "@types/node": "^20.17.19",
48
+ "jsdom": "^27.1.0",
49
+ "typescript": "~5.9.2",
50
+ "vitest": "^4.0.8"
51
+ }
52
+ }
@@ -0,0 +1,75 @@
1
+ (function () {
2
+
3
+ if (document.getElementById("calendar-notes-widget")) return;
4
+
5
+ const config = window.CalendarNotesConfig || {};
6
+
7
+ const iframe = document.createElement("iframe");
8
+
9
+ iframe.id = "calendar-notes-widget";
10
+ iframe.src = "http://localhost:4200/";
11
+
12
+ iframe.style.position = "fixed";
13
+ iframe.style.bottom = "20px";
14
+ iframe.style.right = "20px";
15
+ iframe.style.width = "450px";
16
+ iframe.style.height = "650px";
17
+ iframe.style.border = "none";
18
+ iframe.style.zIndex = "999999";
19
+ iframe.style.background = "transparent";
20
+
21
+ document.body.appendChild(iframe);
22
+
23
+ window.addEventListener("message", async function (event) {
24
+
25
+ const data = event.data;
26
+
27
+ if (!data) return;
28
+
29
+ // LOAD NOTES
30
+ if (data.type === "LOAD_NOTES") {
31
+
32
+ const notes = await config.loadNotes?.();
33
+
34
+ iframe.contentWindow.postMessage({
35
+ type: "NOTES_LOADED",
36
+ notes
37
+ }, "*");
38
+ }
39
+
40
+ // SAVE NOTE
41
+ if (data.type === "SAVE_NOTE") {
42
+
43
+ const saved = await config.saveNote?.(data.payload);
44
+
45
+ iframe.contentWindow.postMessage({
46
+ type: "NOTE_SAVED",
47
+ note: saved
48
+ }, "*");
49
+ }
50
+
51
+ // UPDATE NOTE
52
+ if (data.type === "UPDATE_NOTE") {
53
+
54
+ const updated = await config.updateNote?.(data.payload);
55
+
56
+ iframe.contentWindow.postMessage({
57
+ type: "NOTE_UPDATED",
58
+ note: updated
59
+ }, "*");
60
+ }
61
+
62
+ // DELETE NOTE
63
+ if (data.type === "DELETE_NOTE") {
64
+
65
+ await config.deleteNote?.(data.id);
66
+
67
+ iframe.contentWindow.postMessage({
68
+ type: "NOTE_DELETED",
69
+ id: data.id
70
+ }, "*");
71
+ }
72
+
73
+ });
74
+
75
+ })();
@@ -0,0 +1,12 @@
1
+ import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
2
+ import { provideRouter } from '@angular/router';
3
+
4
+ import { routes } from './app.routes';
5
+ import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
6
+
7
+ export const appConfig: ApplicationConfig = {
8
+ providers: [
9
+ provideBrowserGlobalErrorListeners(),
10
+ provideRouter(routes), provideClientHydration(withEventReplay())
11
+ ]
12
+ };
File without changes
@@ -0,0 +1 @@
1
+ <app-floating-button></app-floating-button>
@@ -0,0 +1,8 @@
1
+ import { RenderMode, ServerRoute } from '@angular/ssr';
2
+
3
+ export const serverRoutes: ServerRoute[] = [
4
+ {
5
+ path: '**',
6
+ renderMode: RenderMode.Prerender
7
+ }
8
+ ];
@@ -0,0 +1,3 @@
1
+ import { Routes } from '@angular/router';
2
+
3
+ export const routes: Routes = [];
@@ -0,0 +1,23 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { App } from './app';
3
+
4
+ describe('App', () => {
5
+ beforeEach(async () => {
6
+ await TestBed.configureTestingModule({
7
+ imports: [App],
8
+ }).compileComponents();
9
+ });
10
+
11
+ it('should create the app', () => {
12
+ const fixture = TestBed.createComponent(App);
13
+ const app = fixture.componentInstance;
14
+ expect(app).toBeTruthy();
15
+ });
16
+
17
+ it('should render title', async () => {
18
+ const fixture = TestBed.createComponent(App);
19
+ await fixture.whenStable();
20
+ const compiled = fixture.nativeElement as HTMLElement;
21
+ expect(compiled.querySelector('h1')?.textContent).toContain('Hello, notes_plugin');
22
+ });
23
+ });
@@ -0,0 +1,17 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import { Component, signal, ViewEncapsulation } from '@angular/core';
3
+ import { FloatingButton } from './components/floating-button/floating-button';
4
+
5
+ @Component({
6
+ selector: 'app-root',
7
+ standalone:true,
8
+ imports: [CommonModule,FloatingButton],
9
+ encapsulation: ViewEncapsulation.ShadowDom,
10
+ templateUrl: './app.html',
11
+ styleUrl: './app.css',
12
+
13
+ })
14
+ export class App {
15
+ protected readonly title = signal('notes_plugin');
16
+
17
+ }
@@ -0,0 +1,73 @@
1
+ .header {
2
+ display: flex;
3
+ justify-content: space-between;
4
+ align-items: center;
5
+ font-weight: 600;
6
+ font-size: 16px;
7
+ margin-bottom: 5px;
8
+ }
9
+
10
+ .header button {
11
+ background: #f1f3f6;
12
+ border: none;
13
+ padding: 6px 10px;
14
+ border-radius: 8px;
15
+ cursor: pointer;
16
+ transition: 0.2s ease;
17
+ }
18
+
19
+ .header button:hover {
20
+ background: #dfe6f3;
21
+ }
22
+
23
+ .calendar {
24
+ display: grid;
25
+ grid-template-columns: repeat(7, 1fr);
26
+ gap: 4px;
27
+ margin-bottom: 0;
28
+ }
29
+
30
+ .day-name {
31
+ text-align: center;
32
+ font-size: 12px;
33
+ font-weight: 600;
34
+ color: #888;
35
+ }
36
+
37
+ .day {
38
+ text-align: center;
39
+ padding: 10px 0;
40
+ border-radius: 10px;
41
+ cursor: pointer;
42
+ transition: 0.2s ease;
43
+ font-size: 14px;
44
+ padding: 6px 0;
45
+ position: relative;
46
+ }
47
+
48
+ .day:hover {
49
+ background: #f0f4ff;
50
+ }
51
+
52
+ .day.selected {
53
+ background: #4f46e5;
54
+ color: white;
55
+ font-weight: bold;
56
+ }
57
+
58
+ .day.today {
59
+ border: 2px solid #4f46e5;
60
+ }
61
+
62
+ .day.empty {
63
+ cursor: default;
64
+ background: transparent;
65
+ }
66
+ .note-dot {
67
+ position: absolute;
68
+ bottom: 4px;
69
+ width: 6px;
70
+ height: 6px;
71
+ background-color: #ff5722;
72
+ border-radius: 50%;
73
+ }
@@ -0,0 +1,26 @@
1
+ <div class="header">
2
+ <button (click)="prevMonth()">â—€</button>
3
+ <span>{{ currentDate | date: 'MMMM yyyy' }}</span>
4
+ <button (click)="nextMonth()">â–¶</button>
5
+ </div>
6
+
7
+ <div class="calendar">
8
+
9
+ <div class="day-name" *ngFor="let d of ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']">
10
+ {{ d }}
11
+ </div>
12
+
13
+ <div
14
+ class="day"
15
+ *ngFor="let day of calendarDays"
16
+ [class.empty]="!day"
17
+ [class.selected]="isSelected(day)"
18
+ [class.today]="isToday(day)"
19
+ (click)="day && selectDate(day)"
20
+ >
21
+ {{ day || '' }}
22
+ <span *ngIf="hasNotes(day)" class="note-dot"></span>
23
+ </div>
24
+
25
+ </div>
26
+
@@ -0,0 +1,23 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { Calendar } from './calendar';
4
+
5
+ describe('Calendar', () => {
6
+ let component: Calendar;
7
+ let fixture: ComponentFixture<Calendar>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [Calendar]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(Calendar);
16
+ component = fixture.componentInstance;
17
+ await fixture.whenStable();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
@@ -0,0 +1,155 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
3
+
4
+ @Component({
5
+ selector: 'app-calendar',
6
+ standalone: true,
7
+ imports: [CommonModule],
8
+ templateUrl: './calendar.html',
9
+ styleUrl: './calendar.css',
10
+ })
11
+ export class Calendar implements OnInit, OnChanges {
12
+
13
+ @Output() dateSelected = new EventEmitter<string>();
14
+ @Output() monthChanged = new EventEmitter<void>();
15
+
16
+ @Input() notes: { [key: string]: any[] } = {};
17
+
18
+ currentDate: Date = new Date();
19
+ calendarDays: (number | null)[] = [];
20
+
21
+ selectedDay: number | null = null;
22
+
23
+ today = new Date();
24
+
25
+ // INIT
26
+
27
+ ngOnInit() {
28
+ this.generateCalendar();
29
+ }
30
+
31
+ // INPUT CHANGES
32
+
33
+ ngOnChanges(changes: SimpleChanges) {
34
+
35
+ if (changes['notes']) {
36
+ // notes updated → refresh markers
37
+ this.generateCalendar();
38
+ }
39
+
40
+ }
41
+ // CALENDAR GENERATION
42
+
43
+
44
+ generateCalendar() {
45
+
46
+ const year = this.currentDate.getFullYear();
47
+ const month = this.currentDate.getMonth();
48
+
49
+ const firstDay = new Date(year, month, 1).getDay();
50
+ const totalDays = new Date(year, month + 1, 0).getDate();
51
+
52
+ this.calendarDays = [];
53
+
54
+ // empty cells before first day
55
+ for (let i = 0; i < firstDay; i++) {
56
+ this.calendarDays.push(null);
57
+ }
58
+
59
+ // actual days
60
+ for (let day = 1; day <= totalDays; day++) {
61
+ this.calendarDays.push(day);
62
+ }
63
+
64
+ }
65
+
66
+ // ----------------SELECT DATE----------------------
67
+
68
+ selectDate(day: number | null) {
69
+
70
+ if (!day) return;
71
+
72
+ this.selectedDay = day;
73
+
74
+ const dateString = this.formatDate(day);
75
+
76
+ this.dateSelected.emit(dateString);
77
+
78
+ }
79
+
80
+ // ---------------DATE FORMAT------------------
81
+
82
+
83
+ formatDate(day: number): string {
84
+
85
+ const year = this.currentDate.getFullYear();
86
+ const month = String(this.currentDate.getMonth() + 1).padStart(2, '0');
87
+ const dayStr = String(day).padStart(2, '0');
88
+
89
+ return `${year}-${month}-${dayStr}`;
90
+
91
+ }
92
+
93
+ // ------------------MONTH NAVIGATION-------------------
94
+
95
+ nextMonth() {
96
+
97
+ this.currentDate = new Date(
98
+ this.currentDate.getFullYear(),
99
+ this.currentDate.getMonth() + 1,
100
+ 1
101
+ );
102
+
103
+ this.selectedDay = null;
104
+
105
+ this.generateCalendar();
106
+
107
+ this.monthChanged.emit();
108
+
109
+ }
110
+
111
+ prevMonth() {
112
+
113
+ this.currentDate = new Date(
114
+ this.currentDate.getFullYear(),
115
+ this.currentDate.getMonth() - 1,
116
+ 1
117
+ );
118
+
119
+ this.selectedDay = null;
120
+
121
+ this.generateCalendar();
122
+
123
+ this.monthChanged.emit();
124
+
125
+ }
126
+
127
+ // -----------------UI HELPERS-------------------------
128
+
129
+ isSelected(day: number | null): boolean {
130
+ return day === this.selectedDay;
131
+ }
132
+
133
+ isToday(day: number | null): boolean {
134
+
135
+ if (!day) return false;
136
+
137
+ return (
138
+ day === this.today.getDate() &&
139
+ this.currentDate.getMonth() === this.today.getMonth() &&
140
+ this.currentDate.getFullYear() === this.today.getFullYear()
141
+ );
142
+
143
+ }
144
+
145
+ //------------------------ NOTES INDICATOR--------------------------
146
+
147
+ hasNotes(day: number | null): boolean {
148
+
149
+ if (!day) return false;
150
+ const date = this.formatDate(day);
151
+ return !!this.notes?.[date]?.length;
152
+
153
+ }
154
+
155
+ }
@@ -0,0 +1,17 @@
1
+ .floating-btn {
2
+ position: fixed;
3
+ bottom: 20px;
4
+ right: 20px;
5
+ width: 60px;
6
+ height: 60px;
7
+ background:transparent;
8
+ border-radius: 10%;
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ cursor: pointer;
13
+ box-shadow: 0 4px 10px rgba(0,0,0,0.3);
14
+ z-index: 999999;
15
+ font-size: 40px;
16
+ color: white;
17
+ }
@@ -0,0 +1,3 @@
1
+ <div class="floating-btn" (click)="toggle()">📅</div>
2
+ <app-panel *ngIf="isOpen"></app-panel>
3
+
@@ -0,0 +1,23 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { FloatingButton } from './floating-button';
4
+
5
+ describe('FloatingButton', () => {
6
+ let component: FloatingButton;
7
+ let fixture: ComponentFixture<FloatingButton>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [FloatingButton]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(FloatingButton);
16
+ component = fixture.componentInstance;
17
+ await fixture.whenStable();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
@@ -0,0 +1,25 @@
1
+ import { Component } from '@angular/core';
2
+ import { Panel } from '../panel/panel';
3
+ import { CommonModule } from '@angular/common';
4
+ import { FormsModule } from '@angular/forms';
5
+ import { Notes } from '../notes/notes';
6
+
7
+ @Component({
8
+ selector: 'app-floating-button',
9
+ imports: [CommonModule,FormsModule,Panel],
10
+ templateUrl: './floating-button.html',
11
+ styleUrl: './floating-button.css',
12
+ })
13
+ export class FloatingButton {
14
+
15
+ selectedDate: string = '';
16
+ isOpen = false;
17
+
18
+ toggle() {
19
+ this.isOpen = !this.isOpen;
20
+ }
21
+
22
+ onDateSelected(date: string) {
23
+ this.selectedDate = date;
24
+ }
25
+ }