Commit acdcb3c5 authored by Ricardo J. Mendez's avatar Ricardo J. Mendez

Merge branch 'release/0.9.0'

parents df301fdb 5d9a783b
(defproject relevance-chrome "0.3.0"
(defproject relevance-chrome "0.9.0"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.7.0"]
[org.clojure/clojurescript "1.7.145"]
[org.clojure/clojurescript "1.7.170"]
[org.clojure/core.async "0.2.371"]
[com.cognitect/transit-cljs "0.8.225"]
[cljsjs/react-bootstrap "0.25.2-0" :exclusions [org.webjars.bower/jquery]]
[khroma "0.2.0-SNAPSHOT"]
[lein-doo "0.1.6-SNAPSHOT"]
[khroma "0.3.0-SNAPSHOT"]
[prismatic/dommy "1.1.0"]
[re-frame "0.4.1" :exclusions [cljsjs/react]]
[re-frame "0.5.0" :exclusions [cljsjs/react]]
]
:source-paths ["src/ui" "src/common" "src/background" "src/content"]
:test-paths ["test"]
:plugins [[lein-cljsbuild "1.1.0"]
[lein-chromebuild "0.3.0"]
:plugins [[lein-cljsbuild "1.1.1"]
[lein-chromebuild "0.3.1"]
[lein-doo "0.1.6-SNAPSHOT"]
]
......@@ -47,11 +46,12 @@
}
:chromebuild {:resource-paths ["resources/js"
"resources/html"
"resources/images"
"resources/css"]
:target-path "target/unpacked"}
:chromebuild {:resource-paths ["resources/js"
"resources/dashboard"
"resources/images"
"resources/css"]
:preserve-folders true
:target-path "target/unpacked"}
:profiles {:release
{:cljsbuild
......@@ -63,14 +63,15 @@
:ui {:compiler {:optimizations :advanced
:pretty-print false}}}}}
:test
{:cljsbuild
{:builds
{:test
{:source-paths ["test" "src/common"]
:compiler {:output-to "target/js/test/relevance-tests.js"
:output-dir "target/js/test"
:main relevance.test.runner
:optimizations :none
:pretty-print :true}}}}
{:dependencies [[lein-doo "0.1.6-SNAPSHOT"]]
:cljsbuild
{:builds
{:test
{:source-paths ["test" "src/common"]
:compiler {:output-to "target/js/test/relevance-tests.js"
:output-dir "target/js/test"
:main relevance.test.runner
:optimizations :none
:pretty-print :true}}}}
}
})
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.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<link rel="icon" type="image/png" href="favicon.png">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<title>Relevance - Dashboard</title>
<meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' name='viewport'/>
<meta name="viewport" content="width=device-width"/>
<!-- Bootstrap core CSS -->
<link href="bootstrap.min.css" rel="stylesheet"/>
<!-- Light Bootstrap Table core CSS -->
<link href="light-bootstrap-dashboard.css" rel="stylesheet"/>
<!-- Fonts and icons -->
<link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<link href='http://fonts.googleapis.com/css?family=Roboto:400,700,300' rel='stylesheet' type='text/css'>
<link href="pe-icon-7-stroke.css" rel="stylesheet"/>
<!-- Google Analytics -->
<script type="text/javascript" src="init-analytics.js"></script>
<!-- End Google Analytics -->
</head>
<body>
<div class="wrapper">
<div class="sidebar" data-color="purple" data-image="relevance-0.4-background.jpg">
<!-- you can change the color of the sidebar using: data-color="blue | azure | green | orange | red | purple" -->
<div class="sidebar-wrapper">
<div class="logo">
<a href="http://www.numergent.com" class="simple-text">
Relevance
</a>
</div>
<div id="nav-left">
<!-- Left navbar goes here -->
</div>
</div>
</div>
<div class="main-panel">
<nav class="navbar navbar-default navbar-fixed">
<div class="container-fluid" id="nav-top">
<!-- top navbar goes here -->
</div>
</nav>
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div id="main-section">
</div>
</div>
</div>
</div>
</div>
<footer class="footer">
<div class="container-fluid">
<p class="copyright pull-right">
&copy; 2015 <a href="http://numergent.com" target="_blank">Numergent Limited</a>
</p>
</div>
</footer>
</div>
</div>
</body>
<!-- Core JS Files -->
<script src="jquery-1.11.3.min.js" type="text/javascript"></script>
<script src="bootstrap.min.js" type="text/javascript"></script>
<script src="light-bootstrap-dashboard.js"></script>
<script type="text/javascript" src="ui.js"></script>
</html>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
var searchVisible = 0;
var transparent = true;
var transparentDemo = true;
var fixedTop = false;
var navbar_initialized = false;
$(document).ready(function(){
window_width = $(window).width();
// check if there is an image set for the sidebar's background
lbd.checkSidebarImage();
// Init navigation toggle for small screens
if(window_width <= 991){
lbd.initRightMenu();
}
// Activate the tooltips
$('[rel="tooltip"]').tooltip();
// Activate the switches with icons
if($('.switch').length != 0){
$('.switch')['bootstrapSwitch']();
}
// Activate regular switches
if($("[data-toggle='switch']").length != 0){
$("[data-toggle='switch']").wrap('<div class="switch" />').parent().bootstrapSwitch();
}
$('.form-control').on("focus", function(){
$(this).parent('.input-group').addClass("input-group-focus");
}).on("blur", function(){
$(this).parent(".input-group").removeClass("input-group-focus");
});
});
// activate collapse right menu when the windows is resized
$(window).resize(function(){
if($(window).width() <= 991){
lbd.initRightMenu();
}
});
lbd = {
misc:{
navbar_menu_visible: 0
},
checkSidebarImage: function(){
$sidebar = $('.sidebar');
image_src = $sidebar.data('image');
if(image_src !== undefined){
sidebar_container = '<div class="sidebar-background" style="background-image: url(' + image_src + ') "/>'
$sidebar.append(sidebar_container);
}
},
initRightMenu: function(){
if(!navbar_initialized){
$navbar = $('nav').find('.navbar-collapse').first().clone(true);
$sidebar = $('.sidebar');
sidebar_color = $sidebar.data('color');
$logo = $sidebar.find('.logo').first();
logo_content = $logo[0].outerHTML;
ul_content = '';
$navbar.attr('data-color',sidebar_color);
// add the content from the sidebar to the right menu
content_buff = $sidebar.find('.nav').html();
ul_content = ul_content + content_buff;
//add the content from the regular header to the right menu
$navbar.children('ul').each(function(){
content_buff = $(this).html();
ul_content = ul_content + content_buff;
});
ul_content = '<ul class="nav navbar-nav">' + ul_content + '</ul>';
navbar_content = logo_content + ul_content;
$navbar.html(navbar_content);
$('body').append($navbar);
background_image = $sidebar.data('image');
if(background_image != undefined){
$navbar.css('background',"url('" + background_image + "')")
.removeAttr('data-nav-image')
.addClass('has-image');
}
$toggle = $('.navbar-toggle');
$navbar.find('a').removeClass('btn btn-round btn-default');
$navbar.find('button').removeClass('btn-round btn-fill btn-info btn-primary btn-success btn-danger btn-warning btn-neutral');
$navbar.find('button').addClass('btn-simple btn-block');
$toggle.click(function (){
if(lbd.misc.navbar_menu_visible == 1) {
$('html').removeClass('nav-open');
lbd.misc.navbar_menu_visible = 0;
$('#bodyClick').remove();
setTimeout(function(){
$toggle.removeClass('toggled');
}, 400);
} else {
setTimeout(function(){
$toggle.addClass('toggled');
}, 430);
div = '<div id="bodyClick"></div>';
$(div).appendTo("body").click(function() {
$('html').removeClass('nav-open');
lbd.misc.navbar_menu_visible = 0;
$('#bodyClick').remove();
setTimeout(function(){
$toggle.removeClass('toggled');
}, 400);
});
$('html').addClass('nav-open');
lbd.misc.navbar_menu_visible = 1;
}
});
navbar_initialized = true;
}
}
}
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
timeout = null;
if (!immediate) func.apply(context, args);
}, wait);
if (immediate && !timeout) func.apply(context, args);
};
};
This diff is collapsed.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Relevance - Personal tab analysis</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<link rel="shortcut icon" href="/favicon.png">
<link rel="stylesheet" href="/bootstrap.min.css" media="screen">
</head>
<body>
<div class="bs-component" id="navbar">
</div>
<div class="container">
<div id="nav-bar">
</div>
<div id="main-section"></div>
<footer>
<div class="row">
<div class="col-sm-6 col-sm-offset-3" style="text-align: center">
<p>&copy <a href="http://numergent.com" target="_blank">Numergent Limited</a></p>
</div>
</div>
</footer>
</div>
<script type="text/javascript" src="/ui.js"></script>
</body>
</html>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-6610761-4', 'auto');
ga('set', 'appName','Relevance-WebApp');
ga('set', 'checkProtocolTask', function(){});
ga('send', 'screenview', {screenName: "intro"});
{
"name": "Relevance - Personal tab organizer",
"name": "Relevance - Smart tab organizer",
"short_name": "Relevance",
"version": "0.3",
"version": "0.9.0",
"browser_action": {
"default_title": "Show history",
"default_title": "Organize tabs",
"default_icon": {
"19": "icon19.png",
"38": "icon38.png"
......@@ -30,5 +30,6 @@
"js": ["content.js"]
}
],
"content_security_policy": "script-src 'self' https://www.google-analytics.com; object-src 'self'",
"manifest_version": 2
}
\ No newline at end of file
......@@ -3,7 +3,7 @@
[relevance.data :as data]
[relevance.io :as io]
[relevance.migrations :as migrations]
[relevance.utils :refer [on-channel url-key host-key hostname is-http?]]
[relevance.utils :refer [on-channel url-key host-key hostname is-http? ms-day]]
[khroma.alarms :as alarms]
[khroma.context-menus :as menus]
[khroma.idle :as idle]
......@@ -32,6 +32,7 @@
(defn now [] (.now js/Date))
;;;;-------------------------------------
;;;; Functions
;;;;-------------------------------------
......@@ -93,7 +94,7 @@
score (if (is-http? url) total (* total non-http-penalty))
]
(or (when tab-time score)
(- 2000 idx))))
(- idx))))
(defn sort-tabs! [window-id data]
(go
......@@ -136,8 +137,24 @@
(register-handler
:data-load
(fn [app-state [_ loaded]]
(let [new-data (migrations/migrate-to-latest loaded)]
(console/trace "Data load" loaded "migrated" new-data)
(let [migrated (migrations/migrate-to-latest loaded)
t (now)
new-urls (->
(:url-times migrated)
(data/time-clean-up (- t (* 7 ms-day)) 30)
(data/time-clean-up (- t (* 14 ms-day)) 90)
(data/time-clean-up (- t (* 30 ms-day)) 300))
site-data (:site-times migrated)
new-sites (if (not= new-urls (:url-times migrated))
(->>
;; Accumulate site times but preserve the icons we had before
(data/accumulate-site-times new-urls)
(map #(vector (key %)
(assoc (val %) :icon (get-in site-data [(key %) :icon]))))
(into {}))
site-data)
new-data (assoc migrated :url-times new-urls :site-times new-sites)]
; (console/trace "Data load" loaded "migrated" new-data)
;; Save the migrated data we just received
(io/save new-data)
;; Process the suspend info
......@@ -165,7 +182,7 @@
(register-handler
:handle-activation
(fn [app-state [_ tab start-time]]
(console/trace "Handling activation" tab)
; (console/trace "Handling activation" tab)
(if tab
(assoc app-state
:active-tab
......@@ -182,7 +199,7 @@
;; We get two parameters: the tab, and optionally the time at which it
;; was deactivated (which defaults to now)
[app-state [_ tab end-time]]
(console/trace " Deactivating " tab)
; (console/trace " Deactivating " tab)
(when (< 0 (:start-time tab))
(dispatch [:track-time tab (- (or end-time (now))
(:start-time tab))]))
......@@ -198,7 +215,7 @@
(get-in app-state [:app-state :idle])
(:active-tab app-state))
]
(console/trace " State changed to " state action)
; (console/trace " State changed to " state action)
;; We only store the idle tabs on the app state if we actually idled any.
;; That way we avoid losing the originally stored idled tabs when we
;; first go from active->idle and then from idle->locked (the first one
......@@ -315,10 +332,10 @@
:track-time
(fn [app-state [_ tab time]]
(let [data (:data app-state)
url-times (data/track-url-time (or (:url-times data) {}) tab time (now))
site-times (data/track-site-time (or (:site-times data) {}) tab time (now))
url-times (data/track-url-time (or (:url-times data) {}) tab (quot time 1000) (now))
site-times (data/track-site-time (or (:site-times data) {}) tab (quot time 1000) (now))
new-data (assoc data :url-times url-times :site-times site-times)]
(console/trace time " milliseconds spent at " tab)
; (console/trace time " milliseconds spent at " tab)
(io/save new-data)
(assoc app-state :data new-data)
)))
......@@ -326,7 +343,7 @@
(register-handler
::window-focus
(fn [app-state [_ {:keys [windowId]}]]
(console/trace "Current window" windowId)
; (console/trace "Current window" windowId)
(let [active-tab (:active-tab app-state)
replacing? (not= windowId (:windowId active-tab))
is-none? (= windowId windows/none)]
......@@ -340,8 +357,7 @@
(go (dispatch [:handle-activation
(first (<! (tabs/query {:active true :windowId windowId})))])))
(assoc app-state :active-tab nil))
app-state)
)
app-state))
))
......@@ -355,7 +371,7 @@
(go-loop
[connections (runtime/on-connect)]
(let [content (<! connections)]
(console/log "--> Background received" (<! content))
; (console/log "--> Background received" (<! content))
(>! content :background-ack)
(recur connections)))
)
......
......@@ -2,6 +2,31 @@
(:require [relevance.utils :refer [url-key host-key hostname]]))
(defn accumulate-site-times
"Accumulates the total time for a site from a hashmap of URL times"
[url-times]
(->>
(group-by #(hostname (:url %)) (vals url-times))
(remove #(empty? (key %)))
(map #(vector (host-key (key %))
(hash-map :host (key %)
:time (apply + (map :time (val %)))
:icon (:icon (first (val %))))
))
(into {})
)
)
(defn time-clean-up
"Removes from url-times all the items that are older than cut-off-ts
and which were viewed for less than min-seconds"
[url-times cut-off-ts min-seconds]
(into {} (remove #(and (< (:ts (val %))
cut-off-ts)
(< (:time (val %)) min-seconds))
url-times)))
(defn track-url-time
"Receives a url time database, a tab record and a time to track, and returns
new time database which is the result of adding the time to the URL. It also
......@@ -10,13 +35,14 @@
(let [url (or (:url tab) "")
id (url-key url)
url-item (or (get url-times id)
{:url url
:time 0
:timestamp 0})
track? (not= 0 id)
{:url url
:time 0
:ts 0})
track? (and (not= 0 id)
(< 0 time))
new-item (assoc url-item :time (+ (:time url-item) time)
:title (:title tab)
:timestamp timestamp)]
:ts timestamp)]
(if track?
(assoc url-times id new-item)
url-times)))
......@@ -31,13 +57,14 @@
(let [host (hostname (or (:url tab) ""))
id (host-key host)
site-item (or (get site-times id)
{:host host
:time 0
:timestamp 0})
track? (not= 0 id)
{:host host
:time 0
:ts 0})
track? (and (not= 0 id)
(< 0 time))
new-item (assoc site-item :time (+ (:time site-item) time)
:favIconUrl (:favIconUrl tab)
:timestamp timestamp)]
:icon (:favIconUrl tab)
:ts timestamp)]
(if track?
(assoc site-times id new-item)
site-times)))
\ No newline at end of file
(ns relevance.migrations
(:require [relevance.utils :refer [url-key host-key hostname]]))
(defn accumulate-site-times [url-times]
(->>
(group-by #(hostname (:url %)) (vals url-times))
(into {} (map #(vector (host-key (key %))
(hash-map :host (key %)
:time (apply + (map :time (val %)))
:favIconUrl (:favIconUrl (first (val %))))
)))
))
(:require
[clojure.set :refer [rename-keys]]
[relevance.data :refer [accumulate-site-times]]
[relevance.utils :refer [url-key host-key hostname]]))
(defn migrate
......@@ -24,13 +16,26 @@
(.-uuid (random-uuid))))
(assoc :data-version 1)
(assoc :url-times (into {} (map #(vector (key %)
(dissoc (val %) :favIconUrl))
(dissoc (val %) :favIconUrl :icon))
(:url-times data))))
(assoc :site-times (accumulate-site-times (:url-times data))))
1 (->
data
(assoc :data-version 2)
(assoc :site-times (accumulate-site-times (:url-times data))))
2 (let [url-times (into {}
(->>
(:url-times data)
(map #(vector (key %)
(-> (val %)
(assoc :time (quot (:time (val %)) 1000))
(rename-keys {:timestamp :ts}))))
(remove #(= 0 (:time (second %))))))]
(assoc data
:data-version 3
:url-times url-times
:site-times (accumulate-site-times url-times))
)
data
))
......
(ns relevance.utils
(:require [cljs.core.async :refer [<!]]
[clojure.string :refer [lower-case trim]]
[dommy.core :as dommy]
[cognitect.transit :as transit])
(:require-macros [cljs.core.async.macros :refer [go go-loop]]))
;;;;-------------------------------------
;;;; Values
;;;;-------------------------------------
(def ms-hour (* 60 60 1000))
(def ms-day 86400000)
(def ms-week (* 7 ms-day))
;;;;-------------------------------------
;;;; Functions
;;;;-------------------------------------
(defn on-channel
"Dispatches msg when there's content received on the channel returned by
function chan-f. Expects a dispatch function."
......@@ -31,7 +47,9 @@
(when url
(-> (dommy/create-element :a)
(dommy/set-attr! :href url)
(.-hostname))))
(.-hostname)
lower-case
trim)))
(defn protocol
"Returns the protocol for a URL"
......@@ -39,7 +57,8 @@
(when url
(-> (dommy/create-element :a)
(dommy/set-attr! :href url)
(.-protocol))))
(.-protocol)
(lower-case))))
(defn is-http?
"Returns true if the string starts with http: or https:"
......@@ -48,7 +67,9 @@
(some? (re-find #"\bhttps?:" (protocol url)))))
(defn host-key [host]
(hash-string host))