codeapp-js 1.0.1 → 1.1.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.
- package/AI/skills/autoreview/SKILL.md +69 -0
- package/AI/skills/connections/SKILL.md +4 -4
- package/AI/skills/dataverse/SKILL.md +4 -2
- package/AI/skills/frontend-design/SKILL.md +32 -20
- package/AI/skills/keyvault/SKILL.md +139 -0
- package/AI/skills/office365-groups/SKILL.md +46 -25
- package/AI/skills/office365-outlook/SKILL.md +56 -25
- package/AI/skills/office365-users/SKILL.md +41 -36
- package/AI/skills/sharepoint/SKILL.md +174 -31
- package/AI/skills/start/SKILL.md +38 -30
- package/codeApp/dist/connectors/autoreview.js +1654 -0
- package/codeApp/dist/connectors/office365groups.js +2995 -432
- package/{examples/outlook Demo/.power/schemas/office365/office365.Schema.json → codeApp/dist/connectors/office365outlook.js} +7439 -16
- package/codeApp/dist/connectors/office365users.js +2990 -349
- package/codeApp/dist/connectors/sharepoint.js +529 -308
- package/examples/combined demo/dist/connectors/office365outlook.js +28521 -0
- package/examples/combined demo/dist/connectors/office365users.js +3154 -0
- package/examples/combined demo/dist/index.js +2 -6
- package/examples/combined demo/power.config.json +1 -1
- package/examples/groups Demo/{.power/schemas/office365groups/office365groups.Schema.json → dist/connectors/office365groups.js } +3205 -2204
- package/examples/groups Demo/dist/index.js +1 -5
- package/examples/groups Demo/power.config.json +1 -1
- package/examples/myProfile/dist/connectors/office365users.js +3154 -0
- package/examples/myProfile/dist/index.js +1 -5
- package/examples/myProfile/power.config.json +1 -1
- package/examples/outlook Demo/dist/connectors/office365outlook.js +28521 -0
- package/examples/outlook Demo/dist/index.js +2 -5
- package/examples/outlook Demo/power.config.json +1 -1
- package/examples/sharePoint Demo/dist/connectors/sharepoint.js +687 -0
- package/examples/sharePoint Demo/dist/index.js +86 -127
- package/examples/sharePoint Demo/power.config.json +1 -1
- package/package.json +1 -1
- package/codeApp/.power/schemas/appschemas/dataSourcesInfo.ts +0 -6275
- package/codeApp/.power/schemas/jira/jira.Schema.json +0 -6903
- package/codeApp/.power/schemas/keyvault/keyvault.Schema.json +0 -1600
- package/codeApp/.power/schemas/office365groups/office365groups.Schema.json +0 -2204
- package/codeApp/.power/schemas/teams/teams.Schema.json +0 -11112
- package/codeApp/dist/connectors/outlook.js +0 -1393
- package/codeApp/src/generated/index.ts +0 -12
- package/codeApp/src/generated/models/AzureKeyVaultModel.ts +0 -107
- package/codeApp/src/generated/models/JiraModel.ts +0 -501
- package/codeApp/src/generated/models/Office365GroupsModel.ts +0 -363
- package/codeApp/src/generated/models/Office365OutlookModel.ts +0 -2046
- package/codeApp/src/generated/models/Office365UsersModel.ts +0 -254
- package/codeApp/src/generated/services/AzureKeyVaultService.ts +0 -257
- package/codeApp/src/generated/services/JiraService.ts +0 -1124
- package/codeApp/src/generated/services/Office365GroupsService.ts +0 -326
- package/codeApp/src/generated/services/Office365OutlookService.ts +0 -2476
- package/codeApp/src/generated/services/Office365UsersService.ts +0 -358
- package/examples/apps/kanban/dist/dataverse.js +0 -94
- package/examples/apps/kanban/dist/environmentVar.js +0 -55
- package/examples/apps/kanban/dist/index.css +0 -605
- package/examples/apps/kanban/dist/index.html +0 -21
- package/examples/apps/kanban/dist/index.js +0 -860
- package/examples/apps/kanban/dist/office365groups.js +0 -97
- package/examples/apps/kanban/dist/office365users.js +0 -451
- package/examples/apps/kanban/dist/outlook.js +0 -162
- package/examples/apps/kanban/dist/power-apps-data.js +0 -2953
- package/examples/apps/kanban/dist/sharepoint.js +0 -435
- package/examples/apps/kanban/power.config.json +0 -35
- package/examples/apps/kanban/src/generated/index.ts +0 -14
- package/examples/apps/kanban/src/generated/models/Office365GroupsModel.ts +0 -363
- package/examples/apps/kanban/src/generated/models/Office365OutlookModel.ts +0 -2046
- package/examples/apps/kanban/src/generated/models/Office365UsersModel.ts +0 -254
- package/examples/apps/kanban/src/generated/services/Office365GroupsService.ts +0 -326
- package/examples/apps/kanban/src/generated/services/Office365OutlookService.ts +0 -2476
- package/examples/apps/kanban/src/generated/services/Office365UsersService.ts +0 -358
- package/examples/apps/planning Poker/additional files/AgilePoker_1_0_0_1.zip +0 -0
- package/examples/apps/planning Poker/additional files/PokerTables_1_0_0_1.zip +0 -0
- package/examples/apps/planning Poker/additional files/customizations (tables).xml +0 -6429
- package/examples/apps/planning Poker/additional files/dataverse-tables.json +0 -165
- package/examples/apps/planning Poker/additional files/readme.md +0 -122
- package/examples/apps/planning Poker/dist/dataverse.js +0 -78
- package/examples/apps/planning Poker/dist/index.html +0 -198
- package/examples/apps/planning Poker/dist/index.js +0 -955
- package/examples/apps/planning Poker/dist/power-apps-data.js +0 -2953
- package/examples/apps/planning Poker/dist/styles.css +0 -815
- package/examples/apps/planning Poker/power.config.json +0 -50
- package/examples/apps/solution explorer/dist/codeapp.js +0 -1098
- package/examples/apps/solution explorer/dist/icon-512.png +0 -0
- package/examples/apps/solution explorer/dist/index.html +0 -80
- package/examples/apps/solution explorer/dist/index.js +0 -735
- package/examples/apps/solution explorer/dist/power-apps-data.js +0 -3007
- package/examples/apps/solution explorer/dist/styles.css +0 -571
- package/examples/apps/solution explorer/power.config.json +0 -151
- package/examples/apps/todo/dist/dataverse.js +0 -64
- package/examples/apps/todo/dist/icon192.png +0 -0
- package/examples/apps/todo/dist/index.html +0 -75
- package/examples/apps/todo/dist/index.js +0 -9
- package/examples/apps/todo/dist/power-apps-data.js +0 -2953
- package/examples/apps/todo/dist/renderer.js +0 -375
- package/examples/apps/todo/dist/styles.css +0 -691
- package/examples/apps/todo/power.config.json +0 -35
- package/examples/combined demo/.power/schemas/appschemas/dataSourcesInfo.ts +0 -6275
- package/examples/combined demo/.power/schemas/jira/jira.Schema.json +0 -6903
- package/examples/combined demo/.power/schemas/keyvault/keyvault.Schema.json +0 -1600
- package/examples/combined demo/.power/schemas/teams/teams.Schema.json +0 -11112
- package/examples/combined demo/dist/office365users.js +0 -513
- package/examples/combined demo/dist/outlook.js +0 -1393
- package/examples/combined demo/src/generated/index.ts +0 -12
- package/examples/combined demo/src/generated/models/AzureKeyVaultModel.ts +0 -107
- package/examples/combined demo/src/generated/models/JiraModel.ts +0 -501
- package/examples/combined demo/src/generated/models/Office365GroupsModel.ts +0 -363
- package/examples/combined demo/src/generated/models/Office365OutlookModel.ts +0 -2046
- package/examples/combined demo/src/generated/models/Office365UsersModel.ts +0 -254
- package/examples/combined demo/src/generated/services/AzureKeyVaultService.ts +0 -257
- package/examples/combined demo/src/generated/services/JiraService.ts +0 -1124
- package/examples/combined demo/src/generated/services/Office365GroupsService.ts +0 -326
- package/examples/combined demo/src/generated/services/Office365OutlookService.ts +0 -2476
- package/examples/combined demo/src/generated/services/Office365UsersService.ts +0 -358
- package/examples/groups Demo/.power/schemas/appschemas/dataSourcesInfo.ts +0 -613
- package/examples/groups Demo/dist/office365groups.js +0 -642
- package/examples/groups Demo/src/generated/index.ts +0 -10
- package/examples/groups Demo/src/generated/models/Office365GroupsModel.ts +0 -363
- package/examples/groups Demo/src/generated/services/Office365GroupsService.ts +0 -326
- package/examples/myProfile/dist/office365users.js +0 -517
- package/examples/outlook Demo/.power/schemas/appschemas/dataSourcesInfo.ts +0 -6512
- package/examples/outlook Demo/dist/outlook.js +0 -1393
- package/examples/outlook Demo/src/generated/index.ts +0 -10
- package/examples/outlook Demo/src/generated/models/Office365OutlookModel.ts +0 -2046
- package/examples/outlook Demo/src/generated/services/Office365OutlookService.ts +0 -2476
- package/examples/sharePoint Demo/dist/sharepoint.js +0 -466
- package/examples/sharePoint Demo/src/generated/index.ts +0 -14
- package/examples/sharePoint Demo/src/generated/models/Office365GroupsModel.ts +0 -363
- package/examples/sharePoint Demo/src/generated/models/Office365OutlookModel.ts +0 -2046
- package/examples/sharePoint Demo/src/generated/models/Office365UsersModel.ts +0 -254
- package/examples/sharePoint Demo/src/generated/services/Office365GroupsService.ts +0 -326
- package/examples/sharePoint Demo/src/generated/services/Office365OutlookService.ts +0 -2476
- package/examples/sharePoint Demo/src/generated/services/Office365UsersService.ts +0 -358
- package/readme.md +0 -590
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
import { createItem, listItems, updateItem, deleteItem } from "./dataverse.js";
|
|
2
|
-
|
|
3
|
-
const TABLE = "tasks";
|
|
4
|
-
const KEY = "activityid";
|
|
5
|
-
|
|
6
|
-
// Track IDs that exist in Dataverse (to distinguish create vs update)
|
|
7
|
-
const oExistingIds = new Set();
|
|
8
|
-
|
|
9
|
-
// Local order persistence (drag-to-reorder stored client-side)
|
|
10
|
-
function getLocalOrder() {
|
|
11
|
-
try { return JSON.parse(localStorage.getItem('noteOrder') || '{}'); } catch { return {}; }
|
|
12
|
-
}
|
|
13
|
-
function saveLocalOrder() {
|
|
14
|
-
const oOrder = {};
|
|
15
|
-
oState.notes.forEach((oN) => { oOrder[oN.id] = oN.order; });
|
|
16
|
-
localStorage.setItem('noteOrder', JSON.stringify(oOrder));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Simple markdown helpers (minimal)
|
|
20
|
-
function mdToHtml(sMd) {
|
|
21
|
-
// escape HTML
|
|
22
|
-
const escapeHtml = (sText) => sText.replace(new RegExp('[&<>]', 'g'), (sChar) => ({ '&': '&', '<': '<', '>': '>' }[sChar]));
|
|
23
|
-
let sOut = escapeHtml(sMd);
|
|
24
|
-
// headings
|
|
25
|
-
sOut = sOut.replace(new RegExp('^###### (.*)$', 'gm'), '<h6>$1</h6>')
|
|
26
|
-
.replace(new RegExp('^##### (.*)$', 'gm'), '<h5>$1</h5>')
|
|
27
|
-
.replace(new RegExp('^#### (.*)$', 'gm'), '<h4>$1</h4>')
|
|
28
|
-
.replace(new RegExp('^### (.*)$', 'gm'), '<h3>$1</h3>')
|
|
29
|
-
.replace(new RegExp('^## (.*)$', 'gm'), '<h2>$1</h2>')
|
|
30
|
-
.replace(new RegExp('^# (.*)$', 'gm'), '<h1>$1</h1>');
|
|
31
|
-
// blockquote
|
|
32
|
-
sOut = sOut.replace(new RegExp('^> (.*)$', 'gm'), '<blockquote>$1</blockquote>');
|
|
33
|
-
// task list items (keep before unordered list so they are not double processed)
|
|
34
|
-
sOut = sOut.replace(new RegExp('^[-*] \\[( |x|X)?\\] (.*)$', 'gm'), (sMatch, sCheck, sText) => {
|
|
35
|
-
const sChecked = sCheck && new RegExp('x|X').test(sCheck) ? ' checked' : '';
|
|
36
|
-
return '<div class="task-item"><label><input type="checkbox" class="task-box"' + sChecked + '><span class="checkbox-ui"></span><span class="task-text">' + sText + '</span></label></div>';
|
|
37
|
-
});
|
|
38
|
-
// ordered list
|
|
39
|
-
sOut = sOut.replace(new RegExp('^(\\d+)\\. (.*)(?:\\n(?!.|\\d+\\.).)*', 'gms'), (sMatch) => {
|
|
40
|
-
const aItems = sMatch.split('\n').map((sLine) => sLine.match(new RegExp('^\\d+\\. (.*)'))?.[1]).filter(Boolean);
|
|
41
|
-
return '<ol>' + aItems.map((sItem) => '<li>' + sItem + '</li>').join('') + '</ol>';
|
|
42
|
-
});
|
|
43
|
-
// unordered list
|
|
44
|
-
sOut = sOut.replace(new RegExp('^(?:- |\\* )(.*)(?:\\n(?!\\n|[-*] ).)*', 'gms'), (sMatch) => {
|
|
45
|
-
const aItems = sMatch.split('\n').map((sLine) => sLine.replace(new RegExp('^(?:- |\\* )'), ''));
|
|
46
|
-
return '<ul>' + aItems.map((sItem) => '<li>' + sItem + '</li>').join('') + '</ul>';
|
|
47
|
-
});
|
|
48
|
-
// bold / italic / code / link
|
|
49
|
-
sOut = sOut.replace(new RegExp('\\*\\*(.+?)\\*\\*', 'g'), '<strong>$1</strong>')
|
|
50
|
-
.replace(new RegExp('\\*(.+?)\\*', 'g'), '<em>$1</em>')
|
|
51
|
-
.replace(new RegExp('`([^`]+?)`', 'g'), '<code>$1</code>')
|
|
52
|
-
.replace(new RegExp('\\[(.+?)\\]\\((.+?)\\)', 'g'), '<a href="$2" target="_blank" rel="noopener">$1</a>');
|
|
53
|
-
// paragraphs
|
|
54
|
-
sOut = sOut.replace(new RegExp('^(?!<h\\d|<blockquote|<ul|<ol|<pre|<code|<div class="task-item")(.*)$', 'gm'), '<p>$1</p>');
|
|
55
|
-
return sOut;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const $ = (sSelector) => document.querySelector(sSelector);
|
|
59
|
-
const $$ = (sSelector) => Array.from(document.querySelectorAll(sSelector));
|
|
60
|
-
|
|
61
|
-
const oState = {
|
|
62
|
-
notes: [], // {id, title, content, tags:[], order}
|
|
63
|
-
selectedId: null,
|
|
64
|
-
tags: new Set(),
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
function setStatus(sMessage) { $('#sync-status').textContent = sMessage; }
|
|
68
|
-
|
|
69
|
-
function isHexColor(sValue) {
|
|
70
|
-
return new RegExp('^([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$').test(sValue);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function renderTags() {
|
|
74
|
-
const eWrap = $('#tag-list');
|
|
75
|
-
eWrap.innerHTML = '';
|
|
76
|
-
const aTags = Array.from(oState.tags).sort();
|
|
77
|
-
aTags.forEach((sTag) => {
|
|
78
|
-
const eButton = document.createElement('button');
|
|
79
|
-
eButton.className = 'tag';
|
|
80
|
-
eButton.textContent = '#' + sTag;
|
|
81
|
-
eButton.onclick = () => filterByTag(sTag);
|
|
82
|
-
eWrap.appendChild(eButton);
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function filterByTag(sTag) {
|
|
87
|
-
const eList = $('#note-list');
|
|
88
|
-
eList.querySelectorAll('li').forEach((eLi) => {
|
|
89
|
-
const bHas = eLi.dataset.tags?.split(',').includes(sTag);
|
|
90
|
-
eLi.style.display = bHas ? '' : 'none';
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function renderList() {
|
|
95
|
-
const eList = $('#note-list');
|
|
96
|
-
eList.innerHTML = '';
|
|
97
|
-
const aSorted = [...oState.notes].sort((oA, oB) => oA.order - oB.order);
|
|
98
|
-
aSorted.forEach((oNote) => {
|
|
99
|
-
const eLi = document.createElement('li');
|
|
100
|
-
eLi.className = 'note-item';
|
|
101
|
-
eLi.draggable = true;
|
|
102
|
-
eLi.dataset.id = oNote.id;
|
|
103
|
-
eLi.dataset.tags = (oNote.tags || []).join(',');
|
|
104
|
-
eLi.innerHTML = '<span class="title">' + (oNote.title || '(Untitled)') + ' </span>' +
|
|
105
|
-
'<span class="muted">' + (oNote.tags || []).map((sTag) => '#' + sTag).join(' ') + '</span>' +
|
|
106
|
-
'<button class="note-del" title="Delete" aria-label="Delete note" data-del>×</button>';
|
|
107
|
-
eLi.addEventListener('click', (oEvent) => { if (oEvent.target.closest('[data-del]')) return; selectNote(oNote.id); });
|
|
108
|
-
// delete button
|
|
109
|
-
eLi.querySelector('[data-del]').addEventListener('click', async (oEvent) => { oEvent.stopPropagation(); await deleteNote(oNote.id); });
|
|
110
|
-
// drag handlers
|
|
111
|
-
eLi.addEventListener('dragstart', (oEvent) => {
|
|
112
|
-
eLi.classList.add('dragging');
|
|
113
|
-
oEvent.dataTransfer.setData('text/plain', oNote.id);
|
|
114
|
-
});
|
|
115
|
-
eLi.addEventListener('dragend', () => eLi.classList.remove('dragging'));
|
|
116
|
-
eLi.addEventListener('dragover', (oEvent) => {
|
|
117
|
-
oEvent.preventDefault();
|
|
118
|
-
const eDragging = document.querySelector('.dragging');
|
|
119
|
-
if (!eDragging || eDragging === eLi) return;
|
|
120
|
-
const oRect = eLi.getBoundingClientRect();
|
|
121
|
-
const bAfter = (oEvent.clientY - oRect.top) > oRect.height / 2;
|
|
122
|
-
if (bAfter) eLi.after(eDragging); else eLi.before(eDragging);
|
|
123
|
-
});
|
|
124
|
-
eLi.addEventListener('drop', () => {
|
|
125
|
-
// update orders by DOM position
|
|
126
|
-
$$('#note-list .note-item').forEach((eItem, iIdx) => {
|
|
127
|
-
const sId = eItem.dataset.id;
|
|
128
|
-
const oFound = oState.notes.find((oN) => oN.id === sId);
|
|
129
|
-
if (oFound) { oFound.order = iIdx; }
|
|
130
|
-
});
|
|
131
|
-
saveLocalOrder();
|
|
132
|
-
});
|
|
133
|
-
eList.appendChild(eLi);
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function selectNote(sId) {
|
|
138
|
-
oState.selectedId = sId;
|
|
139
|
-
const oNote = oState.notes.find((oN) => oN.id === sId);
|
|
140
|
-
$('#title').value = oNote?.title || '';
|
|
141
|
-
$('#content').value = oNote?.content || '';
|
|
142
|
-
updatePreview();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function generateUUID() {
|
|
146
|
-
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
147
|
-
return crypto.randomUUID();
|
|
148
|
-
}
|
|
149
|
-
// Fallback: build a v4 UUID from crypto.getRandomValues
|
|
150
|
-
const aBytes = new Uint8Array(16);
|
|
151
|
-
crypto.getRandomValues(aBytes);
|
|
152
|
-
aBytes[6] = (aBytes[6] & 0x0f) | 0x40; // version 4
|
|
153
|
-
aBytes[8] = (aBytes[8] & 0x3f) | 0x80; // variant 1
|
|
154
|
-
const sHex = Array.from(aBytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
155
|
-
return sHex.slice(0, 8) + '-' + sHex.slice(8, 12) + '-' + sHex.slice(12, 16) + '-' + sHex.slice(16, 20) + '-' + sHex.slice(20);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function newNote() {
|
|
159
|
-
const sId = generateUUID();
|
|
160
|
-
const iMaxOrder = oState.notes.reduce((iMax, oN) => Math.max(iMax, oN.order || 0), -1) + 1;
|
|
161
|
-
const oNote = { id: sId, title: '', content: '', tags: [], order: iMaxOrder };
|
|
162
|
-
oState.notes.push(oNote);
|
|
163
|
-
selectNote(sId);
|
|
164
|
-
renderList();
|
|
165
|
-
debounceSave();
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function applyMd(sAction) {
|
|
169
|
-
const eTextarea = $('#content');
|
|
170
|
-
const iStart = eTextarea.selectionStart;
|
|
171
|
-
const iEnd = eTextarea.selectionEnd;
|
|
172
|
-
const sSelected = eTextarea.value.slice(iStart, iEnd);
|
|
173
|
-
const iLineStart = eTextarea.value.lastIndexOf('\n', iStart - 1) + 1;
|
|
174
|
-
const iLineEnd = eTextarea.value.indexOf('\n', iEnd); // -1 ok
|
|
175
|
-
const aSelLines = eTextarea.value.slice(iLineStart, iLineEnd === -1 ? undefined : iLineEnd).split('\n');
|
|
176
|
-
|
|
177
|
-
let sReplacement = sSelected;
|
|
178
|
-
switch (sAction) {
|
|
179
|
-
case 'bold': sReplacement = '**' + (sSelected || 'bold') + '**'; break;
|
|
180
|
-
case 'italic': sReplacement = '*' + (sSelected || 'italic') + '*'; break;
|
|
181
|
-
case 'h1': sReplacement = '# ' + (sSelected || 'Heading 1'); break;
|
|
182
|
-
case 'h2': sReplacement = '## ' + (sSelected || 'Heading 2'); break;
|
|
183
|
-
case 'ul': sReplacement = aSelLines.map((sLine) => sLine ? '- ' + sLine : '- ').join('\n'); break;
|
|
184
|
-
case 'ol': sReplacement = aSelLines.map((sLine, iIdx) => (iIdx + 1) + '. ' + (sLine || '')).join('\n'); break;
|
|
185
|
-
case 'task': sReplacement = aSelLines.map((sLine) => sLine ? '- [ ] ' + sLine : '- [ ] ').join('\n'); break;
|
|
186
|
-
case 'code': sReplacement = '`' + (sSelected || 'code') + '`'; break;
|
|
187
|
-
case 'quote': sReplacement = sSelected.split('\n').map((sLine) => '> ' + sLine).join('\n'); break;
|
|
188
|
-
case 'link': sReplacement = '[' + (sSelected || 'label') + '](https://)'; break;
|
|
189
|
-
case 'tag': {
|
|
190
|
-
const sTag = prompt('Enter tag (no #):');
|
|
191
|
-
if (!sTag) return; addTagToCurrent(sTag); return;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
eTextarea.setRangeText(sReplacement, iStart, iEnd, 'end');
|
|
195
|
-
onEdit();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function addTagToCurrent(sTag) {
|
|
199
|
-
const oNote = oState.notes.find((oN) => oN.id === oState.selectedId);
|
|
200
|
-
if (!oNote) return;
|
|
201
|
-
// Insert #tag into content so tags stay in sync with text
|
|
202
|
-
const sHashtag = '#' + sTag;
|
|
203
|
-
const eContent = $('#content');
|
|
204
|
-
if (!oNote.content.match(new RegExp('(^|\\s)' + sHashtag + '(\\s|$)', 'm'))) {
|
|
205
|
-
eContent.value = eContent.value + '\n' + sHashtag;
|
|
206
|
-
}
|
|
207
|
-
onEdit();
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function onEdit() {
|
|
211
|
-
const sId = oState.selectedId; if (!sId) return;
|
|
212
|
-
const oNote = oState.notes.find((oN) => oN.id === sId); if (!oNote) return;
|
|
213
|
-
oNote.title = $('#title').value.trim();
|
|
214
|
-
oNote.content = $('#content').value;
|
|
215
|
-
// extract #tags from content - replace entirely so partial keystrokes don't accumulate
|
|
216
|
-
const aTagMatches = [...oNote.content.matchAll(new RegExp('(^|\\s)#([\\w-]+)', 'g'))].map((aMatch) => aMatch[2]).filter((sTag) => !isHexColor(sTag));
|
|
217
|
-
oNote.tags = [...new Set(aTagMatches)];
|
|
218
|
-
// rebuild global tag set from all notes
|
|
219
|
-
oState.tags = new Set(oState.notes.flatMap((oN) => oN.tags || []).filter((sTag) => !isHexColor(sTag)));
|
|
220
|
-
renderTags();
|
|
221
|
-
renderList();
|
|
222
|
-
updatePreview();
|
|
223
|
-
debounceSave();
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function updatePreview() {
|
|
227
|
-
const bHidden = $('#preview').hasAttribute('hidden');
|
|
228
|
-
if (bHidden) return;
|
|
229
|
-
const sMd = $('#content').value;
|
|
230
|
-
const aLines = sMd.split('\n');
|
|
231
|
-
// Build HTML
|
|
232
|
-
const sHtml = mdToHtml(sMd);
|
|
233
|
-
const ePreview = $('#preview');
|
|
234
|
-
ePreview.innerHTML = sHtml;
|
|
235
|
-
// Map task list items to original line numbers
|
|
236
|
-
const aTaskLineIndices = [];
|
|
237
|
-
aLines.forEach((sLine, iIdx) => { if (new RegExp('^[-*] \\[( |x|X)?\\] ').test(sLine)) aTaskLineIndices.push(iIdx); });
|
|
238
|
-
ePreview.querySelectorAll('.task-item input.task-box').forEach((eInput, iIdx) => {
|
|
239
|
-
eInput.dataset.line = aTaskLineIndices[iIdx];
|
|
240
|
-
eInput.addEventListener('change', () => {
|
|
241
|
-
const iLineNum = parseInt(eInput.dataset.line, 10);
|
|
242
|
-
if (Number.isNaN(iLineNum)) return;
|
|
243
|
-
// Toggle the checkbox marker in the markdown line
|
|
244
|
-
aLines[iLineNum] = aLines[iLineNum].replace(new RegExp('^([-*] )\\[( |x|X)?\\] '), (sMatch, sPrefix) => sPrefix + '[' + (eInput.checked ? 'x' : ' ') + '] ');
|
|
245
|
-
// Update textarea (preserve scroll)
|
|
246
|
-
const eTextarea = $('#content');
|
|
247
|
-
const iScrollPos = eTextarea.scrollTop;
|
|
248
|
-
eTextarea.value = aLines.join('\n');
|
|
249
|
-
eTextarea.scrollTop = iScrollPos;
|
|
250
|
-
// Persist change
|
|
251
|
-
onEdit();
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
let iSaveTimer = null;
|
|
257
|
-
function debounceSave() {
|
|
258
|
-
clearTimeout(iSaveTimer);
|
|
259
|
-
iSaveTimer = setTimeout(saveCurrentNote, 500);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
async function saveCurrentNote() {
|
|
263
|
-
const oNote = oState.notes.find((oN) => oN.id === oState.selectedId);
|
|
264
|
-
if (!oNote) return;
|
|
265
|
-
try {
|
|
266
|
-
setStatus('Saving...');
|
|
267
|
-
const oRecord = { subject: oNote.title, description: oNote.content };
|
|
268
|
-
if (oExistingIds.has(oNote.id)) {
|
|
269
|
-
await updateItem(TABLE, KEY, oNote.id, oRecord);
|
|
270
|
-
} else {
|
|
271
|
-
oRecord[KEY] = oNote.id;
|
|
272
|
-
await createItem(TABLE, KEY, oRecord);
|
|
273
|
-
oExistingIds.add(oNote.id);
|
|
274
|
-
}
|
|
275
|
-
saveLocalOrder();
|
|
276
|
-
setStatus('Saved');
|
|
277
|
-
} catch (oError) {
|
|
278
|
-
setStatus('Save failed: ' + oError.message);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
async function sync(bLoad = false) {
|
|
283
|
-
try {
|
|
284
|
-
setStatus('Syncing...');
|
|
285
|
-
const oResult = await listItems(TABLE, KEY, {
|
|
286
|
-
select: ["activityid", "subject", "description"],
|
|
287
|
-
});
|
|
288
|
-
if (!oResult.success) {
|
|
289
|
-
setStatus('Sync failed: ' + (oResult.error?.message || 'Unknown error'));
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
const aRecords = Array.isArray(oResult.data) ? oResult.data : [];
|
|
293
|
-
const oOrderMap = getLocalOrder();
|
|
294
|
-
oState.notes = aRecords.map((oRec, iIdx) => {
|
|
295
|
-
const sId = oRec[KEY] || oRec.activityid;
|
|
296
|
-
oExistingIds.add(sId);
|
|
297
|
-
const sContent = oRec.description || '';
|
|
298
|
-
const aTags = [...sContent.matchAll(new RegExp('(^|\\s)#([\\w-]+)', 'g'))].map((aMatch) => aMatch[2]).filter((sTag) => !isHexColor(sTag));
|
|
299
|
-
return {
|
|
300
|
-
id: sId,
|
|
301
|
-
title: oRec.subject || '',
|
|
302
|
-
content: sContent,
|
|
303
|
-
tags: [...new Set(aTags)],
|
|
304
|
-
order: oOrderMap[sId] ?? iIdx,
|
|
305
|
-
};
|
|
306
|
-
});
|
|
307
|
-
oState.tags = new Set(oState.notes.flatMap((oN) => oN.tags || []).filter((sTag) => !isHexColor(sTag)));
|
|
308
|
-
renderTags();
|
|
309
|
-
renderList();
|
|
310
|
-
if (bLoad && oState.notes[0]) selectNote(oState.notes[0].id);
|
|
311
|
-
setStatus('Synced at ' + new Date().toLocaleTimeString());
|
|
312
|
-
} catch (oError) {
|
|
313
|
-
setStatus('Sync failed: ' + oError.message);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function wire() {
|
|
318
|
-
$('#btn-new').onclick = newNote;
|
|
319
|
-
$('#btn-preview').onclick = () => {
|
|
320
|
-
const ePrev = $('#preview');
|
|
321
|
-
if (ePrev.hasAttribute('hidden')) { ePrev.removeAttribute('hidden'); updatePreview(); }
|
|
322
|
-
else ePrev.setAttribute('hidden', '');
|
|
323
|
-
};
|
|
324
|
-
$('#btn-sync').onclick = () => sync(false);
|
|
325
|
-
$('#title').addEventListener('input', onEdit);
|
|
326
|
-
$('#content').addEventListener('input', onEdit);
|
|
327
|
-
$('.toolbar').addEventListener('click', (oEvent) => {
|
|
328
|
-
const eBtn = oEvent.target.closest('button[data-md]'); if (!eBtn || eBtn.disabled) return;
|
|
329
|
-
applyMd(eBtn.dataset.md);
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
// Network status monitoring
|
|
333
|
-
if ('onLine' in navigator) {
|
|
334
|
-
const updateNetworkStatus = () => {
|
|
335
|
-
if (!navigator.onLine) { setStatus('No internet connection'); }
|
|
336
|
-
};
|
|
337
|
-
window.addEventListener('online', updateNetworkStatus);
|
|
338
|
-
window.addEventListener('offline', updateNetworkStatus);
|
|
339
|
-
updateNetworkStatus();
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Load tasks from Dataverse on startup
|
|
343
|
-
sync(true);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
window.addEventListener('DOMContentLoaded', wire);
|
|
347
|
-
|
|
348
|
-
async function deleteNote(sId) {
|
|
349
|
-
const iIdx = oState.notes.findIndex((oN) => oN.id === sId);
|
|
350
|
-
if (iIdx === -1) return;
|
|
351
|
-
oState.notes.splice(iIdx, 1);
|
|
352
|
-
// normalize order
|
|
353
|
-
oState.notes.sort((oA, oB) => oA.order - oB.order).forEach((oN, iIndex) => oN.order = iIndex);
|
|
354
|
-
if (oState.selectedId === sId) {
|
|
355
|
-
oState.selectedId = oState.notes[0]?.id || null;
|
|
356
|
-
$('#title').value = oState.notes[0]?.title || '';
|
|
357
|
-
$('#content').value = oState.notes[0]?.content || '';
|
|
358
|
-
}
|
|
359
|
-
// rebuild global tags from remaining notes
|
|
360
|
-
oState.tags = new Set(oState.notes.flatMap((oN) => oN.tags || []).filter((sTag) => !isHexColor(sTag)));
|
|
361
|
-
renderTags();
|
|
362
|
-
renderList();
|
|
363
|
-
updatePreview();
|
|
364
|
-
if (oExistingIds.has(sId)) {
|
|
365
|
-
try {
|
|
366
|
-
setStatus('Deleting...');
|
|
367
|
-
await deleteItem(TABLE, KEY, sId);
|
|
368
|
-
oExistingIds.delete(sId);
|
|
369
|
-
setStatus('Deleted');
|
|
370
|
-
} catch (oError) {
|
|
371
|
-
setStatus('Delete failed: ' + oError.message);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
saveLocalOrder();
|
|
375
|
-
}
|