├── .gitignore ├── Images ├── Promo.png ├── popup.png ├── options.png ├── Promo_front.png ├── notifbureau.png ├── notificateur.png ├── options_1280x800.png └── .desktop ├── Firefox ├── icons │ ├── header.png │ ├── pm_new.png │ ├── clem_48.png │ ├── icone_m_20.png │ ├── icone_n_20.png │ └── notconnected.png ├── popup │ ├── fonts │ │ └── source-sans-pro.woff2 │ ├── notifier_popup.html │ ├── notifier.js │ └── notifier_popup.css ├── options │ ├── options.html │ └── options.js ├── manifest.json ├── updateState.js └── notifier.js ├── Google Chrome ├── icons │ ├── mp.png │ ├── badge.png │ ├── bulle.png │ ├── alerte.png │ ├── big_mp.png │ ├── favicon.png │ ├── icone_16.png │ ├── icone_19.png │ ├── icone_24.png │ ├── icone_32.png │ ├── icone_38.png │ ├── icone_48.png │ ├── logo-zds.png │ ├── mp_white.png │ ├── original.png │ ├── roadmap.png │ ├── big_alerte.png │ ├── big_badge.png │ ├── icone_128.png │ ├── alerte_white.png │ ├── badge_white.png │ ├── big_message.png │ ├── big_roadmap.png │ ├── bulle_white.png │ ├── roadmap_white.png │ ├── icone_38_logout.png │ ├── clem_pas_contente.png │ └── icone_38_parsing.png ├── sounds │ ├── standard │ │ └── Ting.wav │ └── packs.json ├── popup │ ├── popup.html │ ├── popup.css │ └── popup.js ├── manifest.json ├── injected.js ├── welcome │ ├── welcome.css │ └── welcome.html ├── common.js ├── options │ ├── options.css │ ├── options.html │ └── options.js ├── fake-data.html └── background.js ├── Universal ├── images │ ├── header.png │ ├── pm-new.png │ ├── icon-128.png │ ├── icon-48.png │ ├── notifications.png │ ├── clemoji-smile.svg │ ├── clemoji-party.svg │ ├── clemoji-moneymouth.svg │ ├── clemoji-dizzy.svg │ ├── clemoji-asleep.svg │ └── logo.svg ├── popup │ ├── fonts │ │ └── source-sans-pro.woff2 │ ├── index.html │ ├── elements │ │ ├── _element.js │ │ ├── logo.js │ │ └── popup.js │ └── popup.css ├── background-page.html ├── models │ ├── user.js │ └── notification.js ├── config.js ├── content-script.js ├── manifest.json ├── date-format.js └── notifier.js ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | -------------------------------------------------------------------------------- /Images/Promo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Images/Promo.png -------------------------------------------------------------------------------- /Images/popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Images/popup.png -------------------------------------------------------------------------------- /Images/options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Images/options.png -------------------------------------------------------------------------------- /Images/Promo_front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Images/Promo_front.png -------------------------------------------------------------------------------- /Images/notifbureau.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Images/notifbureau.png -------------------------------------------------------------------------------- /Firefox/icons/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Firefox/icons/header.png -------------------------------------------------------------------------------- /Firefox/icons/pm_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Firefox/icons/pm_new.png -------------------------------------------------------------------------------- /Images/notificateur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Images/notificateur.png -------------------------------------------------------------------------------- /Firefox/icons/clem_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Firefox/icons/clem_48.png -------------------------------------------------------------------------------- /Google Chrome/icons/mp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/mp.png -------------------------------------------------------------------------------- /Images/options_1280x800.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Images/options_1280x800.png -------------------------------------------------------------------------------- /Universal/images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Universal/images/header.png -------------------------------------------------------------------------------- /Universal/images/pm-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Universal/images/pm-new.png -------------------------------------------------------------------------------- /Firefox/icons/icone_m_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Firefox/icons/icone_m_20.png -------------------------------------------------------------------------------- /Firefox/icons/icone_n_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Firefox/icons/icone_n_20.png -------------------------------------------------------------------------------- /Google Chrome/icons/badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/badge.png -------------------------------------------------------------------------------- /Google Chrome/icons/bulle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/bulle.png -------------------------------------------------------------------------------- /Universal/images/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Universal/images/icon-128.png -------------------------------------------------------------------------------- /Universal/images/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Universal/images/icon-48.png -------------------------------------------------------------------------------- /Firefox/icons/notconnected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Firefox/icons/notconnected.png -------------------------------------------------------------------------------- /Google Chrome/icons/alerte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/alerte.png -------------------------------------------------------------------------------- /Google Chrome/icons/big_mp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/big_mp.png -------------------------------------------------------------------------------- /Google Chrome/icons/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/favicon.png -------------------------------------------------------------------------------- /Google Chrome/icons/icone_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/icone_16.png -------------------------------------------------------------------------------- /Google Chrome/icons/icone_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/icone_19.png -------------------------------------------------------------------------------- /Google Chrome/icons/icone_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/icone_24.png -------------------------------------------------------------------------------- /Google Chrome/icons/icone_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/icone_32.png -------------------------------------------------------------------------------- /Google Chrome/icons/icone_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/icone_38.png -------------------------------------------------------------------------------- /Google Chrome/icons/icone_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/icone_48.png -------------------------------------------------------------------------------- /Google Chrome/icons/logo-zds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/logo-zds.png -------------------------------------------------------------------------------- /Google Chrome/icons/mp_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/mp_white.png -------------------------------------------------------------------------------- /Google Chrome/icons/original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/original.png -------------------------------------------------------------------------------- /Google Chrome/icons/roadmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/roadmap.png -------------------------------------------------------------------------------- /Google Chrome/icons/big_alerte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/big_alerte.png -------------------------------------------------------------------------------- /Google Chrome/icons/big_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/big_badge.png -------------------------------------------------------------------------------- /Google Chrome/icons/icone_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/icone_128.png -------------------------------------------------------------------------------- /Universal/images/notifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Universal/images/notifications.png -------------------------------------------------------------------------------- /Google Chrome/icons/alerte_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/alerte_white.png -------------------------------------------------------------------------------- /Google Chrome/icons/badge_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/badge_white.png -------------------------------------------------------------------------------- /Google Chrome/icons/big_message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/big_message.png -------------------------------------------------------------------------------- /Google Chrome/icons/big_roadmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/big_roadmap.png -------------------------------------------------------------------------------- /Google Chrome/icons/bulle_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/bulle_white.png -------------------------------------------------------------------------------- /Google Chrome/icons/roadmap_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/roadmap_white.png -------------------------------------------------------------------------------- /Google Chrome/icons/icone_38_logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/icone_38_logout.png -------------------------------------------------------------------------------- /Google Chrome/sounds/standard/Ting.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/sounds/standard/Ting.wav -------------------------------------------------------------------------------- /Firefox/popup/fonts/source-sans-pro.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Firefox/popup/fonts/source-sans-pro.woff2 -------------------------------------------------------------------------------- /Google Chrome/icons/clem_pas_contente.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/clem_pas_contente.png -------------------------------------------------------------------------------- /Google Chrome/icons/icone_38_parsing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Google Chrome/icons/icone_38_parsing.png -------------------------------------------------------------------------------- /Universal/popup/fonts/source-sans-pro.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zestedesavoir/extensions-notificateurs/HEAD/Universal/popup/fonts/source-sans-pro.woff2 -------------------------------------------------------------------------------- /Images/.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Encoding=UTF-8 3 | Name=Lien vers 4 | Type=Link 5 | URL=http://zestedesavoir.com/media/galleries/340/42deddfe-1976-4f36-8078-d2ccd617e595.png 6 | Icon=image-png 7 | -------------------------------------------------------------------------------- /Universal/background-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Notifications Zeste de Savoir 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Google Chrome/sounds/packs.json: -------------------------------------------------------------------------------- 1 | { 2 | "sounds": { 3 | "standard": { 4 | "name": "Pack Standard", 5 | "folder": "standard", 6 | "sounds": { 7 | "notif_new": "Ting.wav", 8 | "mp_new": "Ting.wav", 9 | "notif_mp_new": "Ting.wav" 10 | } 11 | } 12 | }, 13 | "default_soundpack": "standard" 14 | } -------------------------------------------------------------------------------- /Google Chrome/popup/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ZdS Notificateur 6 | 7 | 8 | 9 | 12 |
13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Universal/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Notifications Zeste de Savoir 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Universal/models/user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Class representing a user. 3 | * @property {string} username 4 | * @property {string} avatar_url 5 | */ 6 | export class ZdsUser { 7 | /** 8 | * Create a user. 9 | * @param {string} username - The user's public name. 10 | * @param {string} avatar_url - The URL of the avatar (should point to a valid image). 11 | */ 12 | constructor(username, avatar_url) { 13 | this.username = username 14 | this.avatar_url = avatar_url 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Universal/popup/elements/_element.js: -------------------------------------------------------------------------------- 1 | export class ZdsElement extends HTMLElement { 2 | get style() { 3 | return `` 4 | } 5 | get template() { 6 | return `` 7 | } 8 | 9 | constructor() { 10 | super() 11 | } 12 | 13 | connectedCallback() { 14 | this.render() 15 | } 16 | 17 | render() { 18 | this.innerHTML = this.template 19 | 20 | const style = document.createElement('style') 21 | style.textContent = this.styles 22 | this.appendChild(style) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Firefox/options/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | Options: 12 |
13 |
14 | 15 |
16 |
17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Universal/config.js: -------------------------------------------------------------------------------- 1 | /* Variables de configuration */ 2 | 3 | export const DEBUG = false 4 | 5 | export const BASE_URL = DEBUG ? 'https://beta.zestedesavoir.com' : 'https://zestedesavoir.com' 6 | 7 | export const TOKEN = 'zds-notifier' 8 | 9 | export const HTTP_STATUS_LOGGED_OUT = 401 10 | export const HTTP_STATUS_LOGGED_IN = 200 11 | 12 | export const BADGE_COLOR_DEFAULT = '#0d6791' 13 | export const BADGE_COLOR_ERROR = '#d90000' 14 | export const BADGE_COLOR_OK = '#65931a' 15 | 16 | export const POLLING_RATE = 30 // Secondes entre chaque mise à jour 17 | -------------------------------------------------------------------------------- /Firefox/options/options.js: -------------------------------------------------------------------------------- 1 | function saveOptions(e) { 2 | chrome.storage.local.set({ 3 | notify: document.querySelector("#notify").checked, 4 | white_theme: document.querySelector("#white_theme").checked 5 | }); 6 | } 7 | 8 | function restoreOptions() { 9 | chrome.storage.local.get('notify', (res) => { 10 | document.querySelector("#notify").checked = res.notify || false; 11 | }); 12 | chrome.storage.local.get('white_theme', (res) => { 13 | document.querySelector("#white_theme").checked = res.white_theme || false; 14 | }); 15 | } 16 | 17 | document.addEventListener('DOMContentLoaded', restoreOptions); 18 | document.querySelector("form").addEventListener("submit", saveOptions); 19 | -------------------------------------------------------------------------------- /Universal/models/notification.js: -------------------------------------------------------------------------------- 1 | import { BASE_URL } from '../config.js' 2 | import { ZdsUser } from './user.js' 3 | 4 | /** 5 | * Class representing a notification. 6 | * @property {string} title - The title of the notification 7 | * @property {ZdsUser} sender - The author of the notification 8 | * @property {string} url - The URL the notification points to 9 | * @property {string} fullURL - The full URL the notification points to 10 | */ 11 | export class ZdsNotification { 12 | get fullURL() { 13 | return `${BASE_URL}${this.url}` 14 | } 15 | 16 | /** 17 | * Create a notification. 18 | * @param {object} notif - The raw notification retrieved from the API 19 | */ 20 | constructor(notif) { 21 | this.title = notif.title 22 | this.url = notif.url 23 | this.sender = new ZdsUser(notif.sender.username, notif.sender.avatar_url) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Universal/popup/elements/logo.js: -------------------------------------------------------------------------------- 1 | import { BASE_URL } from '../../config.js' 2 | import { ZdsElement } from './_element.js' 3 | 4 | /** 5 | * WebComponent permettant d'afficher le logo ZdS avec un lien vers la page d'accueil 6 | */ 7 | export class Logo extends ZdsElement { 8 | get styles () { 9 | return ` 10 | zds-logo { 11 | display: block; 12 | border-bottom: 2px solid var(--color-orange); 13 | } 14 | zds-logo a { 15 | display: block; 16 | text-align: center; 17 | line-height: 1; 18 | background-color: var(--color-logo); 19 | } 20 | zds-logo a:hover, 21 | zds-logo a:focus { 22 | background-color: var(--color-primary); 23 | } 24 | zds-logo img { 25 | height: 4rem; 26 | } 27 | ` 28 | } 29 | 30 | get template() { 31 | return ` 32 | 33 | Zeste de Savoir 34 | 35 | ` 36 | } 37 | 38 | constructor() { 39 | super() 40 | } 41 | 42 | } 43 | window.customElements.define('zds-logo', Logo) 44 | -------------------------------------------------------------------------------- /Firefox/popup/notifier_popup.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 |
6 | 7 |
8 | 9 |
10 |
11 |
Toutes les notifications
12 | 13 | 14 | 15 |
16 |
17 |
18 |
«Vous zestes les bienvenus.»
19 |
Connexion
20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Google Chrome/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ZdS Notificateur", 3 | "version": "1.1.1", 4 | "manifest_version": 2, 5 | "description": "Extensions pour Google Chrome pour connaitre le nombre de notifications ou MP de ZesteDeSavoir sans avoir besoin d'ouvrir le site", 6 | "offline_enabled": false, 7 | "icons": { 8 | "16": "icons/icone_16.png", 9 | "48": "icons/icone_48.png", 10 | "128": "icons/icone_128.png" 11 | }, 12 | "permissions": [ 13 | "http://*.zestedesavoir.com/*", 14 | "http://www.gravatar.com/*", 15 | "tabs", 16 | "alarms", 17 | "notifications", 18 | "storage" 19 | ], 20 | "options_page": "options/options.html", 21 | "browser_action": { 22 | "default_icon": "icons/icone_38.png", 23 | "default_title": "ZdS Notificateur", 24 | "default_popup": "popup/popup.html" 25 | }, 26 | "background": { 27 | "scripts": ["common.js", "background.js"], 28 | "persistent": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Firefox/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Zds-Notificateur", 4 | "version": "2.6", 5 | "homepage_url": "https://zestedesavoir.com", 6 | "description": "Une extension pour connaitre le nombre de notifications et de MP de ZesteDeSavoir sans avoir besoin d'ouvrir le site", 7 | 8 | "icons": { 9 | "48": "icons/clem_48.png" 10 | }, 11 | 12 | "permissions": [ 13 | "http://useragentstring.com/*", "*://*.zestedesavoir.com/*", "notifications", "storage" 14 | ], 15 | 16 | "applications": { 17 | "gecko": { 18 | "id": "contact@enconn.fr" 19 | } 20 | }, 21 | "options_ui": { 22 | "page": "options/options.html" 23 | }, 24 | 25 | "content_scripts": [ { 26 | "matches": ["*://zestedesavoir.com/*"], 27 | "js": ["updateState.js"] 28 | }], 29 | 30 | "background": { 31 | "scripts": ["notifier.js"] 32 | }, 33 | 34 | "browser_action": { 35 | "default_icon": { 36 | "48": "icons/clem_48.png" 37 | }, 38 | "default_title": "Zds-Notificateur", 39 | "default_popup": "popup/notifier_popup.html" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Eskimon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Universal/content-script.js: -------------------------------------------------------------------------------- 1 | /* Variables de configuration (répétées depuis `config.js`) en l'absence de support des modules ES6 dans les extensions */ 2 | 3 | const DEBUG = false 4 | const BASE_URL = DEBUG ? 'https://beta.zestedesavoir.com' : 'https://zestedesavoir.com' 5 | 6 | /* On active le script si on est sur une page du site */ 7 | 8 | if (document.location.href.startsWith(BASE_URL)) { 9 | /* Sélecteurs pour détecter l'état de connexion et le nombre de notifications */ 10 | 11 | const SELECTOR_LOGBOX = '.logbox' 12 | const SELECTOR_COUNT_ALERTS = '.notifs-links>div:not(:nth-child(3)) ul.dropdown-list>li:not(.dropdown-empty-message)' 13 | 14 | /* Le script qui scanne la page */ 15 | 16 | let state = 'LOGGED_OUT' 17 | 18 | const logbox = document.querySelector(SELECTOR_LOGBOX) 19 | 20 | if (logbox) { // L'utilisateur est connecté 21 | state = 'LOGGED_IN' 22 | 23 | const countTotal = logbox.querySelectorAll(SELECTOR_COUNT_ALERTS).length 24 | 25 | if (countTotal > 0) { // Il a des notifications non lues 26 | state = 'PENDING_NOTIFICATIONS' 27 | } 28 | } 29 | 30 | /* On envoie l'info vers le script de fond (notifier) */ 31 | 32 | browser.runtime.sendMessage({ state: state }) 33 | .catch((err) => { 34 | console.error(err); 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /Firefox/popup/notifier.js: -------------------------------------------------------------------------------- 1 | /* global chrome close:true */ 2 | /* eslint no-undef: "error" */ 3 | const notificationsList = document.getElementById('notificationList') 4 | 5 | function updateUI () { 6 | if (chrome.extension.getBackgroundPage().connected) { 7 | const notConnectedDiv = document.getElementById('notConnected') 8 | notConnectedDiv.style.display = 'none' 9 | const bgNodes = chrome.extension.getBackgroundPage().currentDom.lastChild.cloneNode(true) 10 | notificationsList.appendChild(bgNodes) 11 | } else { 12 | const connectedDiv = document.getElementById('connected') 13 | connectedDiv.style.display = 'none' 14 | } 15 | 16 | chrome.storage.local.get('white_theme', (res) => { 17 | if (res.white_theme) document.body.classList.add('white') 18 | else document.body.classList.remove('white') 19 | }) 20 | } 21 | 22 | // sleep time expects milliseconds 23 | function sleep (time) { 24 | return new Promise((resolve) => setTimeout(resolve, time)) 25 | } 26 | 27 | notificationsList.addEventListener('click', function () { 28 | sleep(2000).then(() => { 29 | if (chrome.extension.getBackgroundPage().notifCounter !== 0) { 30 | close() 31 | } 32 | chrome.extension.getBackgroundPage().getNotificationsFromAPI() 33 | }) 34 | }) 35 | 36 | updateUI() 37 | -------------------------------------------------------------------------------- /Universal/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "ZdS-Notificateur", 4 | "version": "3.0", 5 | "description": "Une extension pour connaitre le nombre de notifications et de MP de Zeste de Savoir sans avoir besoin d'ouvrir le site", 6 | "homepage_url": "https://zestedesavoir.com", 7 | 8 | "icons": { 9 | "48": "images/clemoji-smile.svg", 10 | "128": "images/clemoji-smile.svg" 11 | }, 12 | 13 | "permissions": [ 14 | "https://*.zestedesavoir.com/*", "notifications", "storage", "activeTab" 15 | ], 16 | 17 | "options_ui": { 18 | "page": "options/options.html" 19 | }, 20 | 21 | "content_scripts": [ 22 | { 23 | "matches": [ 24 | "https://zestedesavoir.com/*", 25 | "https://*.zestedesavoir.com/*" 26 | ], 27 | "run_at": "document_end", 28 | "js": ["content-script.js"] 29 | } 30 | ], 31 | 32 | "web_accessible_resources": [ 33 | "update-state.js", 34 | "config.js" 35 | ], 36 | 37 | "background": { 38 | "page": "background-page.html" 39 | }, 40 | 41 | "browser_action": { 42 | "default_icon": { 43 | "48": "images/clemoji-smile.svg", 44 | "128": "images/clemoji-smile.svg" 45 | }, 46 | "default_title": "Zeste de Savoir", 47 | "default_popup": "popup/index.html" 48 | }, 49 | 50 | "browser_specific_settings": { 51 | "gecko": { 52 | "id": "browser-notifier@zestedesavoir.com" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Universal/date-format.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Formate une date au format relatif 3 | * @param {Date|string} pubdate 4 | */ 5 | export function relativeDate(pubdate) { 6 | const date = new Date((pubdate || '').replace(/-/g, '/').replace(/[TZ]/g, ' ')) 7 | const now = new Date() 8 | 9 | const diff = (now.valueOf() - date.valueOf()) / 1000 10 | 11 | if (diff < 0) { 12 | return 'Dans le futur' 13 | } 14 | if (diff < 60) { // Moins d'une minute 15 | return 'À l\'instant' 16 | } 17 | if (diff < 60 * 60) { // Moins d'une heure 18 | const minutes = Math.round(diff / 60) 19 | return `Il y a ${minutes} ${minutes > 1 ? 'minutes' : 'minute'}` 20 | } 21 | if (diff < 24 * 3600) { // Moins de 24 heures 22 | const hours = Math.round(diff / (3600)) 23 | return `Il y a ${hours} ${hours > 1 ? 'heures' : 'heure'}` 24 | } 25 | 26 | const yesterday = now 27 | yesterday.setDate(now.getDate() - 1) 28 | if (date.toDateString() === yesterday.toDateString()) { 29 | return 'Hier' 30 | } 31 | 32 | if (diff < 30 * 24 * 3600) { // Moins d'un mois 33 | const days = Math.round(diff / (24 * 3600)) 34 | return `Il y a ${days} ${days > 1 ? 'jours' : 'jour'}` 35 | } 36 | if (diff < 12 * 30 * 24 * 3600) { // Moins d'un an 37 | const months = Math.round(diff / (30 * 24 * 3600)) 38 | return `Il y a ${months} mois` 39 | } 40 | 41 | const years = Math.round(diff / (365 * 24 * 3600)) 42 | return `Il y a ${years} ${years > 1 ? 'ans' : 'an'}` 43 | } 44 | -------------------------------------------------------------------------------- /Firefox/updateState.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const debugMode = false 3 | 4 | const XPATH_to_test_connection = "count(//div[contains(concat(' ',normalize-space(@class),' '),' unlogged ') and contains(concat(' ',normalize-space(@class),' '),' logbox ')])" 5 | const XPATH_to_get_notif_block = "//div[contains(concat(' ',normalize-space(@class),' '),' notifs-links ')]/div" 6 | const XPATH_to_count_alert = "count(.//ul/li[not(contains(concat(' ', normalize-space(@class), ' '), ' dropdown-empty-message '))])" 7 | 8 | let state = 0; 9 | 10 | const connection = document.evaluate( XPATH_to_test_connection, document, null, XPathResult.ANY_TYPE, null ); 11 | 12 | if( connection.numberValue == 0) { // Logged 13 | state = 1; 14 | const notifLinks = document.evaluate( XPATH_to_get_notif_block, document, null, XPathResult.ANY_TYPE, null ); 15 | 16 | const pmDOM = (notifLinks.iterateNext()); 17 | const notifDOM = (notifLinks.iterateNext()); 18 | const alertDOM = (notifLinks.iterateNext()); 19 | 20 | const countPM = document.evaluate( XPATH_to_count_alert , pmDOM, null, XPathResult.ANY_TYPE, null ); 21 | const countNotif = document.evaluate( XPATH_to_count_alert, notifDOM, null, XPathResult.ANY_TYPE, null ); 22 | 23 | if( countPM.numberValue + countNotif.numberValue > 0 ) { // 1 notif or more 24 | state = 2; 25 | } 26 | } 27 | 28 | // Send the state to background script 29 | var sending = browser.runtime.sendMessage({ state: state }); 30 | sending.then(null, null); 31 | -------------------------------------------------------------------------------- /Google Chrome/injected.js: -------------------------------------------------------------------------------- 1 | /* PLUS NECESSAIRE MAINTENANT QUE L'ON SAIT ARCHIVER ! 2 | 3 | var url = document.URL; 4 | if((url.indexOf("/membres/") == -1) && (url.indexOf("#badges") == -1)) //si on est pas sur la page pour les badges 5 | url = url.slice(0,url.indexOf("?")) + "/" + url.slice(url.lastIndexOf("-")+1); 6 | var container = document.getElementById("lastNotifications"); //on restreint juste aux notifications 7 | var els = container.getElementsByTagName("a"); 8 | var len = els.length; 9 | var target = false; 10 | for (var i = 0; i < len; i++) { 11 | var el = els[i]; 12 | if (el.href === url) { 13 | target = els[i+1]; 14 | break; 15 | } 16 | } 17 | target && target.click(); 18 | */ 19 | /* 20 | // Je suspecte ce bout de code de faire remonter la page tout en haut plutot que rester sur le lien qui va bien 21 | // Update du nombre de notifs 22 | var elem = document.getElementById("notifications"); 23 | elem.click(); 24 | elem.click(); 25 | */ 26 | 27 | // KONAMI! 28 | var keys = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65], 29 | current = 0; 30 | 31 | document.addEventListener("keydown", function(e) { 32 | if(e.which == keys[current]) { 33 | current++; 34 | } 35 | else { 36 | current = 0; 37 | } 38 | 39 | if(current >= keys.length) { 40 | setInterval(function() { 41 | var i = document.createElement("img"), 42 | h = window.innerHeight, 43 | w = window.innerWidth; 44 | i.src = "http://www.siteduzero.com/uploads/fr/ftp/iphone/zozor.png"; 45 | i.style.position = "fixed"; 46 | i.style.top = (Math.floor(Math.random() * (h - 480))) + "px"; 47 | i.style.right = (Math.floor(Math.random() * (w - 320))) + "px"; 48 | i.style.zIndex = 2000; 49 | document.body.appendChild(i); 50 | console.log("Konami"); 51 | }, 100); 52 | current = 0; 53 | } 54 | }, false); 55 | -------------------------------------------------------------------------------- /Google Chrome/welcome/welcome.css: -------------------------------------------------------------------------------- 1 | html, body, h1, h2, h3, h4, h5, h6, p { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | * { 7 | -webkit-box-sizing: border-box; 8 | } 9 | 10 | body { 11 | background: #ECECEC; 12 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 13 | color: #333; 14 | } 15 | 16 | a { 17 | text-decoration: none; 18 | color: #084561; 19 | } 20 | 21 | a:hover { 22 | text-decoration: underline; 23 | } 24 | 25 | .clearfix:after { 26 | clear: both; 27 | height: 1px; 28 | width: 1px; 29 | content: " "; 30 | display: block; 31 | } 32 | 33 | .container { 34 | width: 800px; 35 | margin: auto; 36 | } 37 | 38 | header { 39 | padding: 40px 0; 40 | } 41 | 42 | .logo { 43 | width: 128px; 44 | margin: auto; 45 | } 46 | 47 | header h1 { 48 | line-height: 40px; 49 | font-size: 2em; 50 | padding: 82px 0 6px 142px; 51 | color: rgb(9,33,49); 52 | } 53 | 54 | section { 55 | margin: 20px; 56 | font-size: 1.2em; 57 | line-height: 1.4em; 58 | } 59 | 60 | .github-btns { 61 | margin-top: 30px; 62 | } 63 | 64 | .github-btns .wrapper { 65 | width: 50%; 66 | float: left; 67 | } 68 | 69 | .github-btns .wrapper .button { 70 | margin: auto; 71 | width: 80px; 72 | } 73 | 74 | .links { 75 | border-top: solid 1px #E3E3E3; 76 | border-bottom: solid 1px #E3E3E3; 77 | height: 62px; 78 | line-height: 20px; 79 | background-color: #f8f8f4; 80 | padding: 20px; 81 | color: #707070; 82 | text-align: center; 83 | } 84 | 85 | .links a { 86 | } 87 | 88 | .authors { 89 | } 90 | 91 | .authors .author { 92 | width: 45%; 93 | margin-top: 40px; 94 | margin-left: 10%; 95 | padding: 6px; 96 | background-color: white; 97 | border-bottom: solid 2px #4D4D4D; 98 | } 99 | 100 | .authors .author:nth-child(2n+1) { 101 | float: left; 102 | margin-left: 0; 103 | } 104 | 105 | .authors .author:nth-child(2n) { 106 | float: right; 107 | } 108 | 109 | .avatar { 110 | height: 96px; 111 | width: 96px; 112 | background-position: center; 113 | background-size: contain; 114 | background-repeat: no-repeat; 115 | } 116 | 117 | .author .avatar { 118 | float: left; 119 | margin: 5px; 120 | } 121 | 122 | .author .avatar img { 123 | padding: 2px; 124 | } 125 | 126 | .author .text { 127 | margin: 5px; 128 | padding-left: 116px; 129 | font-size: 0.9em; 130 | color: #454545; 131 | } 132 | 133 | .author .text h3 { 134 | color: #222; 135 | margin: 2px 0; 136 | } 137 | -------------------------------------------------------------------------------- /Universal/images/clemoji-smile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Google Chrome/popup/popup.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | body { 7 | font-family: "Source Sans Pro","Segoe UI","Trebuchet MS",Helvetica,"Helvetica Neue",Arial,sans-serif; 8 | font-size: 14px; 9 | line-height: 1.7em; 10 | min-width: 25em; 11 | max-width: 30em; 12 | min-height: 7em; 13 | background-color: #f7f7f7; 14 | color: #222; 15 | } 16 | 17 | a { 18 | text-decoration: none; 19 | color: inherit; 20 | transition-duration: .15s; 21 | outline-color: #f8ad32; 22 | } 23 | 24 | #header{ 25 | text-align: center; 26 | background-color: #084561; 27 | color: #fff; 28 | border-bottom: 3px solid #f8ad32; 29 | } 30 | #header>h1{ 31 | /*font-size: 1.25em;*/ 32 | font-size: 1em; 33 | font-weight: normal; 34 | text-transform: small-caps; 35 | padding: 0; 36 | margin: 0; 37 | } 38 | #logo{ 39 | display: block; 40 | padding: 1.25em; 41 | background: url(../icons/logo-zds.png) no-repeat center; 42 | background-size: contain; 43 | } 44 | #logo:hover{ 45 | opacity: .7; 46 | } 47 | #logo>span{ 48 | font: 0/0 a; 49 | text-shadow: none; 50 | color: transparent; 51 | } 52 | 53 | 54 | .notification { 55 | display: block; 56 | padding: .5em; 57 | vertical-align: middle; 58 | } 59 | .notification::after{ 60 | content: ''; 61 | display: block; 62 | clear: both; 63 | } 64 | .notification:nth-child(2n){ 65 | background-color: #f0f0f0; 66 | } 67 | .notification:hover{ 68 | background-color: #fff; 69 | } 70 | .notification:focus{ 71 | outline: none; 72 | box-shadow: inset 0 0 .5em #c4c4c4; 73 | } 74 | .notification .user-avatar { 75 | float: left; 76 | height: 4em; 77 | width: 4em; 78 | } 79 | .notification .user-name { 80 | display: block; 81 | float: left; 82 | max-width: 50%; 83 | color: #777; 84 | overflow: hidden; 85 | text-overflow: ellipsis; 86 | white-space: nowrap; 87 | } 88 | .notification .details { 89 | position: relative; 90 | padding-left: 4.5em; 91 | } 92 | .notification .date { 93 | display: block; 94 | float: right; 95 | max-width: 50%; 96 | color: #ee8709; 97 | transition-property: color; 98 | } 99 | .notification .title { 100 | display: block; 101 | font-size: 1.2em; 102 | padding: .5em 0 0; 103 | width: 100%; 104 | color: #0e7aa8; 105 | overflow: hidden; 106 | text-overflow: ellipsis; 107 | white-space: nowrap; 108 | } 109 | 110 | 111 | .noNotifs, .notConnected { 112 | padding: 1em .5em; 113 | text-align: center; 114 | } 115 | 116 | 117 | .allNotifs{ 118 | display: block; 119 | padding: 1em; 120 | text-align: center; 121 | background-color: #084561; 122 | color: #fff; 123 | border-top: 3px solid #f8ad32; 124 | } 125 | .allNotifs:hover, .allNotifs:focus{ 126 | outline: none; 127 | background-color: #396a81; 128 | } -------------------------------------------------------------------------------- /Universal/popup/elements/popup.js: -------------------------------------------------------------------------------- 1 | import { BASE_URL } from '../../config.js' 2 | import { relativeDate } from '../../date-format.js' 3 | 4 | import { ZdsElement } from './_element.js' 5 | 6 | /** 7 | * WebComponent permettant d'afficher la popup de notifications 8 | * @property {string} userState - L'état de l'utilisateur 9 | * @property {ZdsNotification[]} notifications - Les notifications non lues 10 | */ 11 | export class Popup extends ZdsElement { 12 | get styles () { 13 | return ` 14 | :host { 15 | background-color: var(--color-bg); 16 | } 17 | ` 18 | } 19 | 20 | get template() { 21 | return this.userState === 'LOGGED_OUT' 22 | ? ` 23 |
24 |

Oh-oh !

25 |
26 |
On dirait que vous êtes déconnecté·e
27 |
28 | Connexion` 29 | : ` 30 | ${this.notifications.length 31 | ? `` 43 | : `
44 |

Bravo !

45 |
46 |
Aucune notification non lue
47 |
` 48 | } 49 |
50 | 51 | Toutes les notifications 52 | 53 | 54 | Nouveau message privé 55 | 56 |
` 57 | } 58 | 59 | constructor() { 60 | super() 61 | this.userState = 'LOGGED_OUT' 62 | this.notifications = [] 63 | 64 | browser.runtime.sendMessage({ state: 'LOGGED_IN' }) 65 | 66 | browser.storage.local.get({ 67 | userState: 'LOGGED_OUT', 68 | notifications: [] 69 | }).then((data) => { 70 | this.userState = data.userState 71 | this.notifications = data.notifications 72 | 73 | this.render() 74 | }) 75 | 76 | browser.storage.onChanged.addListener((changes, areaName) => { 77 | if (areaName !== 'local') { 78 | return 79 | } 80 | if (changes.userState) { 81 | this.userState = changes.userState.newValue 82 | } 83 | if (changes.notifications) { 84 | this.notifications = changes.notifications.newValue 85 | } 86 | 87 | this.render() 88 | }) 89 | } 90 | 91 | } 92 | window.customElements.define('zds-popup', Popup) 93 | -------------------------------------------------------------------------------- /Google Chrome/common.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Prototypes 4 | */ 5 | Object.prototype.extend = function(obj) { 6 | for(i in obj) { 7 | if (typeof obj[i] === "object") { 8 | if (!this[i]) { 9 | this[i] = {}; 10 | } 11 | this[i].extend(obj[i]); 12 | } 13 | else { 14 | this[i] = obj[i]; 15 | } 16 | } 17 | }; 18 | 19 | 20 | 21 | 22 | 23 | var AjaxRequest = function() { 24 | if (typeof arguments[0] == 'string') { 25 | this.url = arguments[0]; 26 | } 27 | else { 28 | this.url = arguments[0].url; 29 | } 30 | 31 | if (arguments[1] && typeof arguments[1] == 'string') { 32 | this.postData = arguments[1]; 33 | } 34 | else if (arguments[1] && typeof arguments[1] == 'object') { 35 | this.postData = arguments[1]; 36 | } 37 | else { 38 | this.postData = arguments[0].data || null; 39 | } 40 | 41 | if (this.postData && arguments[2] && typeof arguments[2] == 'function') { 42 | this.successCallback = arguments[2]; 43 | } 44 | else if (!this.postData && arguments[1] && typeof arguments[1] == 'function') { 45 | this.successCallback = arguments[1]; 46 | } 47 | else { 48 | this.successCallback = arguments[0].success || null; 49 | } 50 | 51 | if (this.postData && typeof arguments[3] == 'function') { 52 | this.errorCallback = arguments[3]; 53 | } 54 | else if (!this.postData && arguments[2] && typeof arguments[2] == 'function') { 55 | this.errorCallback = arguments[2]; 56 | } 57 | else { 58 | this.errorCallback = arguments[0].error || null; 59 | } 60 | 61 | if (this.postData && typeof arguments[4] == 'boolean' || arguments[0].cache != undefined) { 62 | this.date = new Date(); 63 | this.timestamp = this.date.getTime(); 64 | this.cache = arguments[4] == 'boolean' || arguments[0].cache; 65 | } 66 | else if (!this.postData && typeof arguments[3] == 'boolean' || arguments[0].cache != undefined) { 67 | this.date = new Date(); 68 | this.timestamp = this.date.getTime(); 69 | this.cache = arguments[3] == 'boolean' || arguments[0].cache; 70 | } 71 | else { 72 | this.cache = true; 73 | } 74 | 75 | var XHR = new XMLHttpRequest(); 76 | 77 | XHR.onreadystatechange = function (e) { 78 | if (this.readyState == 4 && this.status == 200) { 79 | if(this._successCallback) 80 | this._successCallback(e); 81 | } 82 | else if (this.readyState == 4) { 83 | console.dir(this); 84 | if(this._errorCallback) 85 | this._errorCallback(e); 86 | } 87 | } 88 | 89 | XHR._successCallback = this.successCallback || null; 90 | XHR._errorCallback = this.errorCallback || null; 91 | 92 | 93 | if (this.postData) { 94 | XHR.open('POST', this.url+(this.cache ? '' : '?timestamp='+this.timestamp), true); 95 | XHR.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 96 | XHR.setRequestHeader('Content-type','application/x-www-form-urlencoded'); 97 | XHR.send(typeof this.postData == 'string' ? this.postData : objectToQueryString(this.postData)); 98 | } 99 | else { 100 | XHR.open('GET', this.url+(this.cache ? '' : '?timestamp='+this.timestamp), true); 101 | XHR.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 102 | XHR.send(); 103 | } 104 | 105 | return this; 106 | } -------------------------------------------------------------------------------- /Universal/images/clemoji-party.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Universal/images/clemoji-moneymouth.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Firefox/popup/notifier_popup.css: -------------------------------------------------------------------------------- 1 | body 2 | { 3 | margin: 0; 4 | padding: 0; 5 | color: white; 6 | background-color: #0c4863; 7 | font-family: 'Source Sans Pro', Fallback, sans-serif; 8 | } 9 | 10 | @font-face { 11 | font-family: "Source Sans Pro"; 12 | font-style: normal; 13 | src: local("Source Sans Pro"), url('fonts/source-sans-pro.woff2') format('woff2'); 14 | } 15 | 16 | #notconnected 17 | { 18 | margin-top: 20px; 19 | text-align: center; 20 | } 21 | 22 | #home_clem img 23 | { 24 | width: 20%; 25 | margin-bottom: 10px; 26 | } 27 | 28 | #payload 29 | { 30 | margin: 0 20px 0 20px; 31 | } 32 | 33 | a 34 | { 35 | text-decoration: none; 36 | color: white; 37 | } 38 | 39 | #notconnected #button 40 | { 41 | margin-top: 20px; 42 | padding: 20px 0 20px 0; 43 | background: #154e69; 44 | border-top: 0; 45 | border-bottom: 3px solid #f8ad32; 46 | } 47 | 48 | #notconnected #button:hover 49 | { 50 | background: #396a81; 51 | } 52 | 53 | #connected #button 54 | { 55 | padding: 5px 0 5px 0; 56 | background: #154e69; 57 | text-align:center; 58 | font-size: 13px; 59 | border-top: 0; 60 | border-bottom: 3px solid #f8ad32; 61 | } 62 | 63 | .white #connected #button 64 | { 65 | padding: 10px 0 10px 0; 66 | font-weight: bold; 67 | border-top: 3px solid #f8ad32; 68 | border-bottom: 0; 69 | 70 | } 71 | 72 | #connected #button:hover, #connected #notification:hover 73 | { 74 | background: #396a81; 75 | } 76 | 77 | .white #connected #notification:hover 78 | { 79 | background: #fdebce; 80 | } 81 | 82 | #notification 83 | { 84 | font-size:13px; 85 | height: 50px; 86 | width: 385px; 87 | border-bottom: #154e69 1px solid; 88 | } 89 | 90 | .white #notification 91 | { 92 | border-bottom: #f8ad32 1px solid; 93 | } 94 | 95 | #notification img 96 | { 97 | height: 50px; 98 | width: 50px; 99 | } 100 | 101 | #notification #blocNotif 102 | { 103 | float:right; 104 | width: 335px; 105 | height: 50px; 106 | overflow: hidden; 107 | text-overflow: ellipsis; 108 | } 109 | 110 | #notification #pseudo 111 | { 112 | width:40%; 113 | max-height: 18px; 114 | overflow-y: hidden; 115 | text-overflow: ellipsis; 116 | padding-left: 5px; 117 | padding-top: 3px; 118 | color: #77b8d5; 119 | } 120 | 121 | .white #notification #pseudo 122 | { 123 | color: #666; 124 | } 125 | 126 | #notification #date 127 | { 128 | width:40%; 129 | max-height: 13px; 130 | float:right; 131 | text-align:right; 132 | padding-top: 3px; 133 | padding-right:20px; 134 | color: #77b8d5; 135 | } 136 | 137 | .white #notification #date 138 | { 139 | color: #f8ad32; 140 | } 141 | 142 | #notification #title 143 | { 144 | margin-top: 5px; 145 | padding-left: 5px; 146 | } 147 | 148 | .white #notification #title 149 | { 150 | color: #0c4863; 151 | } 152 | 153 | #header 154 | { 155 | text-align:center; 156 | background: #154e69; 157 | } 158 | 159 | #notificationList 160 | { 161 | max-height: 300px; 162 | overflow-y: auto; 163 | overflow-x: hidden; 164 | } 165 | 166 | .white #notificationList 167 | { 168 | color: black; 169 | border-top: 3px solid #f8ad32; 170 | background-color: #fcfcfc; 171 | } 172 | 173 | #noNotif 174 | { 175 | font-size: 12px; 176 | text-align: center; 177 | padding: 10px; 178 | } 179 | 180 | .pm-new 181 | { 182 | float: right; 183 | margin-top: -23px; 184 | margin-right: 5px; 185 | margin-left: -28px; 186 | } 187 | 188 | .white .pm-new 189 | { 190 | margin-top: -28px; 191 | } 192 | -------------------------------------------------------------------------------- /Google Chrome/welcome/welcome.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Merci d'avoir installé le ZdS Notificateur! 6 | 7 | 8 | 9 |
10 |
11 | 14 |

Merci d'avoir installé le ZdS Notificateur!

15 |
16 |
17 | 18 |
19 |
20 |

21 | Cette extension vous permet d'être averti lorsque vous recevez un nouveau message ou une nouvelle notification sur Zeste de Savoir. 22 |

23 |

24 | Elle récupère à intervalle régulier les dernières notifications sur ZesteDeSavoir. 25 |

26 |

27 | De nombreux paramètres sont réglables sur la page des options. 28 |

29 | 30 |
31 |
32 |
33 | 34 |
35 |
36 |
37 |
38 | 39 |
40 |
41 |
42 |
43 |
44 | 45 | 52 | 53 |
54 |
55 |
56 |
57 |
58 |

Eskimon

59 |

Codeur de la première heure, Eskimon est à l'origine de l'idée.

60 |

Twitter - Profil ZdS

61 |
62 |
63 |
64 |
65 |
66 |

Sandhose

67 |

Horrifié par le code d'Eskimon, Sandhose a décidé de filer un coup de patte à Eskimon.

68 |

Twitter - Profil ZdS

69 |
70 |
71 |
72 |
73 |
74 |

viki53

75 |

Après une bose dose d'agrumes, il fallait utiliser toute cette énergie pour une bonne cause !

76 |

Twitter - Profil ZdS

77 |
78 |
79 |
80 |
81 | 82 | 83 | -------------------------------------------------------------------------------- /Universal/notifier.js: -------------------------------------------------------------------------------- 1 | import { BASE_URL, BADGE_COLOR_DEFAULT, BADGE_COLOR_ERROR, BADGE_COLOR_OK, POLLING_RATE } from './config.js' 2 | 3 | /** 4 | * Classe tournant en fond permettant de gérer l'icône et le badge ainsi que de mettre à jour les notifications 5 | * @property {string} userState - L'état de l'utilisateur 6 | * @property {ZdsNotification[]} notifications - Les notifications non lues 7 | */ 8 | class Notifier { 9 | constructor() { 10 | this.userState = 'LOGGED_OUT' 11 | this.notifications = [] 12 | this.pollingTimer = null 13 | 14 | browser.runtime.onMessage.addListener((msg) => { 15 | if (msg.state !== undefined) { 16 | return this.updateState(msg.state) 17 | } 18 | }) 19 | } 20 | 21 | /** 22 | * Met à jour l'état de connexion 23 | * @param {number} state - État à afficher pour le badge 24 | * @param {boolean} stopApiUpdate - Empêche de relancer un appel vers l'API 25 | */ 26 | updateState(state, stopApiUpdate) { 27 | if (state !== this.userState) { // If any difference update the state 28 | switch(state) { 29 | case 'ERROR': 30 | chrome.browserAction.setIcon({path: 'images/clemoji-dizzy.svg'}) 31 | break 32 | case 'LOGGED_OUT': 33 | chrome.browserAction.setIcon({path: 'images/clemoji-asleep.svg'}) 34 | break 35 | case 'LOGGED_IN': 36 | chrome.browserAction.setIcon({path: 'images/clemoji-smile.svg'}) 37 | break 38 | case 'PENDING_NOTIFICATIONS': 39 | chrome.browserAction.setIcon({path: 'images/clemoji-moneymouth.svg'}) 40 | break 41 | } 42 | 43 | this.userState = state; 44 | browser.storage.local.set({ userState: this.userState, notifications: this.notifications }) 45 | } 46 | 47 | if (!stopApiUpdate && !['LOGGED_OUT', 'ERROR'].includes(state)) { 48 | if (this.pollingTimer) { 49 | clearTimeout(this.pollingTimer) 50 | } 51 | 52 | this.pollingTimer = setTimeout(() => { 53 | this.updateState(this.userState) 54 | this.pollingTimer = null 55 | }, POLLING_RATE * 1000) 56 | 57 | return this.getNotificationsFromAPI() 58 | } 59 | } 60 | 61 | /** 62 | * Rafraîchit la liste des notifications depuis l'API et met à jour le badge sur l'icône 63 | */ 64 | getNotificationsFromAPI() { 65 | const options = `page_size=100&ordering=-pubdate` 66 | 67 | try { 68 | browser.browserAction.setBadgeText({ text: '…' }) 69 | browser.browserAction.setBadgeBackgroundColor({ color: BADGE_COLOR_OK }) 70 | } 71 | catch (err) { 72 | console.error(err) 73 | } 74 | 75 | return fetch(`${BASE_URL}/api/notifications/?${options}`) 76 | .then(res => res.json()) 77 | .then((res) => { 78 | this.notifications = ((res || {}).results || []).filter(notif => !notif.is_read) 79 | console.info(res.results.length) 80 | console.dir(this.notifications) 81 | browser.storage.local.set({ userState: this.userState, notifications: this.notifications }) 82 | 83 | /* On met à jour le badge sans recharger l'API */ 84 | 85 | if (this.notifications.length) { 86 | browser.browserAction.setBadgeText({ text: (this.notifications.length >= 100 ? '99+' : `${this.notifications.length}`) }) 87 | browser.browserAction.setBadgeBackgroundColor({ color: BADGE_COLOR_DEFAULT }) 88 | 89 | this.updateState('PENDING_NOTIFICATIONS', true) 90 | } 91 | else { 92 | browser.browserAction.setBadgeText({ text: '' }) 93 | this.updateState('LOGGED_IN', true) 94 | } 95 | }) 96 | .catch((err) => { 97 | console.error(err) 98 | this.updateState('ERROR', true) 99 | browser.browserAction.setBadgeText({ text: 'ERR' }) 100 | browser.browserAction.setBadgeBackgroundColor({ color: BADGE_COLOR_ERROR }) 101 | }) 102 | } 103 | } 104 | 105 | const NOTIFIER = new Notifier() 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ZdS_Notificateur 2 | ================ 3 | 4 | 1. [Keskecé ?](#keskec%C3%A9-) 5 | 2. [A quoi ça ressemble ?](#a-quoi-%C3%A7a-ressemble-) 6 | 3. [J'peux vous aider ?](#jpeux-vous-aider-) 7 | 4. [C'est qui qu'a fait ça ?](#cest-qui-qua-fait-%C3%A7a-) 8 | 5. [Licence](license) 9 | 10 | ## Keskecé ? 11 | 12 | C'est une extension pour Google Chrome et Firefox, qui vous avertit des nouvelles notifications sur [Zeste de Savoir](http://zestedesavoir.com). 13 | Elle récupère à intervalle régulier (réglable) vos dernières notifications, et vous les affiche directement. 14 | 15 | L'extension est disponible sur le [Chrome WebStore](https://chrome.google.com/webstore/detail/zds-notificateur/jibjnbbmokappnjpdodmpdmpklfhokkn) ainsi que sur le [AMO de Mozilla](https://addons.mozilla.org/fr/firefox/addon/zds-notificateur/) 16 | 17 | ## À quoi ça ressemble ? 18 | 19 | *L'icône dans la barre d'adresse, et une popup avec la liste des notifications* 20 | 21 | ![](Images/notificateur.png) 22 | 23 | ![](Images/popup.png) 24 | 25 | 26 | *Une notification de bureau* 27 | 28 | ![](Images/notifbureau.png) 29 | 30 | 31 | *La page des options* 32 | 33 | ![](Images/options.png) 34 | 35 | 36 | ## J'peux vous aider ? 37 | 38 | Toute aide est la bienvenue ! Pour cela, faites un fork du repo, et une fois que vous avez rajouté ce que vous voulez, on prendra en compte toute *Pull request* ;) 39 | 40 | ### Google Chrome 41 | 42 | Pour tester l'extension depuis les sources, téléchargez les sources [ici](https://github.com/zestedesavoir/extensions-notificateurs/archive/master.zip), et glissez/déposez le dossier "Google Chrome" du zip dans la page [chrome://extensions](chrome://extension) (nécessite d'avoir activé le *mode développeur* ) 43 | 44 | ### Firefox 45 | 46 | Pour tester l'extension depuis les sources, clonez ce dépot, mettez dans un .zip le contenu du dossier Firefox, puis modifiez l'extension en .xpi. Enfin, glissez-déposez le fichier .xpi dans la page [about:addons](about:addons). 47 | 48 | ## C'est qui qu'a fait ça ? 49 | 50 | Eskimon, est à l'origine de l'idée et Sandhose, horrifié par le code, mais séduit par l'idée, l'a rejoint par après. viki53 a ensuite ajouté sa patte pour virer les spaghetti de jQuery. 51 | (Pour sa défense, Eskimon déclare avoir fait cette extension pour justement apprendre le javascript. Il remercie Sandhose de sa patience :D ) 52 | 53 | Piwit est ensuite venu apporter sa touche graphique pour proposer les différentes illustrations inspirées des graphismes originaux de Zeste De Savoir. 54 | 55 | AmarOk a réalisé l'extension Firefox. Mais n'étant pas développeur Web, le code horrifiera d'autres personnes. 56 | 57 | ## License 58 | 59 | The MIT License (MIT) 60 | 61 | Copyright (c) 2013 Eskimon 62 | 63 | Permission is hereby granted, free of charge, to any person obtaining a copy of 64 | this software and associated documentation files (the "Software"), to deal in 65 | the Software without restriction, including without limitation the rights to 66 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 67 | the Software, and to permit persons to whom the Software is furnished to do so, 68 | subject to the following conditions: 69 | 70 | The above copyright notice and this permission notice shall be included in all 71 | copies or substantial portions of the Software. 72 | 73 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 74 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 75 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 76 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 77 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 78 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 79 | 80 | ------ 81 | 82 | ![](Images/Promo.png) 83 | -------------------------------------------------------------------------------- /Google Chrome/options/options.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | body, html, .content { 7 | height: 100%; 8 | } 9 | 10 | body { 11 | background: #F7F7F7; 12 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 13 | } 14 | 15 | .pull-right { 16 | float: right; 17 | } 18 | 19 | .pull-left { 20 | float: left; 21 | } 22 | 23 | .clear { 24 | clear: both; 25 | height: 0!important; 26 | } 27 | 28 | header { 29 | background: #084561; 30 | border-bottom: 3px solid #F8AD32; 31 | color: white; 32 | padding: 30px 0; 33 | /* La belle ombre :D */ 34 | /*box-shadow: 0 0 14px rgba(0, 0, 0, 0.7); */ 35 | position:relative; 36 | z-index:1; 37 | } 38 | 39 | .container { 40 | margin: auto; 41 | width: 600px; 42 | } 43 | 44 | .champ { 45 | padding: 5px 0; 46 | -webkit-transition: color .1s ease-in-out; 47 | } 48 | 49 | .disabled .champ { 50 | color: #CCC; 51 | } 52 | 53 | .options { 54 | background-color: white; 55 | padding: 20px; 56 | /*box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);*/ 57 | border-bottom: solid 3px #4D4D4D; 58 | margin : 20px 0; 59 | } 60 | 61 | body > .content { 62 | min-height: 100%; 63 | height: auto; 64 | } 65 | 66 | .form-buttons { 67 | float: right; 68 | } 69 | 70 | input[type="button"] { 71 | color: #DDD; 72 | padding: 0 15px; 73 | border: none; 74 | float: right; 75 | text-decoration: none; 76 | margin-left: 1px; 77 | outline: 0; 78 | height: 40px; 79 | line-height: 40px; 80 | cursor: pointer; 81 | } 82 | 83 | input[type="button"]:hover { 84 | -webkit-transition: background-color .2s; 85 | opacity: 0.95; 86 | } 87 | 88 | #reset { 89 | background: #c0392b; 90 | } 91 | 92 | #enregistrer { 93 | background: #396A81; 94 | } 95 | 96 | input[type="number"] { 97 | border: solid 1px #CCC; 98 | background-color: white; 99 | /*display: block;*/ 100 | padding: 2px 5px; 101 | margin: 2px 0; 102 | box-shadow: inset 0 1px 2px #EEE; 103 | } 104 | 105 | input[type="number"]:focus { 106 | outline: none; 107 | border-color: #6bb4bf; 108 | } 109 | 110 | input[type="checkbox"] { 111 | margin: 4px 4px 0 0; 112 | float: left; 113 | } 114 | 115 | .complement { 116 | color: #999; 117 | font-size: 10px; 118 | display: block; 119 | } 120 | 121 | .sub { 122 | border-left: solid 4px #ddd; 123 | padding: 1px 10px; 124 | margin-bottom: 10px; 125 | } 126 | 127 | .hidden { 128 | display: none; 129 | } 130 | 131 | .experimental { 132 | color: #df3716; 133 | font-weight: bold; 134 | -webkit-transition: color .1s ease-in-out; 135 | } 136 | 137 | .disabled .experimental { 138 | color: #e09f92; 139 | } 140 | 141 | .priority > div { 142 | height: 32px; 143 | } 144 | 145 | .priority .value { 146 | line-height: 32px; 147 | font-size: 11px; 148 | float: right; 149 | width: 100px; 150 | } 151 | 152 | .priority .pull-right { 153 | width: 240px; 154 | height: 32px; 155 | } 156 | 157 | .priority input[type="range"] { 158 | height: 32px; 159 | width: 130px; 160 | } 161 | 162 | footer { 163 | background-color: #f8f8f4; 164 | padding: 15px 0; 165 | font-weight: bold; 166 | color: #454545; 167 | font-size: 12px; 168 | text-align: right; 169 | border-top: solid 2px #D4D4D4; 170 | position: relative; 171 | height: 45px; 172 | margin-top: -76px; 173 | clear: both; 174 | } 175 | 176 | footer a { 177 | text-decoration: none; 178 | color: #03638b; 179 | } 180 | 181 | footer a:hover { 182 | color: #065070; 183 | } 184 | 185 | footer p { 186 | margin: 5px 10px; 187 | } 188 | 189 | .footerSubstitutor { 190 | height: 96px; 191 | width: 100%; 192 | } 193 | 194 | #connecteComme { 195 | margin-bottom: 0; 196 | } 197 | 198 | #connecteComme a { 199 | text-decoration: none; 200 | color: black; 201 | display: block; 202 | } 203 | 204 | #connecteComme img { 205 | margin-top: 10px; 206 | display: block; 207 | margin-left: auto; 208 | margin-right: auto; 209 | padding:8px; 210 | border:solid; 211 | border-color: #dddddd #aaaaaa #aaaaaa #dddddd; 212 | border-width: 1px 2px 2px 1px; 213 | background-color:white; 214 | } -------------------------------------------------------------------------------- /Universal/images/clemoji-dizzy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Universal/images/clemoji-asleep.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Universal/popup/popup.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-bg: #0b5a7f; 3 | --color-logo: #084663; 4 | --color-primary: #0d6791; 5 | --color-hover: #396a81; 6 | --color-notif: #003753; 7 | --color-notif-alternate: #00496f; 8 | --color-notif-hover: #063d56; 9 | --color-dark: #063d56; 10 | --color-orange: #f8ab30; 11 | --color-blue-light: #95d7f5; 12 | --color-black: #222; 13 | --color-white: #fff; 14 | } 15 | 16 | @font-face { 17 | font-family: "Source Sans Pro"; 18 | font-style: normal; 19 | src: local("Source Sans Pro"), url('./fonts/source-sans-pro.woff2') format('woff2'); 20 | } 21 | 22 | body { 23 | font-family: Source Sans Pro, Segoe UI, Trebuchet MS, Helvetica, Helvetica Neue, Arial, sans-serif; 24 | margin: 0; 25 | padding: 0; 26 | min-width: 20rem; 27 | max-width: 34rem; 28 | background-color: var(--color-logo); 29 | color: var(--color-white); 30 | border-bottom: 3px solid var(--color-orange); 31 | } 32 | 33 | *, 34 | *::after, 35 | *::before { 36 | box-sizing: border-box; 37 | font-family: inherit; 38 | } 39 | 40 | img { 41 | max-width: 100%; 42 | } 43 | 44 | .sr-only:not(:focus):not(:active) { 45 | clip: rect(0 0 0 0); 46 | clip-path: inset(100%); 47 | height: 1px; 48 | overflow: hidden; 49 | position: absolute; 50 | white-space: nowrap; 51 | width: 1px; 52 | } 53 | 54 | /** 55 | * Annonces 56 | */ 57 | 58 | .alert { 59 | padding: .5rem; 60 | text-align: center; 61 | background-color: var(--color-alternate); 62 | color: var(--color-white); 63 | } 64 | .alert .title { 65 | margin: 1rem; 66 | font-style: italic; 67 | } 68 | .alert .icon { 69 | margin: .5rem; 70 | } 71 | .alert .icon img { 72 | width: 2rem; 73 | } 74 | .alert .message { 75 | margin: .5rem; 76 | } 77 | 78 | /** 79 | * Page connecté 80 | */ 81 | 82 | #notifications-empty { 83 | display: block; 84 | padding: 1rem; 85 | text-align: center; 86 | font-style: italic; 87 | color: rgba(255, 255, 255, .5); 88 | } 89 | #notifications-empty::before { 90 | display: inline-block; 91 | width: 1.25rem; 92 | height: 1.25rem; 93 | margin-right: .5rem; 94 | vertical-align: top; 95 | content: ' '; 96 | background: center / contain no-repeat url('../images/clemoji-party.svg'); 97 | } 98 | #notifications-empty { 99 | content: 'Aucune notification'; 100 | } 101 | 102 | #notifications-list { 103 | list-style: none; 104 | margin: 0; 105 | padding: 0; 106 | } 107 | #notifications-list li { 108 | display: block; 109 | margin: 0; 110 | padding: 0; 111 | } 112 | #notifications-list li a { 113 | display: block; 114 | overflow: hidden; 115 | background-color: var(--color-notif); 116 | color: var(--color-white); 117 | text-decoration: none; 118 | } 119 | #notifications-list li:nth-child(2n) a { 120 | background-color: var(--color-notif-alternate); 121 | } 122 | #notifications-list li a header { 123 | color: var(--color-blue-light); 124 | display: flex; 125 | } 126 | #notifications-list li a header .avatar { 127 | width: 2em; 128 | height: 2em; 129 | background-color: var(--color-white); 130 | } 131 | #notifications-list li a header .username { 132 | padding: .5em; 133 | line-height: 1em; 134 | height: 2em; 135 | flex: 1; 136 | } 137 | #notifications-list li a header .date { 138 | padding: .5em; 139 | height: 2em; 140 | } 141 | #notifications-list li a .topic { 142 | display: block; 143 | padding: .5em; 144 | } 145 | #notifications-list li a header .username, 146 | #notifications-list li a .topic { 147 | overflow: hidden; 148 | white-space: nowrap; 149 | text-overflow: ellipsis; 150 | } 151 | #notifications-list li a:is(:hover, :focus) { 152 | background-color: var(--color-notif-hover); 153 | } 154 | #notifications-list li a:is(:hover, :focus) header .date { 155 | color: var(--color-white); 156 | } 157 | 158 | #bottom-buttons { 159 | display: flex; 160 | border-top: 1px solid var(--color-dark); 161 | } 162 | #bottom-buttons > a { 163 | padding: 1rem; 164 | white-space: nowrap; 165 | text-align: center; 166 | text-decoration: none; 167 | background-color: var(--color-primary); 168 | color: var(--color-white); 169 | } 170 | #bottom-buttons > a:hover, 171 | #bottom-buttons > a:focus { 172 | background-color: var(--color-dark); 173 | color: var(--color-blue-light); 174 | } 175 | #all-notifications-button { 176 | flex: 1 0 auto; 177 | } 178 | #new-message-button { 179 | flex: 0 0 1em; 180 | } 181 | #new-message-button::before { 182 | content: ''; 183 | display: inline-block; 184 | width: 1rem; 185 | height: 1rem; 186 | margin: 0 .5rem; 187 | background: center / contain no-repeat url('../images/pm-new.png'); 188 | } 189 | 190 | /** 191 | * Page non connecté 192 | */ 193 | 194 | #signin-button { 195 | display: block; 196 | text-align: center; 197 | background-color: var(--color-hover); 198 | color: var(--color-white); 199 | text-decoration: none; 200 | padding: 1rem; 201 | } 202 | #signin-button:hover, 203 | #signin-button:focus { 204 | background-color: var(--color-dark); 205 | } 206 | -------------------------------------------------------------------------------- /Google Chrome/fake-data.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 50 | 51 |
52 | 53 | 54 | 85 |
86 |
87 | 88 | -------------------------------------------------------------------------------- /Google Chrome/options/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ZdS Notificateur — Options 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |

Options de l'extension

14 |
15 |
16 |
17 |
18 |
19 | 20 | 21 |
22 |
23 | 24 | Sinon, un clic ouvrira l'accueil de ZdS 25 |
26 |
27 |
28 | 29 | Les notifications s'ouvrent de toute façon dans un nouvel onglet si la page courante n'est pas une page de ZdS 30 |
31 |
32 | 33 |
34 | 37 |
38 | 39 | La popup se fermera automatiquement lors d'un clic sur une notification 40 |
41 | 44 |
45 |
46 | 47 | Attention, cette option a un comportement incomplet sur Linux et Mac (limitation de Chrome) 48 |
49 |
50 | 56 |
57 |
58 | 59 | Les notifications seront plus ou moins visibles 60 |
61 |
62 | 63 |
64 |
65 |
66 |
67 |
68 |
69 | 70 | Les notifications seront plus ou moins visibles 71 |
72 |
73 | 74 |
75 |
76 |
77 |
78 |
79 |
80 | 81 | Un son sera joué pour vous alerter d'une nouvelle notification 82 |
83 | 87 | 88 |
89 | 90 | 91 |
92 |
93 |
94 | 100 |
101 | 102 |
103 |
104 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /Google Chrome/popup/popup.js: -------------------------------------------------------------------------------- 1 | var notificator; 2 | 3 | var urlZdS = "https://zestedesavoir.com"; 4 | 5 | var linkListener = function(notificator, event) { 6 | var url = this.href; 7 | if(url === "#") 8 | return; 9 | 10 | event.preventDefault(); 11 | 12 | var link = this; 13 | var isShortcut = this.classList.contains("allNotifs"); 14 | if(!isShortcut) { 15 | var id = parseInt(this.dataset.notificationId, 10); 16 | var isAlerte = this.classList.contains("alerte"); 17 | var isNotification = this.classList.contains("notification"); 18 | } 19 | 20 | chrome.windows.getCurrent({ populate: true }, function(currentWindow) { 21 | var tab = false; 22 | for(var i in currentWindow.tabs) { 23 | if(currentWindow.tabs[i].active) { 24 | tab = currentWindow.tabs[i]; 25 | break; 26 | } 27 | } 28 | if(!notificator.getOptions("openInNewTab") && tab && tab.url !== undefined && tab.url.indexOf("zestedesavoir.com") != -1 && tab.url.indexOf("zestedesavoir.com") < 14) { 29 | if(isAlerte) { 30 | notificator.alertTabId.push(tab.id); 31 | } 32 | chrome.tabs.update(tab.id, { url: url }); 33 | } 34 | else { 35 | chrome.tabs.create({ 36 | 'url': url, 37 | 'active': false 38 | }, function(tab){ 39 | if(isAlerte) { 40 | notificator.alertTabId.push(tab.id); //on ajout l'id du tab 41 | } 42 | }); 43 | } 44 | 45 | if(notificator.getOptions("autoclosePopup")) { 46 | window.close(); 47 | return; 48 | } 49 | 50 | if (isNotification) { 51 | var notification = notificator.getNotification(id); 52 | 53 | if (notification && notification.constructor.name === "Object") { 54 | var notif_index = notificator.notifications.indexOf(notification); 55 | 56 | if (notif_index !== -1) { 57 | notificator.notifications.splice(notif_index, 1); 58 | 59 | setNotifsList(); 60 | notificator.updateBadge(); 61 | } 62 | } 63 | else { 64 | console.log('Notif non trouvée'); 65 | } 66 | 67 | notificator.check(); 68 | } 69 | }); 70 | } 71 | 72 | var createNotif = function(notif) { 73 | var notif_link = document.createElement("a"); 74 | 75 | notif_link.href = urlZdS + notif.link; 76 | 77 | notif_link.classList.add("notification", notif.type); 78 | 79 | notif_link.id = "notif-" + notif.id; 80 | notif_link.dataset.notificationId = notif.id; 81 | 82 | var notif_answerer = document.createElement("div"); 83 | notif_answerer.className = "user"; 84 | 85 | var notif_avatar = document.createElement("img"); 86 | notif_avatar.src = notif.answerer.avatar; 87 | notif_avatar.className = "user-avatar"; 88 | notif_avatar.alt = "Avatar de " + notif.answerer.username; 89 | 90 | notif_answerer.appendChild(notif_avatar); 91 | 92 | notif_link.appendChild(notif_answerer); 93 | 94 | 95 | 96 | var notif_details = document.createElement("div"); 97 | notif_details.className = "details"; 98 | 99 | var notif_pseudo = document.createElement("span"); 100 | notif_pseudo.className = "user-name"; 101 | notif_pseudo.textContent = notif.answerer.username; 102 | 103 | notif_details.appendChild(notif_pseudo); 104 | 105 | var notif_date = document.createElement("time"); 106 | notif_date.className = "date"; 107 | notif_date.textContent = notif.date; 108 | 109 | notif_details.appendChild(notif_date); 110 | 111 | 112 | var notif_title = document.createElement("span"); 113 | notif_title.className = "title"; 114 | notif_title.textContent = notif.title; 115 | 116 | notif_details.appendChild(notif_title); 117 | 118 | notif_link.appendChild(notif_details); 119 | 120 | return notif_link; 121 | }; 122 | 123 | var setNotifsList = function() { 124 | var content = document.getElementById("content"); 125 | var notifs = notificator.notifications; 126 | 127 | while (content.firstChild) { 128 | content.removeChild(content.firstChild); 129 | } 130 | 131 | if(notificator.logged || notificator.useFakeData) { 132 | var len = notifs.length; 133 | 134 | var notifList = document.createElement("div"); 135 | notifList.classList.add("notifList"); 136 | 137 | if(len === 0) { 138 | var no_notifs_elem = document.createElement("div"); 139 | no_notifs_elem.classList.add("element", "other", "noNotifs"); 140 | 141 | var no_notifs_text = document.createElement("span"); 142 | no_notifs_text.textContent = "Aucune nouvelle notification"; 143 | 144 | no_notifs_elem.appendChild(no_notifs_text); 145 | content.appendChild(no_notifs_elem); 146 | } else { 147 | for(var i = 0; i < len; i++) { 148 | var n = createNotif(notifs[i]); 149 | notifList.appendChild(n); 150 | } 151 | 152 | content.appendChild(notifList); 153 | } 154 | 155 | //ligne "Afficher toute les notifications" 156 | if(notificator.getOptions("showAllNotifButton")) { 157 | var all_notifs_link = document.createElement("a"); 158 | all_notifs_link.classList.add("element", "other", "allNotifs"); 159 | all_notifs_link.href = urlZdS + "/notifications/"; 160 | all_notifs_link.textContent = "Toutes les notifications"; 161 | 162 | content.appendChild(all_notifs_link); 163 | } 164 | 165 | var liens = document.querySelectorAll("#content a"); 166 | for (var i = 0; i < liens.length; i++) { 167 | liens[i].addEventListener("click", linkListener.bind(liens[i], notificator), false); 168 | } 169 | 170 | notificator.setNewNotifCallback(function(notif) { 171 | var no_notifs_elem = document.querySelector(".noNotifs"); 172 | if (no_notifs_elem) { 173 | no_notifs_elem.parentNode.removeChild(no_notifs_elem); 174 | } 175 | 176 | var n = createNotif(notif); 177 | 178 | var liens = n.getElementsByTagName("a"); 179 | 180 | for (var i = 0; i < liens.length; i++) { 181 | liens[i].addEventListener("click", linkListener.bind(liens[i], notificator), false); 182 | } 183 | 184 | notifList.appendChild(n); 185 | }); 186 | } 187 | else { 188 | var not_logged_in_elem = document.createElement("div"); 189 | not_logged_in_elem.classList.add("element", "other", "notConnected"); 190 | not_logged_in_elem.textContent = "Vous n'êtes pas connecté !"; 191 | 192 | content.appendChild(not_logged_in_elem); 193 | } 194 | } 195 | 196 | var backgroundLoaded = function(bgWindow) { 197 | if(!bgWindow || !bgWindow.theNotificator) { 198 | console.error("ZdSNotificator : Failed to load background"); 199 | return; 200 | } 201 | notificator = bgWindow.theNotificator; 202 | 203 | var logo = document.getElementById("logo"); 204 | logo.href = urlZdS; 205 | 206 | logo.addEventListener("click", linkListener.bind(logo, notificator), false); 207 | 208 | setNotifsList(); 209 | }; 210 | 211 | chrome.runtime.getBackgroundPage(function(bgWindow) { 212 | backgroundLoaded(bgWindow); 213 | }); 214 | -------------------------------------------------------------------------------- /Google Chrome/options/options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Notificator Options 3 | * @namespace 4 | */ 5 | 6 | var NotificatorOptions = { 7 | /** 8 | * Init options 9 | */ 10 | init: function(_notificator) { 11 | this.notificator = _notificator; 12 | 13 | this.elems = { 14 | updateInterval: document.getElementById('interval'), 15 | openListe: document.getElementById('openListe'), 16 | openInNewTab: document.getElementById("newTab"), 17 | showAllNotifButton: document.getElementById("allNotifs"), 18 | showDesktopNotif: document.getElementById("notifNative"), 19 | notifPriority: document.getElementById("priorityNotif"), 20 | mpPriority: document.getElementById("priorityMP"), 21 | playSon: document.getElementById("playSon"), 22 | tweet: document.getElementById("tweet"), 23 | // ZdSLink: document.getElementById("ZdSLink"), 24 | autoclosePopup: document.getElementById("autoclosePopup"), 25 | //useDetailedNotifs: document.getElementById("detailedNotifs"), 26 | //archiveAllLink: document.getElementById("archiveAllLink") 27 | }; 28 | 29 | document.getElementById("enregistrer").addEventListener("click", this.save.bind(this)); 30 | document.getElementById("reset").addEventListener("click", this.reset.bind(this)); 31 | this.elems["updateInterval"].addEventListener("keyup", this.checkInput.bind(this)); 32 | this.elems["openListe"].addEventListener("click", this.toggle.bind(this)); 33 | this.elems["showDesktopNotif"].addEventListener("click", this.toggle.bind(this)); 34 | 35 | this.load(); 36 | }, 37 | 38 | oldValue: 5, 39 | 40 | /** 41 | * Check inputs 42 | * @param {Object} [evt] JS Event 43 | */ 44 | checkInput: function(evt) { 45 | var val = this.elems["updateInterval"].value; 46 | if(val == '') 47 | this.elems["updateInterval"].value = this.oldValue; 48 | else { 49 | if(parseInt(val) > 60) { 50 | this.elems["updateInterval"].value = 60; 51 | this.oldValue = 60; 52 | } else { 53 | this.oldValue = val; 54 | } 55 | } 56 | }, 57 | 58 | /** 59 | * Reset options 60 | */ 61 | reset: function() { 62 | var c = confirm("Voulez vous réellement remettre à zéro les options ?"); 63 | if(c) { 64 | this.notificator.resetOptions(); 65 | window.location.reload(); 66 | } 67 | }, 68 | 69 | /** 70 | * Save options 71 | */ 72 | save: function() { 73 | this.notificator.setOptions(this.getValues(), function() { 74 | window.close(); 75 | }); 76 | }, 77 | 78 | /** 79 | * Load options 80 | */ 81 | load: function() { 82 | this.options = this.notificator.getOptions(); 83 | for(var key in this.elems) { 84 | if(this.options[key] !== undefined) { 85 | if(this.elems[key].type == "checkbox") { 86 | this.elems[key].checked = this.options[key]; 87 | } 88 | else { 89 | this.elems[key].value = this.options[key]; 90 | } 91 | } 92 | } 93 | this.toggle(); 94 | this.oldValue = this.elems['updateInterval'].value; 95 | this.checkAvatar(); 96 | }, 97 | 98 | /** 99 | * Get inputs value 100 | * @returns {Object} The input values 101 | */ 102 | getValues: function() { 103 | var obj = {}; 104 | for(var key in this.elems) { 105 | var val = this.elems[key].type == "checkbox" ? this.elems[key].checked : parseInt(this.elems[key].value); 106 | obj[key] = val; 107 | } 108 | return obj; 109 | }, 110 | 111 | /** 112 | * Toggle inputs 113 | */ 114 | toggle: function() { 115 | if(this.elems['showDesktopNotif'].checked) { 116 | Array.prototype.forEach.call(document.querySelectorAll(".subNotifFields"), function(elem) { 117 | elem.classList.remove("disabled"); 118 | 119 | Array.prototype.forEach.call(elem.querySelectorAll("input"), function(input) { 120 | input.removeAttribute("disabled"); 121 | }); 122 | }); 123 | } 124 | else { 125 | Array.prototype.forEach.call(document.querySelectorAll(".subNotifFields"), function(elem) { 126 | elem.classList.add("disabled"); 127 | 128 | Array.prototype.forEach.call(elem.querySelectorAll("input"), function(input) { 129 | input.setAttribute("disabled", true); 130 | }); 131 | }); 132 | } 133 | 134 | if(this.elems['openListe'].checked) { 135 | Array.prototype.forEach.call(document.querySelectorAll(".subPopupFields"), function(elem) { 136 | elem.classList.remove("disabled"); 137 | 138 | Array.prototype.forEach.call(elem.querySelectorAll("input"), function(input) { 139 | input.removeAttribute("disabled"); 140 | }); 141 | }); 142 | } 143 | else { 144 | Array.prototype.forEach.call(document.querySelectorAll(".subPopupFields"), function(elem) { 145 | elem.classList.add("disabled"); 146 | 147 | Array.prototype.forEach.call(elem.querySelectorAll("input"), function(input) { 148 | input.setAttribute("disabled", true); 149 | }); 150 | }); 151 | } 152 | 153 | Array.prototype.forEach.call(document.querySelectorAll(".priority input"), function(input) { 154 | input.addEventListener("input", onPriorityInputChange, false); 155 | onPriorityInputChange.call(input, null); 156 | }); 157 | // $(".priority input").on("change", ).trigger("change"); 158 | function onPriorityInputChange(event) { 159 | var value = "Erreur"; 160 | 161 | switch(parseInt(this.value)) { 162 | case -2: 163 | value = "Pas important"; 164 | break; 165 | case -1: 166 | value = "Peu important"; 167 | break; 168 | case 0: 169 | value = "Normale"; 170 | break; 171 | case 1: 172 | value = "Important"; 173 | break; 174 | case 2: 175 | value = "Très important"; 176 | break; 177 | } 178 | this.parentNode.querySelector(".value").textContent = value; 179 | } 180 | }, 181 | 182 | /** 183 | * Check avatar 184 | */ 185 | checkAvatar: function() { 186 | new AjaxRequest("http://zestedesavoir.com", this.loadCallback.bind(this), "text"); 187 | }, 188 | 189 | /** 190 | * Callback when page loaded 191 | */ 192 | loadCallback: function(event) { 193 | var doc = document.implementation.createHTMLDocument("xhr_result"); 194 | doc.documentElement.innerHTML = event.target.responseText.trim(); 195 | 196 | var leDiv = document.getElementById("connecteComme"); 197 | //on est pas connecté ! 198 | if(!this.notificator.logged) { 199 | leDiv.querySelector("a").setAttribute("href", "http://zestedesavoir.com/membres/connexion/"); 200 | leDiv.querySelector("strong").textContent = "Non connecté !"; 201 | } else { 202 | var link = doc.getElementById("my-account"); 203 | var profileLink = link.getAttribute("href"); 204 | var profileName = link.querySelector("span.username").textContent; 205 | var avatarImgSrc = link.querySelector("img").getAttribute("src").replace(/^\/\/(.*)/, "http://$1"); 206 | leDiv.querySelector("a").setAttribute("href", "http://zestedesavoir.com" + profileLink/* + profileName*/); 207 | 208 | leDiv.querySelector("strong").textContent = profileName; 209 | leDiv.querySelector("img").setAttribute("src", avatarImgSrc); 210 | } 211 | } 212 | }; 213 | 214 | document.addEventListener('DOMContentLoaded', function () { 215 | chrome.runtime.getBackgroundPage(function(bgWindow) { 216 | var notificator = bgWindow.theNotificator; 217 | if(notificator) { 218 | NotificatorOptions.init.call(NotificatorOptions, notificator); 219 | } else { 220 | console.log("Can't fetch the background :("); 221 | } 222 | }); 223 | }); 224 | -------------------------------------------------------------------------------- /Firefox/notifier.js: -------------------------------------------------------------------------------- 1 | /* global chrome XMLHttpRequest:true */ 2 | /* eslint-disable no-console */ 3 | const debugMode = false 4 | const NOT_CONNECTED = 401 5 | const CONNECTED = 200 6 | 7 | // TODO move this 8 | var connected = false 9 | var notifCounter = 0 10 | var currentDom = null 11 | let contentDiv = null 12 | 13 | // Load preferences 14 | const updateDelay = 60 * 1000 15 | let showPopupNotification = false 16 | chrome.storage.local.get('notify', (res) => { 17 | showPopupNotification = res.notify || false 18 | }) 19 | 20 | let baseUrl = 'https://zestedesavoir.com' 21 | if (debugMode) baseUrl = 'https://beta.zestedesavoir.com' 22 | const token = 'zds-notifier' 23 | 24 | /** 25 | * Get notifications from https://zestedesavoir.com via the API 26 | */ 27 | function getNotificationsFromAPI () { 28 | contentDiv = document.createElement('div') 29 | const options = `page_size=30&ordering=-pubdate&Authorization=${token}` 30 | const target = `${baseUrl}/api/notifications/?${options}` 31 | // TODO use fetch instead 32 | const xhr = new XMLHttpRequest() 33 | xhr.open('GET', target, true) 34 | xhr.onload = function (e) { 35 | if (xhr.readyState === 4) { 36 | const result = xhr.status 37 | if (result === NOT_CONNECTED) { 38 | connected = false 39 | if (debugMode) console.log('Not connected') 40 | chrome.browserAction.setIcon({path: 'icons/notconnected.png'}) 41 | } else if (result === CONNECTED) { 42 | connected = true 43 | const rootDOM = JSON.parse(xhr.response) 44 | if (rootDOM.details) { 45 | if (debugMode) console.log('can\'t parse incorrect JSON') 46 | } else { 47 | parseNotifications(rootDOM.results) 48 | } 49 | } else if (debugMode) { 50 | console.log(result) 51 | } 52 | } 53 | 54 | buildPopup() 55 | } 56 | 57 | xhr.onerror = function (e) { 58 | console.error(xhr.statusText) 59 | connected = false 60 | } 61 | xhr.send(null) 62 | } 63 | 64 | /** 65 | * Build the DOM of the popup 66 | */ 67 | function buildPopup () { 68 | if (!notifCounter) { 69 | const divNoNotif = document.createElement('div') 70 | divNoNotif.id = 'noNotif' 71 | divNoNotif.innerText = 'Aucune notification' 72 | contentDiv.appendChild(divNoNotif) 73 | if (debugMode) console.log(divNoNotif.innerText) 74 | } 75 | const body = document.body 76 | body.appendChild(contentDiv) 77 | // Remove useless nodes 78 | while (body.childNodes.length > 2) { 79 | body.removeChild(body.childNodes[1]) 80 | } 81 | currentDom = body 82 | } 83 | 84 | /** 85 | * Parse the JSON returned by the API 86 | * @param results the JSON object 87 | */ 88 | function parseNotifications (results) { 89 | // Get new notifications 90 | let notificationsCounter = 0 91 | for (const notif of results) { 92 | // If a notification is new we have is_read === False 93 | if (!notif.is_read) { 94 | notificationsCounter += 1 95 | const title = notif.title 96 | const author = notif.sender.username 97 | const authorAvatar = notif.sender.avatar_url 98 | 99 | const urlNotif = `${baseUrl}${notif.url}` 100 | if (debugMode) console.log(`${urlNotif} by ${author}`) 101 | addNotification(title, author, authorAvatar, formatDate(notif.pubdate), urlNotif) 102 | } 103 | } 104 | // Notify the user 105 | if (notificationsCounter > notifCounter) { 106 | if (debugMode) console.log(`New notification: ${notificationsCounter}`) 107 | chrome.browserAction.setIcon({path: 'icons/icone_n_20.png'}) 108 | const title = 'Zds-notificateur : Nouvelle notification !' 109 | let content = `Vous avez ${notificationsCounter} notification` 110 | if (notificationsCounter > 1) content += 's' 111 | popupNotification(title, content) 112 | } else if (notificationsCounter === 0) { 113 | chrome.browserAction.setIcon({path: 'icons/clem_48.png'}) 114 | } 115 | notifCounter = notificationsCounter 116 | } 117 | 118 | /** 119 | * Pretty print a publication date 120 | * @param pubdate 121 | */ 122 | function formatDate (pubdate) { 123 | const date = new Date((pubdate || '').replace(/-/g, '/').replace(/[TZ]/g, ' ')) 124 | 125 | const actualDate = new Date() 126 | if (date.toDateString() === actualDate.toDateString()) { 127 | return 'Aujourd\'hui' 128 | } 129 | 130 | const yesterday = actualDate 131 | yesterday.setDate(actualDate.getDate() - 1) 132 | if (date.toDateString() === yesterday.toDateString()) { 133 | return 'Hier' 134 | } 135 | 136 | let minutes = `${date.getMinutes()}` 137 | if (minutes.length < 2) { 138 | minutes = `0${minutes}` 139 | } 140 | const shortDate = [date.getDate(), date.getMonth() + 1].join('/') 141 | const time = [date.getHours(), minutes].join('h') 142 | return `le ${shortDate} à ${time}` 143 | } 144 | 145 | /** 146 | * Add a notification entry 147 | * @param title of the subject 148 | * @param author 149 | * @param authorAvatar 150 | * @param date 151 | * @param url 152 | */ 153 | function addNotification (title, author, authorAvatar, date, url) { 154 | const divBlocNotif = document.createElement('div') 155 | divBlocNotif.id = 'blocNotif' 156 | const divDate = document.createElement('div') 157 | divDate.id = 'date' 158 | divDate.innerText = date 159 | const divPseudo = document.createElement('div') 160 | divPseudo.id = 'pseudo' 161 | divPseudo.innerText = author 162 | const divTitle = document.createElement('div') 163 | divTitle.id = 'title' 164 | divTitle.innerText = title 165 | const imgAvatar = document.createElement('img') 166 | imgAvatar.src = authorAvatar 167 | const divNotif = document.createElement('div') 168 | divNotif.id = 'notification' 169 | const a = document.createElement('a') 170 | a.href = url 171 | a.target = '_blank' // Open the notification in a new window 172 | 173 | divBlocNotif.appendChild(divDate) 174 | divBlocNotif.appendChild(divPseudo) 175 | divBlocNotif.appendChild(divTitle) 176 | divNotif.appendChild(imgAvatar) 177 | divNotif.appendChild(divBlocNotif) 178 | a.appendChild(divNotif) 179 | contentDiv.appendChild(a) 180 | } 181 | 182 | /** 183 | * Show a popup notification 184 | * @param title of the popup 185 | * @param content 186 | */ 187 | function popupNotification (title, content) { 188 | if (showPopupNotification) { 189 | chrome.notifications.create({ 190 | 'type': 'basic', 191 | 'iconUrl': chrome.extension.getURL('icons/icone_n_20.png'), 192 | 'title': title, 193 | 'message': content 194 | }) 195 | } 196 | } 197 | 198 | // Update the popup 199 | setInterval(getNotificationsFromAPI, updateDelay) 200 | getNotificationsFromAPI() 201 | 202 | /** 203 | * Compare current state with the one of the loading page 204 | * @param the content of the message request 205 | */ 206 | 207 | function updateState(request) { 208 | const lastState = connected ? (notifCounter > 0 ? 2 : 1 ) : 0; 209 | 210 | if( request.state !== lastState ) { // If any difference update the state 211 | switch( request.state ) { 212 | case 1: 213 | chrome.browserAction.setIcon({path: 'icons/clem_48.png'}) 214 | break; 215 | case 2: 216 | chrome.browserAction.setIcon({path: 'icons/icone_n_20.png'}) 217 | break; 218 | case 0: 219 | chrome.browserAction.setIcon({path: 'icons/notconnected.png'}) 220 | break; 221 | } 222 | lastIcon = request.state; 223 | getNotificationsFromAPI(); // Update the state 224 | } 225 | } 226 | 227 | browser.runtime.onMessage.addListener(updateState); 228 | -------------------------------------------------------------------------------- /Universal/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Google Chrome/background.js: -------------------------------------------------------------------------------- 1 | /** 2 | * OpenClassrooms Notificateur 3 | * @author Eskimon & Sandhose 4 | * @licence under MIT Licence 5 | * @version 2.2.0 6 | * ====== 7 | * background.js 8 | * Main background script 9 | */ 10 | 11 | 12 | /** 13 | * Notificateur - Main class 14 | * @constructor 15 | */ 16 | var Notificateur = function() { 17 | this.init.apply(this, arguments); 18 | }; 19 | 20 | Notificateur.prototype = { 21 | /** 22 | * ZdS URL 23 | */ 24 | url: "http://zestedesavoir.com", 25 | 26 | /** 27 | * If logged in last check 28 | */ 29 | logged: true, 30 | 31 | /** 32 | * Current notifications 33 | */ 34 | notifications: [], 35 | 36 | /** 37 | * Options 38 | */ 39 | default_options: { 40 | updateInterval: 5, 41 | openListe: true, 42 | openInNewTab: true, 43 | showAllNotifButton: true, 44 | showDesktopNotif: true, 45 | notifPriority: 0, 46 | mpPriority: 0, 47 | playSon: false, 48 | ZdSLink: false, 49 | autoclosePopup: true, 50 | useDetailedNotifs: false, 51 | archiveAllLink: false 52 | }, 53 | 54 | options: {}, 55 | 56 | _optionsTypes: { // Je l'ai quand meme fait ^^ 57 | updateInterval: Number, 58 | openListe: Boolean, 59 | openInNewTab: Boolean, 60 | showAllNotifButton: Boolean, 61 | showDesktopNotif: Boolean, 62 | notifPriority: Number, 63 | mpPriority: Number, 64 | playSon: Boolean, 65 | ZdSLink: Boolean, 66 | autoclosePopup: Boolean, 67 | useDetailedNotifs: Boolean, 68 | archiveAllLink: Boolean 69 | }, 70 | 71 | /** 72 | * Use fake data for debug 73 | */ 74 | useFakeData: false, 75 | 76 | /** 77 | * Check en cours 78 | */ 79 | checkPending: false, 80 | 81 | /** 82 | * chrome.storage 83 | */ 84 | storage: chrome.storage.sync, 85 | 86 | /** 87 | * Init 88 | */ 89 | init: function() { 90 | this.initialized = false; 91 | this.notifications = []; //tableau stockant les notifs 92 | this.alertTabId = []; 93 | this.MPs = []; //tableau stockant les MPs 94 | chrome.browserAction.enable(); //sinon un concours de circonstance pourrait nous faire démarrer avec une icone disable 95 | this.loadOptions(function() { 96 | //action lorsqu'on click sur le bouton (affichage liste ou chargement ZdS 97 | if(!this.options.openListe) { //soit on ouvre le ZdS 98 | chrome.browserAction.setPopup({ popup: "" }); 99 | chrome.browserAction.onClicked.addListener(this.listeners.toolbarClick.bind(this)); 100 | } else { //sinon on ouvre une popup avec le contenu des notifs 101 | chrome.browserAction.setPopup({ popup: "popup/popup.html" }); 102 | } 103 | 104 | chrome.alarms.create('refresh', {periodInMinutes: parseInt(this.options.updateInterval)}); 105 | 106 | this.loadSounds(function() { 107 | this.loadSoundpack(this.soundpack); 108 | }.bind(this)); 109 | 110 | this.initialized = true; 111 | 112 | this.check(); 113 | 114 | }.bind(this)); 115 | 116 | this.initListeners(); 117 | }, 118 | 119 | /* Add events listeners */ 120 | initListeners: function() { 121 | chrome.runtime.onInstalled.addListener(this.listeners.install.bind(this)); 122 | 123 | if(chrome.tabs) { 124 | chrome.tabs.onUpdated.addListener(this.listeners.tabUpdate.bind(this)); 125 | chrome.tabs.onCreated.addListener(this.listeners.tabCreate.bind(this)); 126 | } 127 | 128 | if(chrome.alarms) { 129 | chrome.alarms.onAlarm.addListener(this.listeners.alarm.bind(this)); 130 | } 131 | 132 | if(chrome.notifications) { 133 | chrome.notifications.onButtonClicked.addListener(this.listeners.notifButtonClick.bind(this)); 134 | chrome.notifications.onClicked.addListener(this.listeners.notifClick.bind(this)); 135 | chrome.notifications.onClosed.addListener(this.listeners.notifClose.bind(this)); 136 | } 137 | }, 138 | 139 | /** 140 | * Listeners 141 | */ 142 | listeners: { 143 | /** 144 | * Extension install 145 | */ 146 | 147 | install: function(details) { 148 | if(details.reason == "install") { 149 | chrome.tabs.create({ 150 | 'url': chrome.runtime.getURL("welcome/welcome.html"), 151 | 'active': true 152 | }); 153 | } 154 | }, 155 | 156 | /** 157 | * Tab Update 158 | */ 159 | tabUpdate: function(tabId, changeInfo, tab) { 160 | if(tab.url !== undefined && tab.url.indexOf("zestedesavoir.com") != -1 && tab.url.indexOf("zestedesavoir.com") < 14 && changeInfo.status == "complete") { 161 | if(tab.url.indexOf("/forum/sujet/") != -1) {//cas d'une notif de type forum -> il faut faire l'injection 162 | if(this.alertTabId.indexOf(tabId) == -1) { //ne se déclenche pas si on arrive via une alerte de modo 163 | //on garde le inject juste pour le plaisir du konami code :D 164 | chrome.tabs.executeScript(tabId, { 165 | file: "injected.js" 166 | }); 167 | } else { 168 | this.alertTabId.splice(this.alertTabId.indexOf(tabId),1); 169 | } 170 | //on attend une seconde pour que le script soit injecté puis on check de nouveau les notifs pour mettre à jour le numero 171 | this.check(); 172 | } else if(tab.url.indexOf("/mp/") != -1) { //cas des MP 173 | this.check(); 174 | } 175 | } 176 | }, 177 | 178 | /** 179 | * Tab create 180 | */ 181 | tabCreate: function(tab) { 182 | if(tab.url !== undefined && tab.url.indexOf("zestedesavoir.com") != -1 && tab.url.indexOf("zestedesavoir.com") < 14) { 183 | this.check(); 184 | } 185 | }, 186 | 187 | /** 188 | * Toolbar Click 189 | */ 190 | toolbarClick: function() { 191 | chrome.tabs.create({ 192 | url: this.url, 193 | active: true 194 | }); 195 | }, 196 | 197 | /** 198 | * Alarm 199 | */ 200 | alarm: function(alarm) { 201 | if (alarm.name == "refresh") { 202 | this.check(); 203 | } 204 | }, 205 | 206 | /** 207 | * Notification button click 208 | */ 209 | notifButtonClick: function(notifId, button) { 210 | var notif = this.getNotification(notifId); 211 | if(button == 0) { // Open last message 212 | if(notif) { 213 | switch(notif.type) { 214 | case "forum": //normal 215 | this.openZdS(notif.link); 216 | this.archiveNotification(notif.id); 217 | break; 218 | case "mp": //MP 219 | this.openZdS(notif.link); 220 | break; 221 | case "alerte": //alerte 222 | this.openZdS(notif.link); 223 | break; 224 | } 225 | } 226 | 227 | chrome.notifications.clear(notifId, function() { 228 | 229 | }); 230 | } 231 | else if(button == 1) { // Open thread 232 | if(notif) { 233 | this.openZdS("/forum/sujet/" + notif.id); 234 | this.archiveNotification(notif.id); 235 | } 236 | 237 | chrome.notifications.clear(notifId, function() { 238 | 239 | }); 240 | } 241 | }, 242 | 243 | /** 244 | * Notif Click 245 | */ 246 | notifClick: function(notifId) { 247 | var notif = this.getNotification(notifId); 248 | if(notif) { 249 | switch(notif.type) { 250 | case "forum": //normal 251 | this.openZdS(notif.link); 252 | this.archiveNotification(notif.id); 253 | break; 254 | case "mp": //MP 255 | this.openZdS(notif.link); 256 | break; 257 | case "alerte": //alerte 258 | this.openZdS(notif.link, true); 259 | break; 260 | } 261 | } 262 | 263 | chrome.notifications.clear(notifId, function() { 264 | 265 | }); 266 | }, 267 | 268 | /** 269 | * Notif close 270 | */ 271 | notifClose: function(notifId) { 272 | // A la fermeture de la notif 273 | } 274 | }, 275 | 276 | /** 277 | * Check for new Notifications 278 | */ 279 | check: function() { 280 | var self = this; 281 | if(this.checkPending || !this.initialized) { // Si l'extension n'est pas (encore) initialisee ou un check est deja en cours 282 | return; 283 | } 284 | 285 | this.checkPending = true; 286 | self = this; 287 | chrome.browserAction.setIcon({ "path": "icons/icone_38_parsing.png" }); 288 | 289 | var url = this.useFakeData ? chrome.runtime.getURL("fake-data.html") : this.url; 290 | 291 | new AjaxRequest(url, this.loadCallback.bind(this), function() { 292 | //si jamais la requete plante (pas d'internet, 404 ou autre 500...) 293 | chrome.browserAction.setBadgeText({text: "err"}); 294 | chrome.browserAction.setIcon({"path":"icons/icone_38_logout.png"}); 295 | chrome.browserAction.disable(); 296 | self.logged = false; 297 | self.checkPending = false; 298 | }); 299 | 300 | return; 301 | }, 302 | 303 | /** 304 | * Get notifications by id 305 | * @param {String} [id] Notification ID 306 | * @returns {Array|Object} A notifications if ID is set, or an array of all Notifications 307 | */ 308 | getNotification: function(id) { 309 | if(!id) { 310 | return this.notifications; 311 | } 312 | for(var i=0, nb=this.notifications.length; i>"); 326 | 327 | // On récupère un DOM que l'on peut manipuler 328 | var doc = document.implementation.createHTMLDocument("xhr_result"); 329 | doc.documentElement.innerHTML = event.target.responseText.trim(); 330 | 331 | if (!doc.body) { 332 | return false; 333 | } 334 | 335 | console.dir(doc); 336 | 337 | var contenu = doc.querySelector("div.logbox"); 338 | 339 | var hasNewNotif = { 340 | notification: false, 341 | mp: false 342 | }; 343 | 344 | console.dir(contenu); 345 | 346 | if (!contenu) { 347 | chrome.browserAction.setBadgeText({text: "err"}); 348 | chrome.browserAction.setIcon({"path":"icons/icone_38_logout.png"}); 349 | chrome.browserAction.disable(); 350 | self.logged = false; 351 | self.checkPending = false; 352 | return; 353 | } 354 | 355 | //on est pas connecté ! 356 | if(contenu.classList.contains("unlogged") && !this.useFakeData) { 357 | if(this.logged) { 358 | chrome.browserAction.setBadgeText({ text: "log" }); 359 | chrome.browserAction.setIcon({ "path": "icons/icone_38_logout.png" }); 360 | chrome.browserAction.enable(); 361 | this.logged = false; 362 | } 363 | return; 364 | } else { 365 | if(!this.logged) { 366 | chrome.browserAction.setIcon({ "path": "icons/icone_38.png" }); 367 | chrome.browserAction.setBadgeText({ text: "" }); 368 | chrome.browserAction.enable(); 369 | this.logged = true; 370 | } 371 | } 372 | 373 | //récupere les deux listes, celle des MP (0) et celle des notifs (1) 374 | contenu = contenu.querySelectorAll("div.notifs-links ul.dropdown-list"); 375 | 376 | // Check les notifications --------------------------------------- 377 | var notifications = contenu[1].querySelectorAll("li"), 378 | newNotifs = [], // Liste des nouvelles notifications 379 | oldNotifs = this.notifications, // Ancienne liste 380 | removedNotifs = [], // Notifs enlevées 381 | notifsList = []; // Nouvelle liste 382 | 383 | if(!notifications[0].classList.contains("dropdown-empty-message")) { 384 | for(var i=0, nb=notifications.length; i/ig, "\n").replace(/(<([^>]+)>)/ig, "").substr(0, 140) + "...\n" + notif.date, 531 | priority: parseInt(this.options.notifPriority), 532 | buttons: [{ title: "Voir le message" }, { title: "Voir le début du thread" }] 533 | }, function() {}); 534 | } 535 | else { 536 | var boutons = new Array(); 537 | var priority = 0; 538 | var icone = ""; 539 | switch(notif.type) { 540 | case "forum": 541 | boutons[0] = { title: "Voir le message" }; 542 | boutons[1] = { title: "Voir le début du thread" }; 543 | icone = "icons/big_message.png"; 544 | priority = this.options.notifPriority; 545 | break; 546 | case "mp": 547 | boutons[0] = { title: "Voir le MP" }; 548 | icone = "icons/big_mp.png"; 549 | priority = this.options.mpPriority; 550 | break; 551 | case "alerte": 552 | boutons[0] = { title: "Voir l'alerte" }; 553 | icone = "icons/big_alerte.png"; 554 | priority = this.options.notifPriority; 555 | break; 556 | } 557 | var notifOptions = { // Options des notifications 558 | type: "basic", 559 | iconUrl: icone, 560 | title: notif.title, 561 | message: notif.date, 562 | buttons: boutons, 563 | priority: parseInt(priority) 564 | }; 565 | 566 | chrome.notifications.create(""+notif.id, notifOptions, function() {}); 567 | } 568 | } 569 | }, 570 | 571 | /** 572 | * Clear desktops notifications 573 | * @param {Array} notifs Array of notifications to clear 574 | */ 575 | clearDesktopNotifs: function(notifs) { 576 | if(chrome.notifications) { 577 | for(var i = 0; i < notifs.length; i++) { 578 | chrome.notifications.clear(''+notifs[i].id, function() {}); 579 | } 580 | } 581 | }, 582 | 583 | /** 584 | * Fetch notifications details (forum posts only) 585 | * @param {Object|Array} notif A notifications or an Array of notifications 586 | * @param {Function} callback The callback when the details are fetched 587 | */ 588 | // TODO 589 | fetchNotificationDetails: function(notif, callback) { 590 | if(Object.prototype.toString.call(notif) == "[object Array]") { 591 | for(var i = 0; i < notif.length; i++) { 592 | this.fetchNotificationDetails(notif[i], function(newNotif) { 593 | callback && callback(newNotif, i); 594 | }); 595 | } 596 | 597 | return; 598 | } 599 | 600 | $.get(this.url + notif.link, function(_data) { 601 | var data = _data.replace(/src=/ig, "data-src=").replace(/href=/ig, "data-href="); // <-- pas forcement la meilleur methode... marche pas si qqn a un nom/avatar avec src=/href= dans le nom 602 | //data = data.replace(/]*>(.*?)<\/img>/ig,''); 603 | //data = data.replace(/]*>(.*?)<\/head>/ig,''); 604 | //var xmlDoc = new DOMParser().parseFromString(data, "text/xml"); <-- Le parser bug... 605 | var $data = $(data); 606 | var post = $data.find("#message-" + notif.messageId).parent(); 607 | if(post.length == 1) { 608 | var authorElem = post.find(".avatar .author a"); 609 | var author = $.trim(authorElem.text()); 610 | var authorUrl = authorElem.attr("data-href"); // URL de l'auteur sans le /membres/ 611 | var avatarUrl = post.find(".avatar img[alt=\"avatar\"]").attr("data-src"); 612 | var postContent = post.find(".content .markdown").text(); 613 | var threadTitle = $(data.match(/[\n\r\s]*(.*)[\n\r\s]*<\/title>/gmi)[0]).text() // <-- Un peu hard, je sais ^^ 614 | notif.author = author; 615 | notif.avatarUrl = avatarUrl; 616 | notif.authorUrl = authorUrl; 617 | notif.postContent = postContent; 618 | notif.threadTitle = threadTitle; 619 | notif.detailed = true; 620 | 621 | if(!notif.avatarUrl.indexOf("http") == 0) { // <-- URL relative 622 | notif.avatarUrl = "http://zestedesavoir.com" + notif.avatarUrl; 623 | } 624 | } 625 | else { 626 | notif.detailed = false; 627 | } 628 | 629 | callback && callback(notif); 630 | }, "text"); 631 | }, 632 | 633 | /** 634 | * Get options 635 | * @param {String} [key] Option key 636 | * @returns {Object} The options value or all options 637 | */ 638 | getOptions: function(key) { 639 | if(key) { 640 | return this.options[key]; 641 | } 642 | else { 643 | return this.options; 644 | } 645 | }, 646 | 647 | /** 648 | * Set options 649 | * @param {Object} changes Object of options changes 650 | * @param {Function} [callback] 651 | */ 652 | setOptions: function(changes, callback) { 653 | callback = callback || function() { console.log("Options saved"); }; 654 | 655 | var oldOptions = this.options; 656 | for(var key in changes) { 657 | if(this.options.hasOwnProperty(key)) { 658 | // Conversion des donnees dans le bon type 659 | if(this._optionsTypes[key] == Number) { 660 | changes[key] = parseInt(changes[key]); 661 | } 662 | else if(this._optionsTypes[key] == Boolean) { 663 | changes[key] = !!changes[key]; 664 | } 665 | else if(this._optionsTypes[key] == String) { 666 | changes[key] = changes[key].toString(); 667 | } 668 | 669 | this.options[key] = changes[key]; 670 | } 671 | } 672 | 673 | changes.timestamp = Date.now(); 674 | 675 | console.log("Options changes from", oldOptions, "to", this.options); 676 | this.storage.set(changes, callback); 677 | this.updateOptions(); 678 | }, 679 | 680 | /** 681 | * Reset options 682 | */ 683 | resetOptions: function() { 684 | var defaults = this.default_options; 685 | delete defaults["lastEdit"]; 686 | this.setOptions(defaults); 687 | }, 688 | 689 | /** 690 | * Load options from chrome.storage 691 | * @param {Function} [callback] Callback when options are loaded 692 | */ 693 | loadOptions: function(callback) { // Charge les options depuis le chrome.storage 694 | this.options.extend(this.default_options); 695 | 696 | var keys = Object.keys(this.options); 697 | 698 | this.storage.get(keys, function(items) { 699 | for(var key in items) { 700 | if(this.options.hasOwnProperty(key)) { 701 | this.options[key] = items[key]; 702 | } 703 | } 704 | 705 | (typeof callback == "function") && callback(); 706 | }.bind(this)); 707 | }, 708 | 709 | /** 710 | * Update options 711 | */ 712 | updateOptions: function() { 713 | // Update interval 714 | chrome.alarms.create('refresh', { periodInMinutes: parseInt(this.options.updateInterval) }); 715 | 716 | //action lorsqu'on click sur le bouton (affichage liste ou chargement ZdS 717 | if(!this.options.openListe) { //soit on ouvre le ZdS 718 | chrome.browserAction.setPopup({ popup: "" }); 719 | chrome.browserAction.onClicked.addListener(this.listeners.toolbarClick.bind(this)); 720 | } else { //sinon on ouvre une popup avec le contenu des notifs 721 | chrome.browserAction.onClicked.removeListener(this.listeners.toolbarClick); 722 | chrome.browserAction.setPopup({ popup: "popup/popup.html" }); 723 | } 724 | }, 725 | 726 | /** 727 | * Load soundpacks 728 | * @param {Function} [callback] Callback fired when soundpacks are loaded 729 | */ 730 | loadSounds: function(callback) { 731 | new AjaxRequest(chrome.extension.getURL("/sounds/packs.json"), function(data) { 732 | try{ 733 | data = JSON.parse(data); 734 | } 735 | catch (e) { 736 | console.log("Error parsing JSON"); 737 | return false; 738 | } 739 | if (!data) { 740 | return false; 741 | } 742 | this.soundpacks = data; 743 | if(this.soundpacks.sounds[this.options.soundpack]) { 744 | this.soundpack = this.soundpacks.sounds[this.options.soundpack]; 745 | } 746 | else { 747 | this.soundpack = this.soundpacks.sounds[this.soundpacks.default_soundpack]; 748 | } 749 | 750 | callback && callback(); 751 | }.bind(this)); 752 | }, 753 | 754 | /** 755 | * Load a specific soundpack 756 | * @param {Object} soundpack The soundpack to load 757 | */ 758 | loadSoundpack: function(soundpack) { 759 | var soundsList = document.getElementById("sound_list") || document.createElement("div"); 760 | soundsList.id = "sound_list"; 761 | document.body.appendChild(soundsList); 762 | soundsList.innerHTML = ""; 763 | 764 | ["notif_mp_new", "notif_new", "mp_new"].forEach(function(element) { 765 | //debugger; 766 | var exists = document.getElementById("audio_" + element) !== null, 767 | sound = exists ? document.getElementById("audio_" + element) : new Audio(); 768 | sound.src = chrome.extension.getURL("/sounds/" + soundpack.folder + "/" + soundpack.sounds[element]); 769 | sound.id = "audio_" + element; 770 | sound.autoplay = false; 771 | sound.load(); 772 | !exists && soundsList.appendChild(sound); 773 | }, this); 774 | }, 775 | 776 | /** 777 | * Play sound 778 | * @param {Object} options Which sound should be played? 779 | */ 780 | playSound: function(options) { 781 | var sound; 782 | if(options.notification && options.mp) { 783 | sound = document.getElementById("audio_notif_mp_new"); 784 | } 785 | else if(options.notification) { 786 | sound = document.getElementById("audio_notif_new"); 787 | } 788 | else if(options.mp) { 789 | sound = document.getElementById("audio_mp_new"); 790 | } 791 | else { 792 | return; 793 | } 794 | 795 | if(sound) { 796 | sound.pause(); 797 | sound.currentTime = 0; 798 | sound.play(); 799 | } 800 | }, 801 | 802 | /** 803 | * Set new notif callback 804 | * @param {Function} callback 805 | */ 806 | setNewNotifCallback: function(callback) { 807 | if(typeof callback == "function") { 808 | this.newNotifCallback = callback; 809 | } 810 | else { 811 | this.newNotifCallback = undefined; 812 | } 813 | }, 814 | 815 | /** 816 | * Set remove notif callback 817 | * @param {Function} callback 818 | */ 819 | setRemoveNotifCallback: function(callback) { 820 | if(typeof callback == "function") { 821 | this.removeNotifCallback = callback; 822 | } 823 | else { 824 | this.removeNotifCallback = undefined; 825 | } 826 | }, 827 | 828 | /** 829 | * Update badge 830 | */ 831 | updateBadge: function() { 832 | var notifs = this.notifications, 833 | len = notifs.length, 834 | totMP = 0; 835 | for (var i=0; i<len; i++) { 836 | if (notifs[i].type == "mp") { 837 | totMP++; 838 | } 839 | } 840 | 841 | var badgeTexte = (totMP > 0) ? totMP.toString() + " - " : ""; 842 | badgeTexte += (len-totMP > 0) ? (len-totMP).toString() : ((totMP > 0) ? "0" : ""); 843 | chrome.browserAction.setBadgeText({text: badgeTexte}); 844 | chrome.browserAction.enable(); 845 | }, 846 | 847 | /** 848 | * Open a ZdS page 849 | * @param {String} _url The url to open 850 | * @param {Boolean} remember 851 | */ 852 | openZdS: function(_url, remember) { 853 | var url = this.url + _url, 854 | self = this; 855 | 856 | chrome.windows.getCurrent({ populate:true }, function(currentWindow) { 857 | var tab = false; 858 | for(var i in currentWindow.tabs) { 859 | if(currentWindow.tabs[i].active) { 860 | tab = currentWindow.tabs[i]; 861 | break; 862 | } 863 | } 864 | 865 | if(!self.getOptions("openInNewTab") && tab && tab.url !== undefined && tab.url.indexOf("zestedesavoir.com") != -1 && tab.url.indexOf("zestedesavoir.com") < 14) { 866 | if(remember) { 867 | self.alertTabId.push(tab.id); //on ajout l'id du tab 868 | } 869 | chrome.tabs.update(tab.id, { url: url }); 870 | } 871 | else { 872 | chrome.tabs.create({ 873 | 'url': url, 874 | 'active': false 875 | }, function(tab){ 876 | if(remember) { 877 | self.alertTabId.push(tab.id); //on ajout l'id du tab 878 | } 879 | }); 880 | } 881 | }); 882 | } 883 | }; 884 | 885 | 886 | /** @global */ 887 | window.theNotificator = new Notificateur(); --------------------------------------------------------------------------------