Added task and note crteation SDKs
This commit is contained in:
@@ -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
@@ -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: "<p>Initial content here...</p>",
|
<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: "<p>HTML content here...</p>",
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user