Commit 6fb9eaa7 authored by Ricardo J. Mendez's avatar Ricardo J. Mendez

Merge branch 'release/1.0.8'

parents 657e5297 3b1ba031
Pipeline #4726692 passed with stage
image: clojure:lein-2.7.0
before_script:
- wget -q https://numergent.com/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2
- tar -xvf phantomjs-2.1.1-linux-x86_64.tar.bz2
- PATH=$PATH:.:./phantomjs-2.1.1-linux-x86_64/bin/
- lein deps
test:
script:
- lein with-profile test doo once
......@@ -6,7 +6,7 @@ It’ll create a natural arrangement where the tabs you have spent the longest o
[You can read more about it here](https://numergent.com/relevance/).
This is Relevance 1.0.7
This is Relevance 1.0.8.
# Building
......@@ -18,7 +18,7 @@ To get a development build, run
lein chromebuild auto
```
You'll find the `target/unpacked/`, which you'll need to install into Chrome using developer mode.
You'll find the result in `target/unpacked/`, which you'll need to install into Chrome using developer mode.
## Release
......@@ -37,10 +37,33 @@ Relevance uses `doo` for running ClojureScript tests. I normally use `phantomjs`
We can't test the entire application externally, since a lot of its API depend on Chrome functions being present (which only happens when you're running as a Chrome extension). You can however test the general functions running:
```
lein with-profile test doo phantom
lein with-profile test doo
```
# Development
## Continuous integration
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)
I've had some issues with Travis builds failing to get dependencies, so I decided to deprecate it.
## Process
I'm using [git-flow](http://nvie.com/posts/a-successful-git-branching-model/). Pull requests are welcome. Please base them off the `develop` branch.
## Version number conventions
Relevance uses [break versioning](https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md).
The development version on `project.clj` will reflect the current state of the code, and will normally include SNAPSHOT.
I can't update the package version from `manifest.json` to include alphanumerics, so that version will remain as the last public until I'm nearing a release (or need to test a data migration).
# License
Includes pixeden's [iOS 7 vector icons](http://themes-pixeden.com/font-demos/7-stroke/).
......
(defproject relevance-chrome "1.0.7"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
(defproject relevance-chrome "1.0.8"
:license {:name "MIT License"
:url "https://tldrlegal.com/license/mit-license"}
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.93"]
[org.clojure/core.async "0.2.385"]
[org.clojure/clojurescript "1.9.293"]
[org.clojure/core.async "0.2.395"]
[com.cognitect/transit-cljs "0.8.239"]
[cljsjs/react-bootstrap "0.28.1-1" :exclusions [org.webjars.bower/jquery]]
[cljsjs/react-bootstrap "0.30.2-0"]
[khroma "0.3.0"]
[prismatic/dommy "1.1.0"]
[re-frame "0.7.0" :exclusions [cljsjs/react]]]
[re-frame "0.8.0" :exclusions [cljsjs/react]]]
:source-paths ["src/ui" "src/common" "src/background" "src/content"]
:test-paths ["test"]
:plugins [[lein-cljsbuild "1.1.3"]
[lein-chromebuild "0.3.1"]
[lein-doo "0.1.6"]]
:plugins [[lein-cljsbuild "1.1.4"]
[org.clojars.ricardojmendez/lein-chromebuild "0.3.2"]
[lein-doo "0.1.7"]]
:doo {:build "test"}
:doo {:build "test"
:alias {:default [:phantom]}}
:cljsbuild {:builds
{:background
......@@ -61,7 +62,7 @@
:ui {:compiler {:optimizations :advanced
:pretty-print false}}}}}
:test
{:dependencies [[lein-doo "0.1.6"]]
{:dependencies [[lein-doo "0.1.7"]]
:cljsbuild
{:builds
{:test
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
......@@ -11,7 +11,7 @@
<meta name="viewport" content="width=device-width"/>
<!-- Bootstrap core CSS -->
<link href="bootstrap.min.css" rel="stylesheet"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Light Bootstrap Table core CSS -->
<link href="light-bootstrap-dashboard.css" rel="stylesheet"/>
......@@ -79,11 +79,10 @@
</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 type="text/javascript" src="ui.js"></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.
{
"name": "Relevance - Smart Tab Organizer",
"short_name": "Relevance",
"version": "1.0.7",
"version": "1.0.8",
"browser_action": {
"default_title": "Organize tabs",
"default_icon": {
......
......@@ -14,7 +14,7 @@
[khroma.tabs :as tabs]
[khroma.runtime :as runtime]
[khroma.windows :as windows]
[re-frame.core :refer [dispatch register-sub register-handler subscribe dispatch-sync]]
[re-frame.core :refer [dispatch reg-event-db subscribe dispatch-sync]]
[khroma.extension :as ext]
[khroma.browser-action :as browser]
[khroma.storage :as storage])
......@@ -69,9 +69,11 @@
[]
;; We should use dispatch for anything that does not absolutely require
;; immediate handling, to avoid interferring with the regular initialization
;; and event flow. On this case, I' using it only for log-content,
;; which has no effect on app-state, and for on-suspend, which we want to
;; handle immediately.
;; and event flow.
;;
;; In this case, I'm using dispatch-sync only for log-content, which has no
;; effect on app-state, and for on-suspend, which we want to handle
;; immediately.
(on-channel alarms/on-alarm dispatch ::on-alarm)
(on-channel browser/on-clicked dispatch ::on-clicked-button)
(on-channel runtime/on-message dispatch ::on-message)
......@@ -113,7 +115,7 @@
;;;;-------------------------------------
(register-handler
(reg-event-db
::initialize
(fn [_]
(go
......@@ -133,7 +135,7 @@
{:app-state {}}))
(register-handler
(reg-event-db
:data-load
(fn [app-state [_ data settings]]
(let [migrated (migrations/migrate-to-latest data)
......@@ -174,7 +176,7 @@
(register-handler
(reg-event-db
:data-set
(fn [app-state [_ key item]]
(let [new-state (assoc-in app-state [:data key] item)]
......@@ -182,7 +184,7 @@
new-state)))
(register-handler
(reg-event-db
:delete-url
(fn [app-state [_ url]]
(let [data (:data app-state)
......@@ -199,7 +201,7 @@
(assoc app-state :data new-data))))
(register-handler
(reg-event-db
:handle-activation
(fn [app-state [_ tab start-time]]
; (console/trace "Handling activation" tab)
......@@ -213,7 +215,7 @@
app-state)))
(register-handler
(reg-event-db
:handle-deactivation
(fn
;; We get two parameters: the tab, and optionally the time at which it
......@@ -226,7 +228,7 @@
app-state))
(register-handler
(reg-event-db
:idle-state-change
(fn [app-state [_ message]]
(let [state (:newState message)
......@@ -239,8 +241,8 @@
;; 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
;; would find tabs, the second one would and would overwrite the original
;; saved set with an empty list).
;; would find tabs, the second one wouldn't and would overwrite the
;; original saved set with an empty list).
(if active-tab
(do
(dispatch [action active-tab])
......@@ -251,7 +253,7 @@
(register-handler
(reg-event-db
::on-alarm
(fn [app-state [_ {:keys [alarm]}]]
(when (= window-alarm (:name alarm))
......@@ -259,7 +261,7 @@
app-state))
(register-handler
(reg-event-db
::on-clicked-button
(fn [app-state [_ {:keys [tab]}]]
;; Force it to track the time up until now
......@@ -270,14 +272,14 @@
app-state))
(register-handler
(reg-event-db
::on-clicked-menu
(fn [app-state [_ {:keys [info tab]}]]
(dispatch [(keyword (:menuItemId info)) tab])
app-state))
(register-handler
(reg-event-db
::on-message
(fn [app-state [_ payload]]
(let [{:keys [message sender]} (keywordize-keys payload)
......@@ -293,21 +295,21 @@
(register-handler
(reg-event-db
:on-relevance-show-data
(fn [app-state [_]]
(open-results-tab)
app-state))
(register-handler
(reg-event-db
:on-relevance-sort-tabs
(fn [app-state [_ tab]]
(sort-tabs! (:windowId tab) app-state)
app-state))
(register-handler
(reg-event-db
:suspend
;; The message itself is not relevant, we only care that we are being suspended
(fn [app-state [_]]
......@@ -316,7 +318,7 @@
app-state))
(register-handler
(reg-event-db
::tab-activated
(fn [app-state [_ {:keys [activeInfo]}]]
(let [{:keys [tabId windowId]} activeInfo
......@@ -331,7 +333,7 @@
(register-handler
(reg-event-db
::tab-updated
(fn [app-state [_ {:keys [tabId tab]}]]
(let [active-tab (:active-tab app-state)
......@@ -354,7 +356,7 @@
(register-handler
(reg-event-db
:track-time
(fn [app-state [_ tab time]]
(let [data (:data app-state)
......@@ -366,7 +368,7 @@
(assoc app-state :data new-data))))
(register-handler
(reg-event-db
::window-focus
(fn [app-state [_ {:keys [windowId]}]]
; (console/trace "Current window" windowId)
......
......@@ -26,13 +26,13 @@
is-penalized? (and (not (is-http? url))
(not is-priority?))
tab-time (cond
; Add an extra score if it's a priority URL
;; 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
;; 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
;; ... 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)
......
(ns relevance.shared-handlers
(:require [khroma.log :as console]
[re-frame.core :refer [register-handler]]))
[re-frame.core :refer [reg-event-db]]))
(register-handler
(reg-event-db
:log-content
(fn [app-state [_ content]]
(console/log "Log event:" content)
......
......@@ -78,6 +78,10 @@
[url]
(if (not-empty url)
(let [element (dommy/set-attr! (dommy/create-element :a) :href url)
;; For the url-key we _want_ to use the full host, not just the hostname
;; since it will be used to indicate how much time we have spent on the
;; specific URL. http://localhost/index.html will likely be different from
;; http://localhost:8080/index.html
shortened (str (.toLowerCase (.-host element)) (.-pathname element) (.-search element))]
(hash-string shortened))
0))
......
......@@ -3,6 +3,7 @@
ms-hour ms-day ms-week]]
[cljs.core.async :refer [>! <!]]
[cljs.core :refer [random-uuid]]
[cljsjs.bootstrap]
[cljsjs.react-bootstrap]
[clojure.string :as string]
[khroma.idle :as idle]
......@@ -10,7 +11,7 @@
[khroma.runtime :as runtime]
[khroma.storage :as storage]
[reagent.core :as reagent]
[re-frame.core :refer [dispatch register-sub register-handler subscribe dispatch-sync]]
[re-frame.core :refer [dispatch reg-sub reg-event-db subscribe dispatch-sync]]
[relevance.io :as io]
[relevance.utils :as utils]
[relevance.settings :refer [default-settings]])
......@@ -26,15 +27,15 @@
(defn general-query
[db path]
(reaction (get-in @db path)))
(get-in db path))
;; Application data, will be saved
(register-sub :data general-query)
(register-sub :settings general-query)
(register-sub :raw-data general-query)
(reg-sub :data general-query)
(reg-sub :settings general-query)
(reg-sub :raw-data general-query)
;; Transient data items
(register-sub :ui-state general-query)
(register-sub :app-state general-query)
(reg-sub :ui-state general-query)
(reg-sub :app-state general-query)
;;;;----------------------------
......@@ -55,7 +56,7 @@
;;;; Handlers
;;;;----------------------------
(register-handler
(reg-event-db
:app-state-item
(fn [app-state [_ path item]]
(when (= [:ui-state :section] path)
......@@ -63,7 +64,7 @@
(assoc-in app-state path item)))
(register-handler
(reg-event-db
:data-import
(fn [app-state [_ transit-data]]
;; We actually just need to save it, since ::storage-changed takes care
......@@ -75,7 +76,7 @@
(register-handler
(reg-event-db
::initialize
(fn [_]
(go
......@@ -90,12 +91,12 @@
:site-page 0}}))
(register-handler
(reg-event-db
:modal-info-set
(fn [app-state [_ info]]
(assoc-in app-state [:ui-state :modal-info] info)))
(register-handler
(reg-event-db
:settings-parse
(fn [app-state [_ settings]]
(let [ignore-set (utils/to-string-set (:ignore-set settings))]
......@@ -104,7 +105,7 @@
app-state)))
(register-handler
(reg-event-db
:settings-set
(fn [app-state [_ settings save?]]
; (console/log "Saving" settings save?)
......@@ -116,7 +117,7 @@
(assoc app-state :settings settings)))
(register-handler
(reg-event-db
::storage-changed
(fn [app-state [_ message]]
(let [new-value (get-in message [:changes :data :newValue])
......@@ -265,8 +266,8 @@
(fn []
[:div {:class "row"}
[:p "If you wish to delete an item, hover the mouse over the listing. You'll see a "
[:i {:class "fa fa-remove"
:style {:color "red"}}]
[:i {:class "fa fa-remove"
:style {:color "red"}}]
" appear besides the last visit time. Click it to remove the row."]
[:p "Total: " @total " URLs."]
......@@ -333,7 +334,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.7"]
[:h2 "Thanks for installing Relevance 1.0.8"]
[:p [:a {:href "http://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
......
......@@ -439,14 +439,51 @@
: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.
;; 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"}}
acc)))))
acc))))
(testing "Accumulate site times disregards the port for the URL when accumulating"
(let [data {2080624698
{:url "/tags/khroma/"
:time 117
:ts 1445964037798
:title "Khroma articles"}
-526558523
{:url "http://numergent.com/opensource/"
:time 27
:ts 1445964037798
:title "Open source projects"}
-380467869
{:url "http://google.com"
:time 12
:ts 1445964037798
:title "Google - default port"}
-849773167
{:url "http://google.com:8080"
:time 25
:ts 1445964037798
:title "Google - non-standard port"}
1917381154
{:url "http://www.kitco.com/market/"
:time 4
: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 27 :icon nil :host "numergent.com"}
-915908674 {:time 4 :icon nil :host "www.kitco.com"}
-1536293812 {:time 37 :icon nil :host "google.com"}}
acc))))
)
(deftest test-accumulate-after-clean-up
......
......@@ -5,61 +5,82 @@
(deftest test-time-score
; A score for an unknown URL with no tab index is zero
;; A score for an unknown URL with no tab index is zero
(is (= 0 (order/time-score {:url "http://google.com"}
{}
{}
{})))
; A score for an unknown URL with no tab index is the complement of its index
;; A score for an unknown URL with no tab index is the complement of its index
(is (= -20 (order/time-score {:url "http://google.com"
:index 20}
{}
{}
{})))
; A score for a known URL equals its time value
;; A score for a known URL equals its time value
(is (= 291 (order/time-score {:url "http://google.com"}
{(utils/url-key "http://google.com")
{:time 291}}
{}
{})))
; A score for a known URL gets added the time for its site
;; A score for a known URL equals its time value, even if it includes a port
(is (= 291 (order/time-score {:url "http://google.com:80"}
{(utils/url-key "http://google.com")
{:time 291}}
{}
{})))
;; A score for a known URL gets added the time for its site
(is (= 468 (order/time-score {:url "http://google.com/somepage"}
{(utils/url-key "http://google.com") {:time 291}
(utils/url-key "http://google.com/somepage") {:time 345}}
{(utils/host-key "google.com") {:time 123}
(utils/host-key "apple.com") {:time 987}}
{})))
; A score for a known URL is not affected by the score of other URLs for the same site
;; A score for a known URL gets added the time for its host, even if it's using
;; a non-standard port
(is (= 123 (order/time-score {:url "http://google.com:9090/somepage"}
{(utils/url-key "http://google.com") {:time 291}
(utils/url-key "http://google.com/somepage") {:time 345}}
{(utils/host-key "google.com") {:time 123}
(utils/host-key "apple.com") {:time 987}}
{})))
(is (= 448 (order/time-score {:url "http://google.com:9090/somepage"}
{(utils/url-key "http://google.com") {:time 291}
(utils/url-key "http://google.com/somepage") {:time 345}
(utils/url-key "http://google.com:9090/somepage") {:time 325}}
{(utils/host-key "google.com") {:time 123}
(utils/host-key "apple.com") {:time 987}}
{})))
;; A score for a known URL is not affected by the score of other URLs for the same site
(is (= 414 (order/time-score {:url "http://google.com/"}
{(utils/url-key "http://google.com") {:time 291}
(utils/url-key "http://google.com/somepage") {:time 345}}
{(utils/host-key "google.com") {:time 123}
(utils/host-key "apple.com") {:time 987}}
{})))
; A page inherits its site's score even if the page is unknown
;; A page inherits its site's score even if the page is unknown
(is (= 987 (order/time-score {:url "http://apple.com/mac"}
{(utils/url-key "http://google.com") {:time 291}
(utils/url-key "http://google.com/somepage") {:time 345}}
{(utils/host-key "google.com") {:time 123}
(utils/host-key "apple.com") {:time 987}}
{})))
; A page inherits its site's score even if the page is unknown, but
; substracts the index so that they are placed at the end.
;; A page inherits its site's score even if the page is unknown, but
;; substracts the index so that they are placed at the end.
(is (= 975 (order/time-score {:url "http://apple.com/mac" :index 12}
{(utils/url-key "http://google.com") {:time 291}
(utils/url-key "http://google.com/somepage") {:time 345}}
{(utils/host-key "google.com") {:time 123}
(utils/host-key "apple.com") {:time 987}}
{})))
; A page that has sound gets no extra score if the :sound-to-left? key isn't on settings
;; A page that has sound gets no extra score if the :sound-to-left? key isn't on settings
(is (= 291 (order/time-score {:url "http://google.com"
:audible true}
{(utils/url-key "http://google.com")
{:time 291}}
{}
{})))
; An audible page gets an extra score if the :sound-to-left? key is set to true on the settings,
; but based on the index, not the time spent
;; An audible page gets an extra score if the :sound-to-left? key is set to true on the settings,
;; but based on the index, not the time spent
(is (= (+ 5 123 order/sound-extra-score)
(order/time-score {:url "http://google.com/translate"
:index 5
......@@ -69,8 +90,8 @@
{(utils/host-key "google.com") {:time 123}
(utils/host-key "apple.com") {:time 987}}
{:sound-to-left? true})))
; Non-http pages are penalized, but get a minimum score of 1 (they will
; likely have a time of 0 to begin with since they aren't tracked)
;; Non-http URLs are penalized, but get a minimum score of 1 (they will
;; likely have a time of 0 to begin with since they aren't tracked)
(is (= (* 124 order/non-http-penalty)
(order/time-score {:url "chrome://google.com/translate"
:index 5}
......@@ -87,8 +108,8 @@
(utils/url-key "http://apple.com/") {:time 2120}}
site-times {(utils/host-key "google.com") {:time 4295} ; Includes time from tabs which we have deleted
(utils/host-key "apple.com") {:time 12221}}]
; We have spent the longest at Apple, so it gets prioritized
; The extension tab ends up at the end because it's not http
;; We have spent the longest at Apple, so it gets prioritized
;; The extension tab ends up at the end because it's not http
(is (= [{:index 0 :id 23} {:index 1 :id 9} {:index 2 :id 1}]
(order/score-tabs [{:url "http://google.com"
:id 9
......@@ -102,7 +123,7 @@
url-times
site-times
{})))
; An unknown page ends up at the end
;; An unknown page ends up at the end
(is (= [{:index 0 :id 23} {:index 1 :id 9} {:index 2 :id 1} {:index 3 :id 2}]
(order/score-tabs [{:url "http://google.com"
:id 9
......@@ -119,7 +140,7 @@
url-times
site-times
{})))
; Two unknown pages get sorted by their index order
;; Two unknown pages get sorted by their index order
(is (= [{:index 0 :id 23} {:index 1 :id 9} {:index 2 :id 1} {:index 3 :id 123} {:index 4 :id 2}]
(order/score-tabs [{:url "http://google.com"
:id 9
......@@ -139,7 +160,7 @@
url-times
site-times
{})))
; An unknown page that is playing sound gets prioritized according to the settings
;; An unknown page that is playing sound gets prioritized according to the settings
(is (= [{:index 0 :id 2} {:index 1 :id 23} {:index 2 :id 9} {:index 3 :id 1}]
(order/score-tabs [{:url "http://google.com"
:id 9
......@@ -157,7 +178,7 @@
url-times
site-times
{:sound-to-left? true})))
; If for any reason the Apple URL is not http, it gets de-prioritized
;; If for any reason the Apple URL is not http, it gets de-prioritized
(is (= [{:index 0 :id 9} {:index 1 :id 1} {:index 2 :id 23}]
(order/score-tabs [{:url "http://google.com"
:id 9
......
......@@ -38,6 +38,12 @@
-526558523 "http://numergent.com/opensource/"
93819220 "about:blank"
-847465478 "file:///Users/ricardo/Sources/user.html"
;; URL keys are port-sensitive
-380467869 "http://google.com:80"
-849773167 "http://google.com:8080"
;; Port 80 is treated the same as if it wasn't specified
2045915370 "http://google.com/index.html"
2045915370 "http://google.com:80/index.html"
))
......