Skip to content

Commit

Permalink
Merge pull request #1 from rafaismyname/feature/list
Browse files Browse the repository at this point in the history
0.2.0
  • Loading branch information
rafaismyname authored Oct 5, 2018
2 parents 5430220 + d09be61 commit fc41c81
Show file tree
Hide file tree
Showing 9 changed files with 392 additions and 32 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
# WhatsBuddy
> WebExtension that gives your WhatsApp Web SUPER POWERS! 🖥💬⚡️🔥
Message Macros: Add quick message macros for a faster response!
🤘🏼Hide List: Hide annoying contacts! Keep your chatlist enjoyable again!

WYSI**N**WYG: Enables an utility bar on top of your compose message's bar allowing you to easily format your text.
⚡️Message Macros: Add quick message macros for a faster response!

👸🏼WYSI**N**WYG: Enables an utility bar on top of your compose message's bar allowing you to easily format your text.

## Installing

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "whatsbuddy",
"version": "0.1.0",
"version": "0.2.0",
"description": "WebExtension that adds extra functionalities to WhatsApp Web",
"license": "MIT",
"bugs": {
Expand Down
29 changes: 29 additions & 0 deletions src/list/events.js
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 {};
14 changes: 14 additions & 0 deletions src/list/list.js
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());
});
8 changes: 8 additions & 0 deletions src/list/list.scss
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;
}
196 changes: 196 additions & 0 deletions src/list/models.js
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);
});
}
}
6 changes: 3 additions & 3 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

"name": "WhatsBuddy",
"description": "WebExtension that adds extra functionalities to WhatsApp Web",
"version": "0.1.0",
"version": "0.2.0",

"icons": {
"512": "./icons/icon.png",
Expand Down Expand Up @@ -39,8 +39,8 @@
"content_scripts": [
{
"matches": ["https://web.whatsapp.com/*"],
"js": ["./chat/chat.js"],
"css" : ["./chat/chat.scss"]
"js": ["./chat/chat.js", "./list/list.js"],
"css" : ["./chat/chat.scss", "./list/list.scss"]
}
],

Expand Down
61 changes: 48 additions & 13 deletions src/options/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@
<header class="container">
<section class="columns text-center mt-2">
<div class="column col-12">
<h4>WhatsBuddy</h4>
<h2>WhatsBuddy</h2>
</div>
</section>
</header>

<main class="container">
<section class="columns">
<div class="column col-12 mb-2" id="whatsbuddy-options-success">
</div>

<div class="column col-6 col-xs-12 col-mx-auto">
<div class="panel">
<div class="panel-header">
Expand All @@ -33,29 +36,61 @@ <h4>Message Macros</h4>
</div>
</div>
<div class="panel-body">
<div class="columns">
<div class="column col-12" id="whatsbuddy-options-success">
<table class="table table-striped table-hover">
<thead>
<th>Macro Name</th>
<th>Message</th>
<th>&nbsp;</th>
</thead>
<tbody id="whatsbuddy-options-macros">
</tbody>
</table>
</div>
</div>
</div>

<div class="column col-6 col-xs-12 col-mx-auto">
<div class="panel">
<div class="panel-header">
<h4>Chats List</h4>
</div>
<div class="panel-body">
<div class="columns col-gapless">
<div class="column col-6">
<h5 class="text-center">Visible Chats</h5>

<table class="table table-striped table-hover">
<thead>
<th>Name</th>
<th>Hide</th>
</thead>
<tbody id="whatsbuddy-chats">
</tbody>
</table>
</div>
<div class="column col-12">

<div class="column col-6">
<h5 class="text-center">Hidden Chats</h5>

<table class="table table-striped table-hover">
<thead>
<th>Macro Name</th>
<th>Message</th>
<th>&nbsp;</th>
<th>Show</th>
<th class="text-right">Name</th>
</thead>
<tbody id="whatsbuddy-options-macros">
<tbody id="whatsbuddy-hidden-chats">
</tbody>
</table>
</div>
</div>
</div>
<div class="panel-footer">
<button class="btn btn-block btn-success" id="whatsbuddy-options-save">
<i class="icon icon-check"></i> Save options
</button>
</div>
</div>
</div>

<div class="column col-12 mt-2">
<button class="btn btn-block btn-success" id="whatsbuddy-options-save">
<i class="icon icon-check"></i> Save options
</button>
</div>
</section>
</main>

Expand Down
Loading

0 comments on commit fc41c81

Please sign in to comment.