Commit 0e303136 authored by Ricardo J. Mendez's avatar Ricardo J. Mendez

Merge branch 'release/1.1.0'

parents 541b4869 b2f00b15
Pipeline #13327705 passed with stage
in 1 minute and 15 seconds
......@@ -4,9 +4,9 @@ Relevance is a smart tab organizer for Chrome, written in ClojureScript.
It’ll create a natural arrangement where the tabs you have spent the longest on, which are expected to be the most relevant, are placed first, and the ones you haven’t read at all are shunted to the end of your list.
[You can read more about it here](https://numergent.com/relevance/).
[You can read more about it here](https://numergent.com/relevance/), which includes a changelog.
This is Relevance 1.0.10.
This is Relevance 1.1.0.
# Building
......@@ -47,7 +47,7 @@ lein with-profile test doo
I'm using [Gitlab CI](https://gitlab.com/ricardojmendez/relevance/pipelines) to test Relevance.
[![build status](https://gitlab.com/ricardojmendez/relevance/badges/develop/build.svg)](https://gitlab.com/ricardojmendez/relevance/commits/develop)
[![build status](https://gitlab.com/Numergent/relevance/badges/develop/build.svg)](https://gitlab.com/Numergent/relevance/commits/develop)
I've had some issues with Travis builds failing to get dependencies, so I decided to deprecate it.
......
(defproject relevance-chrome "1.0.10"
(defproject relevance-chrome "1.1.0"
:license {:name "MIT License"
:url "https://tldrlegal.com/license/mit-license"}
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.542"]
[org.clojure/core.async "0.3.442"]
[org.clojure/clojurescript "1.9.946"]
[org.clojure/core.async "0.3.443"]
[com.cognitect/transit-cljs "0.8.239"]
[cljsjs/react-bootstrap "0.30.7-0"]
[cljsjs/react-bootstrap "0.31.0-0" :exclusions [cljsjs/react]]
[khroma "0.3.0"]
[prismatic/dommy "1.1.0"]
[re-frame "0.9.3" :exclusions [cljsjs/react]]]
[re-frame "0.10.1"]]
:source-paths ["src/ui" "src/common" "src/background" "src/content"]
:test-paths ["test"]
:plugins [[lein-cljsbuild "1.1.6"]
:plugins [[lein-cljsbuild "1.1.7"]
[org.clojars.ricardojmendez/lein-chromebuild "0.3.2"]
[lein-doo "0.1.7"]]
[lein-doo "0.1.8"]]
:doo {:build "test"
:alias {:default [:phantom]}}
:aliases {"test"
["do"
["clean"]
["with-profile" "test" "doo" "once"]]}
:cljsbuild {:builds
{:background
{:source-paths ["src/background" "src/common"]
......@@ -62,7 +67,7 @@
:ui {:compiler {:optimizations :advanced
:pretty-print false}}}}}
:test
{:dependencies [[lein-doo "0.1.7"]]
{:dependencies [[lein-doo "0.1.8"]]
:cljsbuild
{:builds
{:test
......
......@@ -33,7 +33,7 @@
<div class="sidebar" data-color="purple" data-image="relevance-0.4-background.jpg">
<div class="sidebar-wrapper">
<div class="logo">
<a href="http://www.numergent.com" class="simple-text">
<a href="https://www.numergent.com" class="simple-text">
Relevance
</a>
</div>
......
{
"name": "Relevance - Smart Tab Organizer",
"short_name": "Relevance",
"version": "1.0.10",
"version": "1.1.0",
"browser_action": {
"default_title": "Organize tabs",
"default_icon": {
......
......@@ -4,7 +4,7 @@
[relevance.data :as data]
[relevance.io :as io]
[relevance.migrations :as migrations]
[relevance.order :refer [time-score score-tabs]]
[relevance.order :refer [time-score sort-by-root]]
[relevance.utils :refer [on-channel url-key host-key hostname is-http? ms-day]]
[relevance.settings :refer [default-settings]]
[khroma.alarms :as alarms]
......@@ -101,10 +101,10 @@
(go
(let [{:keys [settings data]} app-state
{:keys [url-times site-times]} data
tabs (score-tabs (:tabs (<! (windows/get window-id)))
url-times
site-times
settings)]
tabs (sort-by-root (:tabs (<! (windows/get window-id)))
url-times
site-times
settings)]
(doseq [tab tabs]
(tabs/move (:id tab) {:index (:index tab)})))))
......@@ -285,7 +285,7 @@
(let [{:keys [message sender]} (keywordize-keys payload)
{:keys [action data]} message]
; (console/log "GOT INTERNAL MESSAGE" message "from" sender)
(condp = (keyword action)
(case (keyword action)
:reload-data (go (dispatch [:data-load (<! (io/load :data)) (<! (io/load :settings))]))
:delete-url (dispatch [:delete-url data])
(console/error "Nothing matched" message)))
......
(ns relevance.data
(:require [relevance.utils :refer [url-key is-http? host-key hostname]]
"Contains functions related to data tracking and accumulation.
It does not account for the actual ordering based on this data. You should
see `relevance.order` for that."
(:require [relevance.utils :refer [url-key is-http? host-key hostname root]]
[khroma.log :as console]))
......@@ -7,7 +10,14 @@
"Accumulates the total time for a site from a hashmap of URL times.
Returns a hashmap with the URL ID as the key, and the :time, :icon and
:host string on its value."
:host string on its value.
This function differentiates between hostnames on the different root domain.
This means that docs.gitlab.com and gitlab.com are accumulated separately.
While we could lump them together into one at this point, keeping them
separately will allows us to apply the same weight to pages on the same
hostname, which will lead to more natural ordering."
[url-times]
(->>
(group-by #(hostname (:url %)) (vals url-times))
......@@ -19,6 +29,19 @@
(into {})))
(defn accumulate-root-times
"Expects the hashmap resulting from `accumulate-site-times`, and returns a
new hashmap where times are accumulated by the root name.
This will let us prioritize the pages in the same root domain together,
while still keeping the per-site ordering."
[site-times]
(->> (group-by #(root (:host %)) (vals site-times))
(remove #(empty? (key %)))
(map #(vector (key %)
(apply + (map :time (val %)))))
(into {})))
(defn clean-up-by-time
......
......@@ -9,12 +9,15 @@
;;;; can just have a separate namespace for data transformations
(defn save-raw
"Saves the data raw, without converting it to transit first."
"Saves the data raw, without converting it to transit first. Will pass
the optional callback function it receives to storage/set for notification
when the operation has completed."
[id data callback]
(storage/set {id data} storage/local callback))
(defn save
"Saves our data on the extension's storage after converting it to transit."
"Saves our data on the extension's storage after converting it to transit.
Can optionally receive a callback function for when storage/set completes."
([id data]
(save id data nil))
([id data callback]
......
......@@ -9,7 +9,7 @@
"Migrates a data set from its version to the next one. Returns the same
data set if it cannot apply any migration."
[data]
(condp = (:data-version data)
(case (:data-version data)
nil (->
data
(assoc :instance-id (or (:instance-id data)
......@@ -41,7 +41,7 @@
(defn migrate-to-latest
"Takes a data set and interates on it until no more version migrations can be applied"
"Takes a data set and iterates on it until no more version migrations can be applied"
[data]
(loop [to-migrate data]
(let [migrated (migrate to-migrate)]
......
(ns relevance.order
(:require [relevance.utils :refer [on-channel url-key host-key hostname is-http? ms-day]]))
(:require [relevance.utils :refer [on-channel url-key host-key hostname root is-http? ms-day]]
[relevance.data :refer [accumulate-root-times]]))
;;;;------------------------------------
......@@ -15,43 +16,77 @@
;;;;------------------------------------
(defn time-score
"Returns a score for a tab based on the total time spent at both URLs and sites"
"Returns map containing a score for a tab based on the total time spent at
both a URL and the site the URL belongs to, as well as a flag indicating
if it's a priority tab."
[tab url-times site-times settings]
(let [url (:url tab)
idx (:index tab)
url-time (or (:time (get url-times (url-key url)))
0)
is-priority? (and (:sound-to-left? settings)
(:audible tab))
is-penalized? (and (not (is-http? url))
(not is-priority?))
tab-time (cond
;; Add an extra score if it's a priority URL
is-priority? (+ sound-extra-score idx)
;; If a URL is penalized, we want it to at least have a
;; value of 1, otherwise the tab time gets ignored and
;; we'd default to using the raw site time
is-penalized? (max url-time 1)
;; ... otherwise we just go with the raw URL time
:else url-time)
site-time (or (:time (get site-times (host-key (hostname url)))) 0)
total (+ tab-time site-time)
score (if is-penalized? (* total non-http-penalty) total)]
(or (when (pos? tab-time) score)
(- site-time idx))))
(defn score-tabs
"Returns a hashmap of the new tab ids and their indexes, based on a tab list and
the score function for time spent on urls and sites."
[tabs url-times site-times settings]
(->> tabs
(map #(assoc % :time (time-score % url-times site-times settings)))
(sort-by #(* -1 (:time %)))
(map-indexed #(hash-map :index %1
:id (:id %2)))))
(let [url (:url tab)
idx (:index tab)
url-time (or (:time (get url-times (url-key url)))
0)
priority? (and (:sound-to-left? settings)
(:audible tab))
penalized? (and (not (is-http? url))
(not priority?))
tab-time (cond
;; Add an extra score if it's a priority URL
priority? (+ sound-extra-score idx)
;; If a URL is penalized, disregard the time
;; and use its index (it'll get penalized later)
penalized? idx
;; ... otherwise we just go with the raw URL time
:else url-time)
host-time (or (:time (get site-times (host-key (hostname url)))) 0)
total (+ tab-time host-time)
score (if penalized? (* total non-http-penalty) total)]
;; A tab without positive time in it will use its index as a small
;; offset to the host time. That way pages from the same host are
;; lumped together, and new tabs are sorted by index.
{:score (or (when (pos? tab-time) score)
(+ host-time (* 0.1 idx)))
:priority? (not (or (nil? priority?)
(false? priority?)))}))
(defn score-and-sort-simple
"Expects a group of tabs and associates a time score with each."
[tabs url-times site-times settings]
(->> tabs
(map #(merge % (time-score % url-times site-times settings)))
(sort-by #(- (:score %)))))
(defn sort-by-root
"Returns a hashmap of the new tab ids and their indexes, based on a tab list and
the score function for time spent on urls and sites."
[tabs url-times site-times settings]
(let [root-times (accumulate-root-times site-times)]
; (cljs.pprint/pprint tabs)
; (cljs.pprint/pprint root-times)
(->> tabs
;; We first group them by root hostname, sort the tab subgroups,
;; and then sort the groups by the time spend on the root.
(group-by #(root (hostname (:url %))))
(map (fn [[k v]]
(let [scored (score-and-sort-simple v url-times site-times settings)
root-time (get root-times k)
;; We may get a bunch of pages where the root time is 0.
;; In that case, let's sort them by their accumulated score.
idx (if (pos? root-time)
root-time
(apply + (map :score scored)))]
[(- idx)
scored])))
(sort-by first)
;; Discard the root names and just flatten the list
(map second)
flatten
;; Once we have done this, we will need to take priority tabs into
;; account. Those will break from their main group and appear first.
(map-indexed #(assoc %2 :index %1))
(sort-by #(if (:priority? %)
(- (:score %))
(:index %)))
;; Ready!
(map-indexed #(hash-map :index %1
:id (:id %2))))))
\ No newline at end of file
(ns relevance.reagent-utils
(:require [reagent.core :as reagent]))
(def initial-focus-wrapper
(with-meta identity
{:component-did-mount #(.focus (reagent/dom-node %))}))
\ No newline at end of file
......@@ -52,6 +52,14 @@
lower-case
trim)))
(defn root
"Returns the root domain for a host"
[host]
(->> (string/split (lower-case (or host ""))
#"\.")
(take-last 2)
(string/join ".")))
(defn protocol
"Returns the protocol for a URL"
[url]
......@@ -67,7 +75,9 @@
(and (some? url)
(some? (re-find #"\bhttps?:" (protocol url)))))
(defn host-key [host]
(defn host-key
"Returns a key for a hostname, or 0 if the hostname is empty"
[host]
(if (not-empty host)
(hash-string (trim (lower-case host)))
0))
......@@ -87,14 +97,14 @@
0))
(defn to-string-set
"Split a string into a string set using commas, semi-colons or new lines, and returns it as a set"
"Split a string using commas, semi-colons or new lines, trims the resulting
elements, and returns them as a set"
[s]
(->>
(string/split (or s "") #",|\n|;| ")
(map string/trim)
(remove empty?)
(map string/lower-case)
(into #{})))
(->> (string/split (lower-case (or s ""))
#",|\n|;| ")
(map string/trim)
(remove empty?)
(into #{})))
(defn time-display
"Returns a display string for a number of seconds"
......
......@@ -166,7 +166,7 @@
(fn []
[:div {:class "navbar-header"}
[:a {:class "navbar-brand"}
(condp = @section
(case @section
:intro "About Relevance"
:url-times "Time reading a page"
:site-times "Time visiting a site"
......@@ -328,7 +328,7 @@
[:div {:class "page-header col-sm-10 col-sm-offset-1"}
[:h1 "Welcome!"]]
[:div {:class "col-sm-10 col-sm-offset-1"}
[:h2 "Thanks for installing Relevance 1.0.10"]
[:h2 "Thanks for installing Relevance 1.1.0"]
[:p [:a {:href "https://numergent.com/relevance/" :target "_blank"} "You can read about the latest changes here."]]
[:p "Relevance will help you sort your tabs when you have too many of them open. Here's how to use it:"]
[:ul
......
......@@ -437,15 +437,22 @@
{:url "http://www.kitco.com/market/"
:time 4
:ts 1446051494575
:title "New York spot price Gold..."}
-24505671
{:url "http://kitco.com/market/"
:time 2
:ts 1446051494575
:title "New York spot price Gold..."}}
acc (data/accumulate-site-times data)]
;; There should be no empty hostnames
;; We check (get acc 0) because the result is indexed by the host-key,
;; which returns 0 on nil or empty.
(is (nil? (get acc 0)))
;; Let's verify we got the right data
(is (= {971841386 {:time 39, :icon nil, :host "numergent.com"},
-915908674 {:time 4, :icon nil, :host "www.kitco.com"}}
;; Let's verify we got the right data. Notice that accumulate-site-times
;; does not take into account differences in the root domain.
(is (= {971841386 {:time 39 :icon nil :host "numergent.com"}
-915908674 {:time 4 :icon nil :host "www.kitco.com"}
996869973 {:time 2 :icon nil :host "kitco.com"}}
acc))))
(testing "Accumulate site times disregards the port for the URL when accumulating"
(let [data {2080624698
......@@ -482,8 +489,40 @@
(is (= {971841386 {:time 27 :icon nil :host "numergent.com"}
-915908674 {:time 4 :icon nil :host "www.kitco.com"}
-1536293812 {:time 37 :icon nil :host "google.com"}}
acc))))
)
acc)))))
(deftest test-accumulate-root-times
(let [data {2080624698
{:url "/tags/khroma/"
:time 117
:ts 1445964037798
:title "Khroma articles"}
-526558523
{:url "https://numergent.com/opensource/"
:time 27
:ts 1445964037798
:title "Open source projects"}
-327774960
{:url "https://www.numergent.com/tags/khroma/"
:time 12
:ts 1445964037798
:title "Khroma articles"}
1917381154
{:url "http://www.kitco.com/market/"
:time 4
:ts 1446051494575
:title "New York spot price Gold..."}
-24505671
{:url "http://KITCO.com/market/"
:time 3
:ts 1446051494575
:title "New York spot price Gold..."}}
site-times (data/accumulate-site-times data)
root-times (data/accumulate-root-times site-times)]
(testing "Accumulate root times takes into lumps together pages on the same root"
(is (= {"numergent.com" 39
"kitco.com" 7}
root-times)))))
(deftest test-accumulate-after-clean-up
......
This diff is collapsed.
......@@ -99,6 +99,17 @@
))
(deftest test-root
(are [host name] (= (utils/root host) name)
"www.google.com" "google.com"
"WWW.google.COM" "google.com"
"some.sub.domain.com" "domain.com"
"localhost" "localhost"
"" ""
nil ""
))
(deftest test-protocol
(are [url name] (= (utils/protocol url) name)
"https://www.google.com" "https:"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment