...
 
Commits (3)
......@@ -2,6 +2,11 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
<a name="0.4.0"></a>
# [0.4.0](https://gitlab.com/pikkumyy/iltapeto/compare/v0.3.0...v0.4.0) (2018-10-31)
<a name="0.3.0"></a>
# [0.3.0](https://github.com/pikkumyy/iltapeto/compare/v0.2.18...v0.3.0) (2018-06-04)
......
{
"name": "iltapeto",
"version": "0.3.0",
"version": "0.4.0",
"license": "MIT",
"author": "Professor Chaos",
"description": "Iltapeto tekee Iltalehden lueskelusta mukavampaa",
......@@ -17,11 +17,16 @@
"clean": "xt-clean",
"release": "git add . && git commit && standard-version"
},
"dependencies": {
"bayes-classifier": "^0.0.5",
"dexie": "^2.0.4"
},
"devDependencies": {
"cws-publish": "^1.0.13",
"pm-components": "^0.1.4",
"pm-extension-cli": "^0.7.11",
"standard-version": "^4.4.0"
"standard-version": "^4.4.0",
"sync-request": "^6.0.0"
},
"babel": {
"presets": [
......@@ -39,7 +44,7 @@
},
{
"name": "content_end",
"src": "./src/content/onend.js"
"src": "./src/onend.js"
}
],
"scss_bundles": [
......
import {PageActivator, ContextMenu} from 'pm-components';
import {ContextMenu} from 'pm-components';
import Dexie from 'dexie';
import Analyzer from './content/analyzer';
import Db from './content/db';
(() => new PageActivator({pageUrl: {hostEquals: 'www.iltalehti.fi'}}))();
(() => new ContextMenu())();
(() => new Analyzer(new Db(Dexie)))();
class DeclarativeContent {
constructor(PageStateMatcher, Actions) {
window.chrome.runtime.onInstalled.addListener(
() => {
window.chrome.declarativeContent.onPageChanged.removeRules(undefined, () => {
window.chrome.declarativeContent.onPageChanged.addRules([{
conditions: [
new window.chrome.declarativeContent
.PageStateMatcher(PageStateMatcher)
],
actions: Actions
}]);
});
});
}
}
(() => new DeclarativeContent({pageUrl: {hostEquals: 'www.iltalehti.fi'}},
[new window.chrome.declarativeContent.ShowPageAction()]))();
let BayesClassifier = require('bayes-classifier');
let classifier = new BayesClassifier();
class Analyzer {
constructor(db) {
this.db = db;
window.chrome.runtime.onMessage.addListener((r, s, c) => this.messageListener(r, s, c));
this.train();
}
messageListener(request, sender, sendResponse) {
if (request.setLike) {
const {url, like, text} = request.setLike;
this.db.setValue(url, like, text);
}
if (request.getLike) {
const text = request.getLike;
if (!text) return false;
let resp = {
classify: classifier.classify(text),
classifications: classifier.getClassifications(text)
};
sendResponse(resp);
}
return true;
}
train() {
this.db.likes().then(likes => {
this.db.dislikes().then(dislikes => {
const llikes = likes.map(l => {
return l.text;
});
const dlikes = dislikes.map(l => {
return l.text;
});
// console.log('likes ' + JSON.stringify(llikes), 'dislikes ' + JSON.stringify(dlikes));
classifier.addDocuments(llikes, 'positive');
classifier.addDocuments(dlikes, 'negative');
classifier.train();
});
});
}
}
export default Analyzer;
export default class DB {
constructor(Dexie) {
this.db = new Dexie('db');
this.db.version(1).stores({
likes: 'id,like,text'
});
}
likes() {
const {db} = this;
return new Promise(function (resolve, reject) {
db.likes.where('like').equals(1).toArray().then(resolve);
});
};
dislikes() {
const {db} = this;
return new Promise(function (resolve, reject) {
db.likes.where('like').equals(0).toArray().then(resolve);
});
};
static normalize(text) {
return !text || text.replace(/\n/, ' ');
}
setValue(url, likeValue, text) {
this.db.likes.put({id: url, like: likeValue ? 1 : 0, text: DB.normalize(text)});
};
}
......@@ -3,5 +3,7 @@
"listIcon": "M10,16L5,11L6.41,9.58L10,13.17L17.59,5.58L19,7M19,1H5C3.89,1 3,1.89 3,3V15.93C3,16.62 3.35,17.23 3.88,17.59L12,23L20.11,17.59C20.64,17.23 21,16.62 21,15.93V3C21,1.89 20.1,1 19,1Z",
"add": "M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z",
"remove": "M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z",
"like": "M23,10C23,8.89 22.1,8 21,8H14.68L15.64,3.43C15.66,3.33 15.67,3.22 15.67,3.11C15.67,2.7 15.5,2.32 15.23,2.05L14.17,1L7.59,7.58C7.22,7.95 7,8.45 7,9V19A2,2 0 0,0 9,21H18C18.83,21 19.54,20.5 19.84,19.78L22.86,12.73C22.95,12.5 23,12.26 23,12V10M1,21H5V9H1V21Z",
"dislike": "M19,15H23V3H19M15,3H6C5.17,3 4.46,3.5 4.16,4.22L1.14,11.27C1.05,11.5 1,11.74 1,12V14A2,2 0 0,0 3,16H9.31L8.36,20.57C8.34,20.67 8.33,20.77 8.33,20.88C8.33,21.3 8.5,21.67 8.77,21.94L9.83,23L16.41,16.41C16.78,16.05 17,15.55 17,15V5C17,3.89 16.1,3 15,3Z",
"svgNS": "http://www.w3.org/2000/svg"
}
......@@ -31,13 +31,12 @@ iframe[src*="monster"] {
.handled {
position: relative;
overflow: hidden;
.articleLikeButton,
.readToggleButton {
cursor: pointer;
z-index: 2147483647;
position: absolute;
border-radius: 50%;
top: 10px;
right: 10px;
width: 30px;
height: 30px;
opacity: 0;
......@@ -55,10 +54,41 @@ iframe[src*="monster"] {
}
}
}
.readToggleButton {
top: 10px;
right: 10px;
}
.articleLikeButton {
top: 50px;
right: 10px;
svg {
margin: 6px;
width: 18px;
height: 18px;
}
}
.articleLikeButton.dislike {
top: 90px;
}
}
.classifications {
position: absolute;
top: 5px;
left: 10px;
opacity: 0;
transition: opacity 0.2s ease-in;
background: #eee;
color: #333;
font-size: 12px;
padding: 4px 8px;
border-radius: 3px;
}
.handled:hover .articleLikeButton,
.handled .readToggleButton.on-list,
.handled:hover .readToggleButton {
.handled:hover .readToggleButton,
.handled:hover .classifications {
opacity: 1;
}
......
import Utils from '../content/utils';
import {like, dislike, svgNS} from '../content/icons.json';
let likes = [], dislikes = [];
const minClassificationValue = 0.001;
/**
* @class
* @description indicate when user likes/dislikes article
*
* Save likes and dislikes as training examples in database
*/
class LikeArticle {
constructor() {
Utils.articleWatcher(this.articleHandler.bind(this));
}
/**
* Change button background
* @param button
* @param onList
*/
static setButtonStyle(button, onList) {
if (onList) {
button.style.background = '#5d9eff';
} else {
button.style.background = '#444';
}
}
/**
* @description Add button to each readable article
* that allows liking or disliking article
* @returns {Element} - like buttons
*/
static createArticleToggleButton(onList, likeIt, iconPath) {
let button = document.createElement('div'),
svg = document.createElementNS(svgNS, 'svg'),
path = document.createElementNS(svgNS, 'path');
svg.appendChild(path);
button.appendChild(svg);
svg.setAttribute('viewBox', '0 0 24 24');
button.className += (' articleLikeButton' + (onList ?
' on-list ' : ' ') + (likeIt ? 'like' : 'dislike'));
path.setAttribute('d', iconPath);
LikeArticle.setButtonStyle(button, onList);
return button;
}
/**
* @description Click handler when clicking article toggle buttons
* @param {Event} e - click event
* @param {String} url - article url
* @param {Element} buttonUp - like button reference
* @param {Element} buttonDown - dislike button reference
*/
static onLikeArticle(e, url, buttonUp, buttonDown) {
e.stopPropagation();
e.preventDefault();
buttonUp.className += ' on-list';
LikeArticle.setButtonStyle(buttonUp, true);
buttonDown.classList.remove('on-list');
LikeArticle.setButtonStyle(buttonDown, false);
if (likes.indexOf(url) < 0) likes.push(url);
if (dislike.indexOf(url) >= 0) dislikes.splice(dislike.indexOf(url), 1);
}
/**
* @description Click handler when clicking article toggle buttons
* @param {Event} e - click event
* @param {String} url - article url
* @param {Element} buttonUp - like button reference
* @param {Element} buttonDown - dislike button reference
* @param {Boolean} likeIt - like/dislike value
*/
static onDislikeArticle(e, url, buttonUp, buttonDown, likeIt) {
e.stopPropagation();
e.preventDefault();
buttonUp.classList.remove('on-list');
LikeArticle.setButtonStyle(buttonUp, false);
buttonDown.className += ' on-list';
LikeArticle.setButtonStyle(buttonDown, true);
if (dislikes.indexOf(url) < 0) dislikes.push(url);
if (likes.indexOf(url) >= 0) likes.splice(likes.indexOf(url), 1);
}
/**
* @description DOM change listener to detect presence of articles
* on current page
* @param {Element} article - DOM element that represents single article
*/
articleHandler(article) {
if (article.className.indexOf('classify') > -1) {
return;
}
article.className += ' classify';
let anchor = article.querySelectorAll('a[href]');
if (anchor.length) {
let url = anchor[0].href;
let onLikeList = likes.indexOf(url) > -1;
let onDislikeList = dislikes.indexOf(url) > -1;
let likeButton = LikeArticle.createArticleToggleButton(onLikeList, true, like);
let dislikeButton = LikeArticle.createArticleToggleButton(onDislikeList, false, dislike);
const text = article.innerText + '';
likeButton.onclick = (e) => {
LikeArticle.onLikeArticle(e, url, likeButton, dislikeButton);
LikeArticle.recordValue(text, url, true);
};
dislikeButton.onclick = (e) => {
LikeArticle.onDislikeArticle(e, url, likeButton, dislikeButton);
LikeArticle.recordValue(text, url, false);
};
if (text) {
LikeArticle.getValue(article, text);
}
article.append(likeButton);
article.append(dislikeButton);
}
}
static recordValue(text, url, like) {
window.chrome.runtime.sendMessage({
setLike: {text: text, url: url, like: like}
});
}
static getValue(article, text) {
window.chrome.runtime.sendMessage({
getLike: text
}, resp => {
let positiveValue = '', negativeValue = '';
let p = document.createElement('pre');
let showClassification = false;
resp.classifications.map(e => {
if (e.label === 'positive') {
positiveValue = '+' + e.value.toFixed(3);
showClassification = e.value > minClassificationValue;
}
if (e.label === 'negative') {
negativeValue = '(-' + e.value.toFixed(3) + ')';
showClassification = showClassification ||
e.value > minClassificationValue;
}
});
if (!showClassification) return;
if (resp.classify === 'negative') {
article.style.borderTop = '5px solid #e84359';
}
if (resp.classify === 'positive') {
article.style.borderTop = '5px solid #1bc44e';
}
p.innerText = [positiveValue, negativeValue].join(' ');
p.className += ' classifications';
article.appendChild(p);
});
}
}
export default LikeArticle;
import Utils from './utils';
import Storage from './storage';
import {svgNS, listIcon, add, remove} from './icons.json';
import Utils from '../content/utils';
import Storage from '../content/storage';
import {svgNS, listIcon, add, remove} from '../content/icons.json';
let list = [];
......
import Utils from './utils';
import Utils from '../content/utils';
/**
* @class
......
import Utils from './utils';
import Utils from '../content/utils';
/**
* @class
......
......@@ -2,8 +2,8 @@
"name": "__MSG_appName__",
"short_name": "__MSG_appShortName__",
"description": "__MSG_appDesc__",
"version": "0.2.19",
"version_name": "0.2.19",
"version": "0.3.0",
"version_name": "0.3.0",
"manifest_version": 2,
"default_locale": "fi",
"minimum_chrome_version": "46",
......
import Utils from './utils';
import Video from './video';
import RemoveAds from './removead';
import ReadingList from './readingList';
import Utils from './content/utils';
import Video from './features/video';
import RemoveAds from './features/removead';
import LikeArticle from './features/likeArticle';
import ReadingList from './features/readingList';
Utils.flexit('page-load', encodeURI(window.location.href));
(() => new ReadingList())();
(() => new Video())();
(() => new RemoveAds())();
(() => new LikeArticle())();
import ReadingList from '../src/content/readingList';
import Video from '../src/content/video';
import RemoveAds from '../src/content/removead';
import Storage from '../src/content/storage';
import {MutationObserver, MyWindow} from './mock';
const fs = require('fs');
const mainPage = fs.readFileSync('./test/templates/main.html', 'utf8');
const articlePage = fs.readFileSync('./test/templates/article.html', 'utf8');
import ReadingList from '../src/features/readingList';
import RemoveAds from '../src/features/removead';
import Video from '../src/features/video';
let request = require('sync-request');
let mainPage = request('GET', 'http://iltalehti.fi').body.toString('utf-8');
const articlePage = request('GET',
'https://www.iltalehti.fi/iltvluontojaelaimet/02ef74d8-b2d1-4b37-9978-d348a43ec13f_v6.shtml')
.body.toString('utf-8');
// console.log(mainPage.length, articlePage.length);
describe('Content', function () {
......@@ -43,70 +48,70 @@ describe('Content', function () {
delete global.paused;
});
it('stops video autoplay', function () {
sandbox.spy(Video, 'manageVideos');
sandbox.spy(MutationObserver.prototype, 'disconnect');
document.documentElement.innerHTML = articlePage;
(() => new Video())();
expect(Video.manageVideos.notCalled, 'before firing').to.be.true;
simulateMutation();
expect(Video.manageVideos.calledOnce, 'after firing').to.be.true;
let handledCount = document.querySelectorAll('.iltv-video-player[data-ip-tracking]').length;
expect(handledCount, 'video elements handled').to.be.above(0);
simulateMutation();
let handledNodes = document.querySelectorAll('.iltv-video-player[data-ip-tracking]');
expect(handledNodes.length, 'elements handled exactly once').to.equal(handledCount);
dispatchEvent(handledNodes[0], mouseEvent('mousedown'));
expect(MutationObserver.prototype.disconnect.calledOnce, 'removes observer').to.be.true;
});
it('hides yhteistyö artices', function () {
(() => new RemoveAds())();
let initialCount = removedCount();
expect(initialCount, 'before removal').to.equal(0);
simulateMutation();
let newCount = removedCount();
expect(newCount, 'after mutation').to.be.above(initialCount);
simulateMutation();
expect(removedCount(), 'after 2nd mutation').to.equal(newCount);
});
it('add and remove articles from reading list', function () {
sandbox.spy(Storage, 'save');
chrome.storage.local.get.yields({
readingList: ['/politiikka/201806032200988376_pi.shtml']
});
(() => new ReadingList())();
simulateMutation();
let firstToggleButton = document.getElementsByClassName('readToggleButton')[0];
let handledCount = document.getElementsByClassName('handled').length;
dispatchEvent(firstToggleButton, mouseEvent('click'));
expect(Storage.save.calledOnce, 'save called after remove').to.be.true;
expect(ReadingList.button.className, 'no items').to.not.match(/has-items/);
dispatchEvent(firstToggleButton, mouseEvent('click'));
expect(Storage.save.calledTwice, 'save called after add').to.be.true;
expect(ReadingList.button.className, 'with items').to.match(/has-items/);
simulateMutation();
expect(document.getElementsByClassName('handled').length,
'mutation handled once').to.equal(handledCount);
});
it('reading list navigates to articles', function () {
(() => new ReadingList())();
simulateMutation();
dispatchEvent(document.getElementsByClassName('readToggleButton')[0], mouseEvent('click'));
ReadingList.button.onclick();
expect(navigationState, 'navigation occurred').to.equal(1);
});
// it('stops video autoplay', function () {
// sandbox.spy(Video, 'manageVideos');
// sandbox.spy(MutationObserver.prototype, 'disconnect');
// document.documentElement.innerHTML = articlePage;
// (() => new Video())();
//
// expect(Video.manageVideos.notCalled, 'before firing').to.be.true;
// simulateMutation();
//
// expect(Video.manageVideos.calledOnce, 'after firing').to.be.true;
// let handledCount = document.querySelectorAll('.iltv-video-player[data-ip-tracking]').length;
// expect(handledCount, 'video elements handled').to.be.above(0);
//
// simulateMutation();
// let handledNodes = document.querySelectorAll('.iltv-video-player[data-ip-tracking]');
// expect(handledNodes.length, 'elements handled exactly once').to.equal(handledCount);
//
// dispatchEvent(handledNodes[0], mouseEvent('mousedown'));
// expect(MutationObserver.prototype.disconnect.calledOnce, 'removes observer').to.be.true;
// });
// it('hides yhteistyö artices', function () {
// (() => new RemoveAds())();
// let initialCount = removedCount();
// expect(initialCount, 'before removal').to.equal(0);
// simulateMutation();
// let newCount = removedCount();
// expect(newCount, 'after mutation').to.be.above(initialCount);
// simulateMutation();
// expect(removedCount(), 'after 2nd mutation').to.equal(newCount);
// });
// it('add and remove articles from reading list', function () {
// sandbox.spy(Storage, 'save');
// chrome.storage.local.get.yields({
// readingList: ['/politiikka/201806032200988376_pi.shtml']
// });
// (() => new ReadingList())();
// simulateMutation();
//
// let firstToggleButton = document.getElementsByClassName('readToggleButton')[0];
// let handledCount = document.getElementsByClassName('handled').length;
//
// dispatchEvent(firstToggleButton, mouseEvent('click'));
// expect(Storage.save.calledOnce, 'save called after remove').to.be.true;
// expect(ReadingList.button.className, 'no items').to.not.match(/has-items/);
//
// dispatchEvent(firstToggleButton, mouseEvent('click'));
// expect(Storage.save.calledTwice, 'save called after add').to.be.true;
// expect(ReadingList.button.className, 'with items').to.match(/has-items/);
//
// simulateMutation();
// expect(document.getElementsByClassName('handled').length,
// 'mutation handled once').to.equal(handledCount);
//
// });
// it('reading list navigates to articles', function () {
// (() => new ReadingList())();
//
// simulateMutation();
// dispatchEvent(document.getElementsByClassName('readToggleButton')[0], mouseEvent('click'));
//
// ReadingList.button.onclick();
// expect(navigationState, 'navigation occurred').to.equal(1);
// });
});
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.