Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
aa7926d
Upd. Added synchronization of Spotfix and WebSocket updates
veronika-tseleva-cleantalk Jan 22, 2026
b2be869
Upd. Added cleaning for indexedDB
veronika-tseleva-cleantalk Jan 22, 2026
9fba7e7
Merge branch 'indexeddb-websocket' into indexeddb-websocket-upd
veronika-tseleva-cleantalk Jan 22, 2026
35ec816
Merge branch 'dev' into indexeddb-websocket-upd
veronika-tseleva-cleantalk Jan 22, 2026
3c939c3
Merge branch 'dev' into indexeddb-websocket-upd
veronika-tseleva-cleantalk Jan 22, 2026
6e07ef8
Fix. fixed merge conflicts
veronika-tseleva-cleantalk Jan 22, 2026
7b85758
Fix. add no session option
veronika-tseleva-cleantalk Jan 23, 2026
d6c79f8
Fix. Fixed synchronization of Spotfix and WebSocket updates
veronika-tseleva-cleantalk Jan 24, 2026
dbeec98
Fix. Fixed synchronization of Spotfix and WebSocket updates
datorik Jan 26, 2026
521739b
Fix. Fixed synchronization of Spotfix and WebSocket updates. Update t…
datorik Jan 26, 2026
4a799e4
Fix. Fixed synchronization of Spotfix and WebSocket updates. Update task
datorik Jan 26, 2026
d2d5e31
Fix. Fixed synchronization of Spotfix and WebSocket updates. Update t…
datorik Jan 26, 2026
706cb4f
Fix. Added word-break to full link
veronika-tseleva-cleantalk Jan 27, 2026
e64e3c1
Fix. fixed localdb
veronika-tseleva-cleantalk Jan 30, 2026
e983d02
Fix. fixed localdb
veronika-tseleva-cleantalk Jan 30, 2026
e02db49
Merge branch 'dev' into indexeddb-websocket-upd
veronika-tseleva-cleantalk Jan 30, 2026
c9c662f
Fix. fixed merge conflicts
veronika-tseleva-cleantalk Jan 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
961 changes: 531 additions & 430 deletions dist/doboard-widget-bundle.js

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions dist/doboard-widget-bundle.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/doboard-widget-bundle.min.js.map

Large diffs are not rendered by default.

455 changes: 227 additions & 228 deletions js/src/handlers.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions js/src/loaders/SpotFixTemplatesLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class SpotFixTemplatesLoader {

static concrete_issue() {
return `
<div class="{{contenerClasess}}">
<div id="doboard_task_widget_concrete-container" class="{{contenerClasess}}">
<div class="doboard_task_widget-header">
<div class="doboard_task_widget_return_to_all doboard_task_widget_cursor-pointer">
<img src="{{chevronBack}}" alt="" title="Return to all spots list">
Expand All @@ -70,8 +70,8 @@ class SpotFixTemplatesLoader {
</div>
</div>
<div class="doboard_task_widget-content doboard_task_widget-concrete_issue">
<div style="background-color: #D6DDE3; padding: 12px 16px">
<a rel="nofollow" href="{{taskPageUrl}}">{{taskFormattedPageUrl}}</a>
<div id="spotfix_doboard_task_widget_url" style="background-color: #D6DDE3; padding: 12px 16px">
<a rel="nofollow" style="word-break: break-all" href="{{taskPageUrl}}">{{taskFormattedPageUrl}}</a>
</div>
<div class="doboard_task_widget-spinner_wrapper_for_containers">
<div class="doboard_task_widget-spinner_for_containers"></div>
Expand Down
196 changes: 90 additions & 106 deletions js/src/localDB.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,120 +6,96 @@ const TABLE_TASKS = 'tasks';
const TABLE_COMMENTS = 'comments';

const LOCAL_DATA_BASE_TABLE = [
{name: TABLE_USERS, keyPath: 'user_id'},
{name: TABLE_TASKS, keyPath: 'taskId'},
{name: TABLE_COMMENTS, keyPath: 'commentId'},
{ name: TABLE_USERS, keyPath: 'user_id' },
{ name: TABLE_TASKS, keyPath: 'taskId' },
{ name: TABLE_COMMENTS, keyPath: 'commentId' },
];

async function openIndexedDB(name, version = indexedDBVersion) {
let dbPromise = null;

function getDBName() {
return `${INDEXED_DB_NAME}_${
localStorage.getItem('spotfix_session_id') ||
localStorage.getItem('spotfix_project_token')
}`;
}

function openIndexedDB(name, version) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(name, version);

request.onupgradeneeded = (e) => {
const db = e.target.result;

LOCAL_DATA_BASE_TABLE.forEach((item) => {
if (!db.objectStoreNames.contains(item.name)) {
const store = db.createObjectStore(item.name, {
keyPath: item.keyPath,
});

if (item.name === TABLE_COMMENTS) {
store.createIndex('taskId', 'taskId');
}
if (item.name === TABLE_TASKS) {
store.createIndex('userId', 'userId');
}
}
});
};

request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
request.onupgradeneeded = (e) => resolve(request.result);
});
}

async function deleteDB() {
try {
const dbs = await window.indexedDB.databases();
for (const db of dbs) {
await new Promise((resolve) => {
const deleteReq = indexedDB.deleteDatabase(db.name);
deleteReq.onsuccess = () => resolve();
deleteReq.onerror = () => resolve();
});
}
} catch (err) {
console.warn('deleteDB error', err);
function getDB() {
if (!dbPromise) {
dbPromise = openIndexedDB(getDBName(), indexedDBVersion);
}
return dbPromise;
}

const spotfixIndexedDB = {
getIndexedDBName: () =>
`${INDEXED_DB_NAME}_${localStorage.getItem('spotfix_session_id') || localStorage.getItem('spotfix_project_token')}`,

error: (request, error) => {
console.error('IndexedDB error', request, error);
},
async function deleteCurrentDB() {
const name = getDBName();
return new Promise((resolve) => {
const req = indexedDB.deleteDatabase(name);
req.onsuccess = () => resolve();
req.onerror = () => resolve();
req.onblocked = () => {
console.warn('IndexedDB delete blocked');
resolve();
};
});
}

const spotfixIndexedDB = {
init: async () => {
const sessionId = localStorage.getItem('spotfix_session_id');
const projectToken = localStorage.getItem('spotfix_project_token');

if (!sessionId && !projectToken) return { needInit: false };
if (!sessionId && !projectToken) {
return { needInit: false };
}

const dbName = spotfixIndexedDB.getIndexedDBName();
await deleteCurrentDB();

await deleteDB();
dbPromise = null;

return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName, indexedDBVersion);

request.onupgradeneeded = (e) => {
const db = e.target.result;
LOCAL_DATA_BASE_TABLE.forEach((item) => {
if (!db.objectStoreNames.contains(item.name)) {
const store = db.createObjectStore(item.name, { keyPath: item.keyPath });
if (item.name === TABLE_COMMENTS) store.createIndex('taskId', 'taskId');
if (item.name === TABLE_TASKS) store.createIndex('userId', 'userId');
}
});
resolve({ needInit: true });
};

request.onsuccess = (e) => {
const db = e.target.result;
const missingStores = LOCAL_DATA_BASE_TABLE.filter(
(item) => !db.objectStoreNames.contains(item.name)
);

if (missingStores.length === 0) {
db.close();
resolve({ needInit: true });
} else {
const newVersion = db.version + 1;
db.close();
const upgradeRequest = indexedDB.open(dbName, newVersion);
upgradeRequest.onupgradeneeded = (e2) => {
const db2 = e2.target.result;
missingStores.forEach((item) => {
const store = db2.createObjectStore(item.name, { keyPath: item.keyPath });
if (item.name === TABLE_COMMENTS) store.createIndex('taskId', 'taskId');
if (item.name === TABLE_TASKS) store.createIndex('userId', 'userId');
});
};
upgradeRequest.onsuccess = () => resolve({ needInit: true });
upgradeRequest.onerror = (err) => reject(err);
}
};

request.onerror = (err) => reject(err);
});
await getDB();
return { needInit: true };
},

withStore: async (table, mode = 'readwrite', callback) => {
const dbName = spotfixIndexedDB.getIndexedDBName();
const db = await openIndexedDB(dbName, indexedDBVersion);
const db = await getDB();

return new Promise((resolve, reject) => {
try {
const transaction = db.transaction(table, mode);
const store = transaction.objectStore(table);

const result = callback(store);

transaction.oncomplete = () => {
db.close();
resolve(result);
};
transaction.onerror = (e) => {
db.close();
reject(e.target.error);
};
} catch (err) {
db.close();
reject(err);
}
const tx = db.transaction(table, mode);
const store = tx.objectStore(table);

const result = callback(store);

tx.oncomplete = () => resolve(result);
tx.onerror = () => reject(tx.error);
});
},

Expand All @@ -140,35 +116,43 @@ const spotfixIndexedDB = {
},

clearTable: async (table) => {
return spotfixIndexedDB.withStore(table, 'readwrite', (store) => store.clear());
return spotfixIndexedDB.withStore(table, 'readwrite', (store) =>
store.clear()
);
},

clearPut: async (table, data) => {
await spotfixIndexedDB.clearTable(table);
await spotfixIndexedDB.put(table, data);
return spotfixIndexedDB.withStore(table, 'readwrite', (store) => {
store.clear();
if (Array.isArray(data)) {
data.forEach((item) => store.put(item));
} else {
store.put(data);
}
});
},

getAll: async (table, indexName, value) => {
return spotfixIndexedDB.withStore(table, 'readonly', (store) => {
return new Promise((resolve, reject) => {
let request;
if (indexName && value !== undefined) {
request = store.index(indexName).getAll(value);
} else {
request = store.getAll();
}
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
const req =
indexName && value !== undefined
? store.index(indexName).getAll(value)
: store.getAll();

req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
});
},

getTable: async (table) => {
if (!localStorage.getItem('spotfix_session_id') && !localStorage.getItem('spotfix_project_token')) return [];
if (
!localStorage.getItem('spotfix_session_id') &&
!localStorage.getItem('spotfix_project_token')
) {
return [];
}
return spotfixIndexedDB.getAll(table);
},

deleteTable: async (table, key) => {
return spotfixIndexedDB.delete(table, key);
},
};
Loading