diff --git a/src/components/CollectionsDropdown.vue b/src/components/CollectionsDropdown.vue
new file mode 100644
index 0000000..c9cbebb
--- /dev/null
+++ b/src/components/CollectionsDropdown.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+
diff --git a/src/constants/ContentScriptMessageTypes.ts b/src/constants/ContentScriptMessageTypes.ts
index e1296d3..3e16966 100644
--- a/src/constants/ContentScriptMessageTypes.ts
+++ b/src/constants/ContentScriptMessageTypes.ts
@@ -1,4 +1,6 @@
export default {
GET_CONTENT_SCRIPT_STATUS: 'get-content-script-status',
- GET_COLLECTION_SAVED_ITEMS: 'get-collection-saved-items'
+ GET_COLLECTION_SAVED_ITEMS: 'get-collection-saved-items',
+ GET_COLLECTIONS_LIST: 'get-collections-list',
+ REDIRECT_TO_COLLECTION: 'redirect-to-collection',
}
\ No newline at end of file
diff --git a/src/models/Collection.ts b/src/models/Collection.ts
new file mode 100644
index 0000000..bba4237
--- /dev/null
+++ b/src/models/Collection.ts
@@ -0,0 +1,4 @@
+export interface Collection {
+ _id: string,
+ name: string,
+}
\ No newline at end of file
diff --git a/src/services/content/content-script.ts b/src/services/content/content-script.ts
index 2195c70..3ac454c 100644
--- a/src/services/content/content-script.ts
+++ b/src/services/content/content-script.ts
@@ -6,13 +6,17 @@ import { SavedItem } from '@/models/SavedItem';
import {
SOCIAL_SHARE_BUTTON_IMG_SELECTOR,
locateColumnsWrapper,
- unNestElement
+ getUserCollections,
+ unNestElement,
} from '@/services/content/utils/extractTools';
import { getUid } from '@/utils'
const {
+ GET_COLLECTIONS_LIST,
+ GET_COLLECTION_SAVED_ITEMS,
GET_CONTENT_SCRIPT_STATUS,
+ REDIRECT_TO_COLLECTION,
} = ContentScriptMessageTypes;
const {
@@ -34,12 +38,53 @@ chrome.runtime.onConnect.addListener((port) => {
}
})
+// Switch case handlers
+type HandlerArgs = {
+ port: chrome.runtime.Port,
+ opts?: any,
+};
+
+function handle_GET_COLLECTION_SAVED_ITEMS({ port }: HandlerArgs) {
+ port.postMessage(extractSavedItemsData());
+}
+
+function handle_REDIRECT_TO_COLLECTION({ port, opts }: HandlerArgs) {
+ port.disconnect()
+ const { url } = opts;
+ location.href = url;
+}
+
+function handle_GET_COLLECTIONS_LIST({ port }: HandlerArgs) {
+ port.postMessage(getCollectionsList());
+}
+
function handleResponse(port: chrome.runtime.Port) {
+
+ // Middleware to attach port instance to handler
+ const call = (handlerFunc: Function, opts?: Object) => {
+ handlerFunc({ port, opts });
+ }
+
port.onMessage.addListener(request => {
- if (request) {
- port.postMessage(extractSavedItemsData())
+ const { type } = request;
+
+ switch(type){
+ case GET_COLLECTION_SAVED_ITEMS:
+ call(handle_GET_COLLECTION_SAVED_ITEMS);
+ break;
+
+ case GET_COLLECTIONS_LIST:
+ call(handle_GET_COLLECTIONS_LIST);
+ break;
+
+ case REDIRECT_TO_COLLECTION:
+ call(handle_REDIRECT_TO_COLLECTION, { url: request.url });
+ break;
+
+ default:
+ break;
}
- })
+ });
}
/**
@@ -135,3 +180,10 @@ function handleResponse(port: chrome.runtime.Port) {
return data;
}
+
+function getCollectionsList() {
+ return getUserCollections();
+}
+
+// Need to craft the URL manually for each collection
+// We can extract the data-id from one of the span's children
diff --git a/src/services/content/utils/extractTools.ts b/src/services/content/utils/extractTools.ts
index fe910e9..6270614 100644
--- a/src/services/content/utils/extractTools.ts
+++ b/src/services/content/utils/extractTools.ts
@@ -1,8 +1,22 @@
+import { Collection } from "@/models/Collection";
+
+/**
+ * Query selector strings
+ */
const SOCIAL_SHARE_BUTTON_IMG_SELECTOR = 'img[src="https://www.gstatic.com/save/icons/share_blue.svg"]';
+const COLLECTIONS_QUERY = 'span[role="option"]';
+const DATA_ID_ATTR = 'data-id';
+
+/**
+ * Index paths for traversing children
+ */
+const COLLECTION_TITLE_DIV = [0,0,0,1,0];
+const COLLECTION_DATAID_DIV = [1];
+
/**
* Go up "n" parent nodes from passed element.
*/
-const unNestElement = (element: any, levels = 7) => {
+const unNestElement = (element: any, levels: number) => {
let i = 0;
let destElement = element;
try {
@@ -17,6 +31,37 @@ const unNestElement = (element: any, levels = 7) => {
}
}
+// Notes, if we want to access a children that is between the traversing path,
+// could update to return the target and a second option that is the array of seen
+// children (dynamic programming?)
+/**
+ * Function to traverse children of an element through the indexPath
+ *
+ * Ex: indicesPath = [0,1,2] => element.children[0].children[1].children[2]
+ * @param element
+ * @param indicesPath array of indices where to traverse children
+ * @returns
+ */
+const traverseChildren = (element: Element, indicesPath: number[]) => {
+ let targetElem: Element | null = element;
+
+ try {
+
+ indicesPath.forEach((childrenIndex) => {
+ if (!targetElem?.hasChildNodes()) {
+ throw new Error('Element has no children elements');
+ }
+
+ targetElem = targetElem.children[childrenIndex];
+ });
+
+ return targetElem;
+
+ } catch (e) {
+ throw new Error('Error while traversing children');
+ }
+}
+
const locateColumnsWrapper = (savedItemsGrid: any | null) => {
let columnsWrapperFound = false;
let div = savedItemsGrid;
@@ -30,8 +75,50 @@ const locateColumnsWrapper = (savedItemsGrid: any | null) => {
return div;
}
+const getUserCollections = () => {
+ // Helper function
+ // @TODO: Recheck typing
+ /**
+ * Receive element wrapper and extract collection's relevant
+ * information
+ */
+ const extractCollectionData = (collectionElem: HTMLSpanElement) => {
+ const divWithDataId = traverseChildren(
+ collectionElem,
+ COLLECTION_DATAID_DIV
+ );
+
+ const _id = divWithDataId.getAttribute(DATA_ID_ATTR);
+ const divWithTitle = traverseChildren(
+ divWithDataId,
+ COLLECTION_TITLE_DIV
+ );
+
+ if (!divWithTitle || !_id) {
+ throw new Error('Error while getting collection data.');
+ }
+
+ return {
+ _id,
+ name: divWithTitle.textContent || 'Couldn\'t find name'
+ }
+ };
+
+ // Variables
+ const collections: Collection[] = [];
+
+ // Logic
+ const collectionElements: NodeList = document.querySelectorAll(COLLECTIONS_QUERY);
+ collectionElements.forEach((collectionElem) => {
+ const data = extractCollectionData(collectionElem as HTMLSpanElement);
+ collections.push(data);
+ });
+ return collections;
+}
+
export {
SOCIAL_SHARE_BUTTON_IMG_SELECTOR,
unNestElement,
- locateColumnsWrapper
+ locateColumnsWrapper,
+ getUserCollections
}
\ No newline at end of file
diff --git a/src/store/savedItemsStore.ts b/src/store/savedItemsStore.ts
index 13767d9..0e64d03 100644
--- a/src/store/savedItemsStore.ts
+++ b/src/store/savedItemsStore.ts
@@ -16,8 +16,13 @@ export const useSavedItemsStore= defineStore('savedItemsStore', () => {
savedItems.value.set(newSavedItem._id, newSavedItem);
}
+ function clearItems() {
+ savedItems.value.clear();
+ }
+
return {
addSavedItem,
+ clearItems,
itemsCount,
savedItems,
savedItemsAsArray
diff --git a/src/store/toExportItemsStore.ts b/src/store/toExportItemsStore.ts
index c2edd4d..4dbf38d 100644
--- a/src/store/toExportItemsStore.ts
+++ b/src/store/toExportItemsStore.ts
@@ -11,14 +11,17 @@ export const useToExportItemsStore= defineStore('toExportItemsStore', () => {
itemsToExport.value,
([_, { _id, url, title }]) => ({ _id, url, title })
));
-
-
+
function addItemToExport(savedItem: SavedItem | undefined) {
if (savedItem && savedItem._id) {
itemsToExport.value.set(savedItem._id, savedItem);
}
}
+ function clearItems() {
+ itemsToExport.value.clear();
+ }
+
function removeItemFromExport(_id: string) {
const entryExists = itemsToExport.value.has(_id);
if (entryExists) {
@@ -47,6 +50,7 @@ export const useToExportItemsStore= defineStore('toExportItemsStore', () => {
return {
addItemToExport,
+ clearItems,
itemsCount,
itemsToExport,
removeItemFromExport,
diff --git a/src/utils/index.ts b/src/utils/index.ts
index cee88ad..491d603 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -8,7 +8,10 @@ const getUid = function(){
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
+const buildCollectionRedirectURL = (collectionId: string) => `https://www.google.com/save/list/${collectionId}`;
+
export {
+ buildCollectionRedirectURL,
getCurrentTabID,
getUid
}
\ No newline at end of file
diff --git a/src/views/Home.vue b/src/views/Home.vue
index c6be322..53e9abb 100644
--- a/src/views/Home.vue
+++ b/src/views/Home.vue
@@ -1,10 +1,14 @@