Added task and note crteation SDKs

This commit is contained in:
2026-03-05 16:19:27 -06:00
parent 6a963a32fd
commit 75dde1b0d5
3 changed files with 256 additions and 65 deletions
+114
View File
@@ -0,0 +1,114 @@
/**
* TasGrid SDK
*
* A headless utility for creating tasks in the TasGrid PocketBase database
* from sister applications, ensuring all TasGrid defaults and date calculations
* are applied consistently.
*/
const URGENCY_HOURS = {
10: 12,
9: 24,
8: 36,
7: 72,
6: 120,
5: 240,
4: 504,
3: 1080,
2: 2160,
1: 4320
};
/**
* Calculates a future ISO date string based on the TasGrid urgency decay algorithm.
* @param {number} level Urgency level (1-10)
* @returns {string} ISO Date string
*/
export const calculateDateFromUrgency = (level) => {
const roundedLevel = Math.max(1, Math.min(10, Math.round(level)));
const hours = URGENCY_HOURS[roundedLevel] || 24;
return new Date(Date.now() + hours * 60 * 60 * 1000).toISOString();
};
/**
* Creates a new task in the TasGrid database using the provided PocketBase instance.
*
* @param {object} pb - An authenticated PocketBase JS SDK instance.
* @param {object} payload - The task payload.
* @param {string} [payload.title="New Task"] - The task title.
* @param {string} [payload.owner] - PocketBase User ID. Defaults to pb.authStore.model.id.
* @param {number} [payload.priority=5] - Priority (1-10).
* @param {number} [payload.urgency=5] - Urgency (1-10). Used to calculate dueDate if omitted.
* @param {number} [payload.size=3] - Size (0-10).
* @param {number} [payload.status=0] - Status (0-10).
* @param {boolean} [payload.completed=false] - Completion status.
* @param {string[]} [payload.tags=[]] - Array of tag strings.
* @param {string} [payload.content=""] - HTML string for task description/notes.
* @param {string} [payload.dueDate] - Strict ISO Date string. Overrides urgency calculation.
* @param {string} [payload.startDate=""] - ISO Date string.
* @returns {Promise<object>} The created PocketBase task record.
*/
export const createTasgridTask = async (pb, payload = {}) => {
if (!pb || !pb.authStore || !pb.authStore.isValid || !pb.authStore.model) {
throw new Error("Invalid or unauthenticated PocketBase instance provided.");
}
const urgencyVal = payload.urgency ?? 5;
const taskData = {
title: payload.title || "New Task",
priority: payload.priority ?? 5,
size: payload.size ?? 3,
status: payload.status ?? 0,
completed: payload.completed ?? false,
tags: payload.tags || [],
content: payload.content || "",
user: payload.owner || pb.authStore.model.id,
dueDate: payload.dueDate || calculateDateFromUrgency(urgencyVal),
startDate: payload.startDate || "",
};
try {
const record = await pb.collection('TasGrid').create(taskData);
return record;
} catch (err) {
console.error("TasGrid SDK: Failed to create task", err);
throw err;
}
};
/**
* Creates a new note in the TasGrid database using the provided PocketBase instance.
*
* @param {object} pb - An authenticated PocketBase JS SDK instance.
* @param {object} payload - The note payload.
* @param {string} [payload.title="New Note"] - The note title.
* @param {string} [payload.content=""] - HTML string for note content.
* @param {string[]} [payload.tags=[]] - Array of tag strings.
* @param {boolean} [payload.isPrivate=false] - Whether the note is private.
* @param {string[]} [payload.tasks=[]] - Array of task IDs to link.
* @param {string} [payload.owner] - PocketBase User ID. Defaults to pb.authStore.model.id.
* @returns {Promise<object>} The created PocketBase note record.
*/
export const createTasgridNote = async (pb, payload = {}) => {
if (!pb || !pb.authStore || !pb.authStore.isValid || !pb.authStore.model) {
throw new Error("Invalid or unauthenticated PocketBase instance provided.");
}
const noteData = {
title: payload.title || "New Note",
content: payload.content || "",
tags: payload.tags || [],
isPrivate: payload.isPrivate || false,
tasks: payload.tasks || [],
user: payload.owner || pb.authStore.model.id,
};
try {
const record = await pb.collection('TasGrid_Notes').create(noteData);
return record;
} catch (err) {
console.error("TasGrid SDK: Failed to create note", err);
throw err;
}
};
+138 -65
View File
@@ -113,6 +113,22 @@
<h1>TasGrid Parent App Simulator</h1> <h1>TasGrid Parent App Simulator</h1>
<div class="controls"> <div class="controls">
<div id="env-settings" style="margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid #eee;">
<p style="font-size: 13px; color: #666; margin-top: 0;"><strong>Target Environment:</strong></p>
<label style="margin-right: 15px; cursor: pointer;">
<input type="radio" name="envToggle" value="https://tasgrid.ccllc.pro" checked onchange="updateEnv()">
Production (tasgrid.ccllc.pro)
</label>
<label style="margin-right: 15px; cursor: pointer;">
<input type="radio" name="envToggle" value="http://localhost:4000" onchange="updateEnv()">
Localhost:4000
</label>
<label style="cursor: pointer;">
<input type="radio" name="envToggle" value="http://localhost:4001" onchange="updateEnv()">
Localhost:4001
</label>
</div>
<div id="login-section"> <div id="login-section">
<h3>Login to Parent Application</h3> <h3>Login to Parent Application</h3>
<p>This simulates the parent application authenticating with the shared backend.</p> <p>This simulates the parent application authenticating with the shared backend.</p>
@@ -132,12 +148,23 @@
<button onclick="loadSpecificNote()" style="padding: 8px 16px; cursor: pointer;">Load Note</button> <button onclick="loadSpecificNote()" style="padding: 8px 16px; cursor: pointer;">Load Note</button>
</div> </div>
<p style="font-size: 13px; color: #666; margin-top: 20px;"><strong>Test API Creation:</strong></p> <p style="font-size: 13px; color: #666; margin-top: 20px;"><strong>Test Headless SDK (Centralized
<div style="display: flex; gap: 10px; margin-bottom: 10px;"> Logic):</strong></p>
<input type="text" id="new-note-title" style="flex: 1; padding: 8px;" placeholder="New Note Title..."> <div style="display: flex; flex-direction: column; gap: 10px; margin-bottom: 10px;">
<button onclick="createNoteViaAPI()" <div style="display: flex; gap: 10px;">
style="padding: 8px 16px; cursor: pointer; background: #28a745; color: white; border: none; border-radius: 4px; font-weight: bold;">Create <input type="text" id="sdk-task-title" style="flex: 1; padding: 8px;"
& Open Note</button> placeholder="New Task Title...">
<button onclick="createTaskViaSDK()"
style="padding: 8px 16px; cursor: pointer; background: #6f42c1; color: white; border: none; border-radius: 4px; font-weight: bold;">Create
Task via SDK</button>
</div>
<div style="display: flex; gap: 10px;">
<input type="text" id="sdk-note-title" style="flex: 1; padding: 8px;"
placeholder="New Note Title...">
<button onclick="createNoteViaSDK()"
style="padding: 8px 16px; cursor: pointer; background: #fd7e14; color: white; border: none; border-radius: 4px; font-weight: bold;">Create
Note via SDK</button>
</div>
</div> </div>
<p style="font-size: 13px; color: #666; margin-top: 20px;"><strong>Manual Auth:</strong></p> <p style="font-size: 13px; color: #666; margin-top: 20px;"><strong>Manual Auth:</strong></p>
@@ -157,44 +184,46 @@
<li><code>/embed/quick-add</code>: Renders a standalone task creation form.</li> <li><code>/embed/quick-add</code>: Renders a standalone task creation form.</li>
</ul> </ul>
<p><strong>2. Creating Notes Programmatically (API):</strong></p> <p><strong>2. Programmatic Creation (TasGrid SDK):</strong></p>
<p>Before embedding, a sister app can create a Note via PocketBase API to retrieve a new <p>The recommended way to create entries is using the <code>tasgrid-sdk.js</code> module. This
<code>noteId</code>. By default, notes should have an array of <code>tags</code> and a ensures all TasGrid-specific defaults and date logic (like urgency decay) are handled correctly.
<code>title</code>.
</p> </p>
<p><em>Using the PocketBase JS SDK:</em></p>
<p><em>Setup:</em></p>
<pre <pre
style="background: #f8f9fa; padding: 15px; border-radius: 4px; border: 1px solid #e9ecef; overflow-x: auto; font-size: 12px;"> style="background: #f8f9fa; padding: 15px; border-radius: 4px; border: 1px solid #e9ecef; overflow-x: auto; font-size: 12px;">
const record = await pb.collection('TasGrid_Notes').create({ import { createTasgridTask, createTasgridNote } from 'https://tasgrid.ccllc.pro/tasgrid-sdk.js';</pre>
title: "Sister App Payload",
content: "&lt;p&gt;Initial content here...&lt;/p&gt;", <div style="border-left: 4px solid #6f42c1; padding-left: 15px; margin: 20px 0;">
tags: ["sister_app_export"], <p><strong>A. Creating a TASK:</strong></p>
<p style="font-size: 12px; color: #666; margin-top: -10px;">Use this for items that need status,
priority, and automated due date calculation.</p>
<pre
style="background: #f8f9fa; padding: 15px; border-radius: 4px; border: 1px solid #e9ecef; overflow-x: auto; font-size: 12px;">
const task = await createTasgridTask(pb, {
title: "Urgent Task",
urgency: 10, // Automated: 10=Today, 5=Next Week, 1=Six Months
priority: 8, // 1-10
size: 3, // Default: 3 (1 hour). Range 0-10.
tags: ["external"],
owner: "USER_ID" // Optional
});</pre>
</div>
<div style="border-left: 4px solid #fd7e14; padding-left: 15px; margin: 20px 0;">
<p><strong>B. Creating a NOTE:</strong></p>
<p style="font-size: 12px; color: #666; margin-top: -10px;">Use this for long-form content or
documentation without a deadline.</p>
<pre
style="background: #f8f9fa; padding: 15px; border-radius: 4px; border: 1px solid #e9ecef; overflow-x: auto; font-size: 12px;">
const note = await createTasgridNote(pb, {
title: "Project Brief",
content: "&lt;p&gt;HTML content here...&lt;/p&gt;",
tags: ["documentation"],
isPrivate: false, isPrivate: false,
user: pb.authStore.model.id, // Must be authenticated user ID tasks: [] // Optional: Array of Task IDs to link
tasks: [] // Optional array of linked task IDs });</pre>
}); </div>
const newNoteId = record.id;
// Then iframe to /embed/notes?noteId=${newNoteId}
</pre>
<p><em>Using standard REST API (fetch):</em></p>
<pre
style="background: #f8f9fa; padding: 15px; border-radius: 4px; border: 1px solid #e9ecef; overflow-x: auto; font-size: 12px;">
const res = await fetch('https://pocketbase.ccllc.pro/api/collections/TasGrid_Notes/records', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'YOUR_PB_AUTH_TOKEN'
},
body: JSON.stringify({
title: "REST Custom Note",
user: "YOUR_USER_ID",
tags: [],
tasks: [],
isPrivate: false
})
});
const data = await res.json();
const newNoteId = data.id;</pre>
<p><strong>3. Authentication Protocol:</strong></p> <p><strong>3. Authentication Protocol:</strong></p>
<p>The parent application must send a <code>postMessage</code> to the iframe immediately after the <p>The parent application must send a <code>postMessage</code> to the iframe immediately after the
@@ -232,7 +261,7 @@ function syncAuth(iframe) {
<span>Notes Embed</span> <span>Notes Embed</span>
<code style="font-size: 10px;">/embed/notes</code> <code style="font-size: 10px;">/embed/notes</code>
</div> </div>
<iframe id="notes-iframe" src="http://localhost:4000/embed/notes" onload="syncAuthOnLoad(this)"></iframe> <iframe id="notes-iframe" onload="syncAuthOnLoad(this)"></iframe>
</div> </div>
<div class="iframe-container"> <div class="iframe-container">
@@ -241,14 +270,36 @@ function syncAuth(iframe) {
<span>Quick Add Embed</span> <span>Quick Add Embed</span>
<code style="font-size: 10px;">/embed/quick-add</code> <code style="font-size: 10px;">/embed/quick-add</code>
</div> </div>
<iframe id="quick-add-iframe" src="http://localhost:4000/embed/quick-add" <iframe id="quick-add-iframe" onload="syncAuthOnLoad(this)"></iframe>
onload="syncAuthOnLoad(this)"></iframe>
</div> </div>
</div> </div>
<script> <script>
const pb = new PocketBase('https://pocketbase.ccllc.pro'); const pb = new PocketBase('https://pocketbase.ccllc.pro');
const statusEl = document.getElementById('status'); const statusEl = document.getElementById('status');
let ENV_URL = 'https://tasgrid.ccllc.pro';
function updateEnv() {
const selected = document.querySelector('input[name="envToggle"]:checked').value;
ENV_URL = selected;
// Reload iframes with new base URL
const notesIframe = document.getElementById('notes-iframe');
const noteId = document.getElementById('note-id-input')?.value.trim();
if (noteId) {
notesIframe.src = `${ENV_URL}/embed/notes?noteId=${noteId}`;
} else {
notesIframe.src = `${ENV_URL}/embed/notes`;
}
document.getElementById('quick-add-iframe').src = `${ENV_URL}/embed/quick-add`;
console.log(`Switched target environment to: ${ENV_URL}`);
}
// Initialize iframes on load
window.addEventListener('DOMContentLoaded', () => {
updateEnv();
});
function syncAuthOnLoad(iframe) { function syncAuthOnLoad(iframe) {
console.log(`Iframe ${iframe.id} loaded, checking auth sync...`); console.log(`Iframe ${iframe.id} loaded, checking auth sync...`);
@@ -262,46 +313,68 @@ function syncAuth(iframe) {
const id = document.getElementById('note-id-input').value.trim(); const id = document.getElementById('note-id-input').value.trim();
const iframe = document.getElementById('notes-iframe'); const iframe = document.getElementById('notes-iframe');
if (id) { if (id) {
iframe.src = `http://localhost:4000/embed/notes?noteId=${id}`; iframe.src = `${ENV_URL}/embed/notes?noteId=${id}`;
} else { } else {
iframe.src = `http://localhost:4000/embed/notes`; iframe.src = `${ENV_URL}/embed/notes`;
} }
} }
async function createNoteViaAPI() { async function createTaskViaSDK() {
const title = document.getElementById('new-note-title').value.trim() || 'API Generated Note'; const title = document.getElementById('sdk-task-title').value.trim() || 'SDK Generated Task';
if (!pb.authStore.isValid) { if (!pb.authStore.isValid) {
alert("Please log in first before creating a note via API."); alert("Please log in first before creating a task via SDK.");
return; return;
} }
statusEl.className = 'status'; statusEl.className = 'status';
statusEl.textContent = 'Creating note via API...'; statusEl.textContent = 'Importing SDK and creating task...';
statusEl.style.display = 'block'; statusEl.style.display = 'block';
try { try {
// Using PocketBase SDK to mimic a REST API call from sister app const { createTasgridTask } = await import(`${ENV_URL}/tasgrid-sdk.js`);
const record = await pb.collection('TasGrid_Notes').create({
const record = await createTasgridTask(pb, {
title: title, title: title,
content: "<p>This note was created automatically via the API script!</p>", priority: 8,
tags: ["api_generated"], urgency: 4,
isPrivate: false, tags: ["sdk_test"],
user: pb.authStore.model.id, content: "This task was created by a sister app using the imported Headless SDK!"
tasks: []
}); });
statusEl.className = 'status success'; statusEl.className = 'status success';
statusEl.textContent = `Note created successfully (ID: ${record.id})! Loading iframe...`; statusEl.textContent = `Task created successfully via SDK (ID: ${record.id})!`;
// Automatically set the new ID in the deep link input
document.getElementById('note-id-input').value = record.id;
// Automatically load it
loadSpecificNote();
} catch (err) { } catch (err) {
statusEl.className = 'status error'; statusEl.className = 'status error';
statusEl.textContent = 'Failed to create note: ' + err.message; statusEl.textContent = 'Failed to create task via SDK: ' + err.message;
}
}
async function createNoteViaSDK() {
const title = document.getElementById('sdk-note-title').value.trim() || 'SDK Generated Note';
if (!pb.authStore.isValid) {
alert("Please log in first before creating a note via SDK.");
return;
}
statusEl.className = 'status';
statusEl.textContent = 'Importing SDK and creating note...';
statusEl.style.display = 'block';
try {
const { createTasgridNote } = await import(`${ENV_URL}/tasgrid-sdk.js`);
const record = await createTasgridNote(pb, {
title: title,
content: "<p>This note was created headlessly via the SDK!</p>",
tags: ["sdk_test"],
isPrivate: false
});
statusEl.className = 'status success';
statusEl.textContent = `Note created successfully via SDK (ID: ${record.id})!`;
} catch (err) {
statusEl.className = 'status error';
statusEl.textContent = 'Failed to create note via SDK: ' + err.message;
} }
} }
+4
View File
@@ -63,6 +63,10 @@ export default defineConfig({
server: { server: {
host: '0.0.0.0', host: '0.0.0.0',
port: 4000, port: 4000,
cors: { origin: '*' },
headers: {
'Access-Control-Allow-Origin': '*'
},
allowedHosts: [ allowedHosts: [
'tasgrid.ccllc.pro' 'tasgrid.ccllc.pro'
] ]