-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from rafaismyname/feature/list
0.2.0
- Loading branch information
Showing
9 changed files
with
392 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
const SIDE_PANE_SELECTOR = '#pane-side'; | ||
const SIDE_PANE_ROOT_CLASSNAME = 'app'; | ||
|
||
export const onSidePaneLoaded = (callback) => { | ||
const paneObserver = new MutationObserver((mutations, observer) => { | ||
mutations.some((mutation) => { | ||
const addedNodes = [...mutation.addedNodes]; | ||
return addedNodes.some((node) => { | ||
const nodeClass = node.className && node.className.includes && node.className; | ||
if (!nodeClass || !nodeClass.includes(SIDE_PANE_ROOT_CLASSNAME)) { | ||
return false; | ||
} | ||
|
||
const sidePane = node.querySelector(SIDE_PANE_SELECTOR); | ||
if (!sidePane) return false; | ||
|
||
observer.disconnect(); | ||
|
||
callback(sidePane); | ||
|
||
return true; | ||
}); | ||
}); | ||
}); | ||
|
||
return paneObserver.observe(document, { childList: true, subtree: true }); | ||
}; | ||
|
||
export default {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { onSidePaneLoaded } from './events'; | ||
import { BaseList, OverlayList } from './models'; | ||
|
||
const baseList = new BaseList(); | ||
const overlayList = new OverlayList(); | ||
|
||
onSidePaneLoaded((sidePane) => { | ||
baseList.build(sidePane, overlayList); | ||
|
||
overlayList.build(sidePane, baseList); | ||
overlayList.init(); | ||
|
||
window.addEventListener('error', () => document.location.reload()); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
.whatsbuddy-overlay-chat { | ||
transform: none !important; | ||
position: initial !important; | ||
} | ||
|
||
.whatsbuddy-overlay-hidden-chat { | ||
display: none; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
import * as storage from '../helpers/storage'; | ||
|
||
const BASE_LIST_SELECTOR = 'div:first-child > div:first-child > div:first-child'; | ||
const OVERLAY_LIST_ID = 'whatsbuddy-overlay-list'; | ||
const OVERLAY_CHAT_CLASSNAME = 'whatsbuddy-overlay-chat'; | ||
const OVERLAY_HIDDEN_CHAT_CLASSNAME = 'whatsbuddy-overlay-hidden-chat'; | ||
const CHAT_NAME_SELECTOR = 'span[dir="auto"][title][class]'; | ||
const CHAT_NAME_ATTRIBUTE = 'title'; | ||
const CHAT_TYPE_SELECTOR = 'span[data-icon^="default-"]'; | ||
const CHAT_TYPE_ATTRIBUTE = 'data-icon'; | ||
const CHAT_TYPE_PREFIX = 'default-'; | ||
const CHAT_IMAGE_SELECTOR = 'img[src^="https://dyn.web"]'; | ||
const CHAT_IMAGE_UID_URL_PARAM = 'u'; | ||
|
||
export class Chat { | ||
constructor(element) { | ||
this.element = element; | ||
|
||
this.id = null; | ||
this.name = null; | ||
this.type = null; | ||
|
||
this.styleObserver = null; | ||
|
||
this.build(); | ||
} | ||
|
||
build() { | ||
const nameElement = this.element.querySelector(CHAT_NAME_SELECTOR); | ||
this.name = nameElement.getAttribute(CHAT_NAME_ATTRIBUTE); | ||
|
||
const typeElement = this.element.querySelector(CHAT_TYPE_SELECTOR); | ||
this.type = typeElement.getAttribute(CHAT_TYPE_ATTRIBUTE).replace(CHAT_TYPE_PREFIX, ''); | ||
|
||
const imgElement = this.element.querySelector(CHAT_IMAGE_SELECTOR); | ||
const imgUrl = imgElement && imgElement.getAttribute('src'); | ||
const uid = imgUrl && new URLSearchParams(imgUrl).get(CHAT_IMAGE_UID_URL_PARAM); | ||
|
||
this.id = uid || this.name; | ||
|
||
return this; | ||
} | ||
|
||
setStyleObserver(callback) { | ||
this.styleObserver = new MutationObserver(() => callback()); | ||
} | ||
|
||
initStyleObserver() { | ||
const observerOptions = { attributes: true, attributeFilter: ['style'] }; | ||
this.styleObserver.observe(this.element, observerOptions); | ||
} | ||
|
||
toObject() { | ||
return { | ||
id: this.id, | ||
type: this.type, | ||
name: this.name, | ||
}; | ||
} | ||
} | ||
|
||
export class BaseList { | ||
constructor() { | ||
this.parent = null; | ||
this.element = null; | ||
|
||
this.overlay = null; | ||
|
||
this.chats = {}; | ||
this.sortedChatIds = []; | ||
} | ||
|
||
build(parent, overlay) { | ||
this.parent = parent; | ||
this.element = parent.querySelector(BASE_LIST_SELECTOR); | ||
this.overlay = overlay; | ||
|
||
return this; | ||
} | ||
|
||
init() { | ||
this.process(); | ||
|
||
const observer = new MutationObserver(() => this.process()); | ||
observer.observe(this.element, { childList: true, subtree: true }); | ||
} | ||
|
||
hide() { | ||
this.element.style.display = 'none'; | ||
} | ||
|
||
fetchChats() { | ||
const chatsElements = [...this.element.children]; | ||
this.chats = chatsElements.reduce((acc, chatElement) => { | ||
const chat = new Chat(chatElement); | ||
|
||
chat.setStyleObserver(() => this.process()); | ||
|
||
return Object.assign({}, acc, { [chat.id]: chat }); | ||
}, this.chats); | ||
|
||
const chatIds = Object.keys(this.chats); | ||
this.sortedChatIds = chatIds.sort((a, b) => { | ||
const getPriority = (chatId) => { | ||
const chat = this.chats[chatId]; | ||
const chatElement = chat.element; | ||
const transformVal = chatElement.style.transform; | ||
const transformMatch = transformVal.match(/translateY\((\w*)px\)/i); | ||
const rawPriority = (transformMatch && transformMatch[1]) || 999999; | ||
return parseInt(rawPriority, 10); | ||
}; | ||
|
||
return getPriority(a) - getPriority(b); | ||
}); | ||
|
||
return this.chats; | ||
} | ||
|
||
persistChats() { | ||
const chats = Object | ||
.keys(this.chats) | ||
.map(chatId => this.chats[chatId].toObject()); | ||
|
||
return storage.save({ chats }); | ||
} | ||
|
||
process() { | ||
this.fetchChats(); | ||
this.persistChats(); | ||
this.hide(); | ||
|
||
this.overlay.render(); | ||
} | ||
} | ||
|
||
export class OverlayList { | ||
constructor() { | ||
this.parent = null; | ||
this.element = null; | ||
|
||
this.baseList = null; | ||
|
||
this.hiddenChats = []; | ||
} | ||
|
||
build(parent, baseList) { | ||
this.parent = parent; | ||
this.baseList = baseList; | ||
|
||
this.element = document.createElement('div'); | ||
this.element.id = OVERLAY_LIST_ID; | ||
|
||
parent.insertBefore(this.element, parent.firstChild); | ||
|
||
return this; | ||
} | ||
|
||
init() { | ||
const storageParams = { hiddenChats: [] }; | ||
storage.get(storageParams).then(({ hiddenChats }) => { | ||
this.hiddenChats = hiddenChats; | ||
this.baseList.init(); | ||
}); | ||
} | ||
|
||
empty() { | ||
while (this.element.firstChild) { | ||
this.element.removeChild(this.element.firstChild); | ||
} | ||
} | ||
|
||
appendChat(chat) { | ||
const chatElement = chat.element; | ||
|
||
chatElement.classList.add(OVERLAY_CHAT_CLASSNAME); | ||
|
||
if (this.hiddenChats.includes(chat.id)) { | ||
chatElement.classList.add(OVERLAY_HIDDEN_CHAT_CLASSNAME); | ||
} | ||
|
||
this.element.appendChild(chatElement); | ||
} | ||
|
||
render() { | ||
this.empty(); | ||
|
||
this.baseList.sortedChatIds.forEach((chatId) => { | ||
const chat = this.baseList.chats[chatId]; | ||
if (!chat) return; | ||
|
||
chat.initStyleObserver(); | ||
|
||
this.appendChat(chat); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.