├── .gitignore
├── src
├── pages
│ ├── popup
│ │ ├── list-view.css
│ │ ├── app.css
│ │ ├── settings.css
│ │ ├── search.css
│ │ ├── index.js
│ │ ├── item-view.css
│ │ ├── app.js
│ │ ├── test.js
│ │ ├── ListView.js
│ │ ├── Search.js
│ │ ├── dashboard.css
│ │ ├── ItemView.js
│ │ ├── Settings.js
│ │ └── Dashboard.js
│ ├── background
│ │ ├── reducers.js
│ │ ├── reducers
│ │ │ ├── animations.js
│ │ │ ├── settings.js
│ │ │ └── bookmarks.js
│ │ ├── localStorage.js
│ │ ├── index.js
│ │ ├── store.js
│ │ └── actions.js
│ └── content
│ │ ├── index.css
│ │ └── index.js
├── assets
│ ├── list.png
│ ├── list128.png
│ └── nothing.png
└── manifest.json
├── images
├── cover.png
├── cover2.png
├── image1.png
└── image2.png
├── logo-readme.png
├── coverage
├── lcov-report
│ ├── sort-arrow-sprite.png
│ ├── prettify.css
│ ├── background
│ │ ├── index.html
│ │ └── actions.js.html
│ ├── index.html
│ ├── popup
│ │ ├── App.js.html
│ │ ├── ListView.js.html
│ │ ├── index.html
│ │ ├── Search.js.html
│ │ ├── ItemView.js.html
│ │ ├── Settings.js.html
│ │ └── Dashboard.js.html
│ ├── sorter.js
│ ├── base.css
│ └── prettify.js
├── lcov.info
├── clover.xml
└── coverage-final.json
├── .babelrc
├── LICENSE
├── README.md
├── webpack.config.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | Dist/
3 | dist.zip
4 | logo/
--------------------------------------------------------------------------------
/src/pages/popup/list-view.css:
--------------------------------------------------------------------------------
1 | .empty-list {
2 | margin: 5px 3px;
3 | }
4 |
--------------------------------------------------------------------------------
/images/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pierroberto/Pin-Tabs/HEAD/images/cover.png
--------------------------------------------------------------------------------
/logo-readme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pierroberto/Pin-Tabs/HEAD/logo-readme.png
--------------------------------------------------------------------------------
/images/cover2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pierroberto/Pin-Tabs/HEAD/images/cover2.png
--------------------------------------------------------------------------------
/images/image1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pierroberto/Pin-Tabs/HEAD/images/image1.png
--------------------------------------------------------------------------------
/images/image2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pierroberto/Pin-Tabs/HEAD/images/image2.png
--------------------------------------------------------------------------------
/src/assets/list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pierroberto/Pin-Tabs/HEAD/src/assets/list.png
--------------------------------------------------------------------------------
/src/pages/popup/app.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | margin: 15px 20px;
3 | margin-bottom: 80px;
4 | }
5 |
--------------------------------------------------------------------------------
/src/assets/list128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pierroberto/Pin-Tabs/HEAD/src/assets/list128.png
--------------------------------------------------------------------------------
/src/assets/nothing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pierroberto/Pin-Tabs/HEAD/src/assets/nothing.png
--------------------------------------------------------------------------------
/coverage/lcov-report/sort-arrow-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pierroberto/Pin-Tabs/HEAD/coverage/lcov-report/sort-arrow-sprite.png
--------------------------------------------------------------------------------
/src/pages/popup/settings.css:
--------------------------------------------------------------------------------
1 | .fa-check:hover {
2 | color: #33c3f0;
3 | }
4 |
5 | label {
6 | margin-right: 10px;
7 | }
8 |
9 | ul {
10 | list-style-type: none;
11 | line-height: 3.5;
12 | padding: 0;
13 | }
14 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "react",
4 | "es2015",
5 | "stage-2"
6 | ],
7 | "plugins": [
8 | "transform-class-properties",
9 | "transform-async-to-generator",
10 | "react-html-attrs"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/src/pages/background/reducers.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import bookmark from "./reducers/bookmarks";
3 | import settings from "./reducers/settings";
4 | import animation from "./reducers/animations";
5 |
6 | export default combineReducers({
7 | bookmark: bookmark,
8 | settings: settings,
9 | animation: animation
10 | });
11 |
--------------------------------------------------------------------------------
/src/pages/popup/search.css:
--------------------------------------------------------------------------------
1 | .hide {
2 | visibility: hidden;
3 | }
4 |
5 | input {
6 | border: none;
7 | }
8 |
9 | input:focus {
10 | outline: none;
11 | border: none;
12 | margin-right: 5px;
13 | margin-bottom: 10px;
14 | padding: 5px 0 10px;
15 | font-size: 16px;
16 | transition: padding 0.5s;
17 | transition: font-size 0.5s;
18 | }
19 |
20 | .show {
21 | visibility: visible;
22 | }
23 |
--------------------------------------------------------------------------------
/src/pages/background/reducers/animations.js:
--------------------------------------------------------------------------------
1 | const defaultState = {
2 | buttonCog: false
3 | };
4 |
5 | const animation = (state = defaultState, action) => {
6 | switch (action.type) {
7 | case "TOGGLE-COG":
8 | return {
9 | ...state,
10 | buttonCog: action.buttonCog
11 | };
12 | case "TOGGLE-SEARCH":
13 | return {
14 | ...state,
15 | toggleSearch: action.toggleSearch
16 | };
17 | default:
18 | return state;
19 | }
20 | };
21 |
22 | export default animation;
23 |
--------------------------------------------------------------------------------
/src/pages/background/localStorage.js:
--------------------------------------------------------------------------------
1 | export const saveState = state => {
2 | try {
3 | const serializedState = JSON.stringify(state);
4 | localStorage.setItem("state", serializedState);
5 | } catch (e) {
6 | console.error("Error saving state", e);
7 | }
8 | };
9 |
10 | export const loadState = () => {
11 | try {
12 | const serializedState = localStorage.getItem("state");
13 | if (serializedState === null) {
14 | return undefined;
15 | }
16 | return JSON.parse(serializedState);
17 | } catch (e) {}
18 | };
19 |
--------------------------------------------------------------------------------
/src/pages/popup/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { Provider } from "react-redux";
4 | import { Store } from "react-chrome-redux";
5 | import App from "./App";
6 |
7 | const store = new Store({
8 | portName: "COUNTING"
9 | });
10 |
11 | store.ready().then(() => {
12 | const mountNode = document.createElement("div");
13 | document.body.appendChild(mountNode);
14 |
15 | ReactDOM.render(
16 |
17 |
18 | ,
19 | mountNode
20 | );
21 | });
22 |
--------------------------------------------------------------------------------
/src/pages/background/index.js:
--------------------------------------------------------------------------------
1 | import store from "./store";
2 |
3 | store.subscribe(() => {
4 | store.getState().bookmark.tabs.map(infoTab => {
5 | chrome.alarms.create(infoTab.tab[0].url, {
6 | when: infoTab.expiry + store.getState().settings.expireDate
7 | });
8 | });
9 | });
10 |
11 | chrome.alarms.onAlarm.addListener(function(data) {
12 | const elementExpired = store.getState().bookmark.tabs.filter(el => {
13 | return el.tab[0].url === data.name;
14 | });
15 | store.dispatch({ type: "EXPIRY", url: elementExpired[0].tab[0].url });
16 | });
17 |
--------------------------------------------------------------------------------
/src/pages/popup/item-view.css:
--------------------------------------------------------------------------------
1 | .content-container {
2 | display: flex;
3 | align-items: end;
4 | justify-content: space-between;
5 | padding: 15px 0;
6 | border-bottom: 1px solid #f2f2f2;
7 | }
8 |
9 | .content-container:last-child {
10 | border-bottom: 0;
11 | padding-bottom: 0;
12 | }
13 |
14 | .col-1-1 {
15 | width: 16px;
16 | height: 16px;
17 | margin-right: 5px;
18 | }
19 |
20 | .col-1-2 {
21 | color: #099ecf;
22 | }
23 |
24 | .icon {
25 | width: 16px;
26 | height: 16px;
27 | }
28 |
29 | .row {
30 | display: flex;
31 | }
32 |
33 | .title {
34 | text-decoration: none;
35 | }
36 |
37 | .warning {
38 | font-size: 10px;
39 | }
40 |
--------------------------------------------------------------------------------
/coverage/lcov-report/prettify.css:
--------------------------------------------------------------------------------
1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
2 |
--------------------------------------------------------------------------------
/src/pages/background/reducers/settings.js:
--------------------------------------------------------------------------------
1 | const defaultState = {
2 | button: false,
3 | buttonHystory: true,
4 | expireDate: 86400000 // 1 day
5 | };
6 |
7 | const settings = (state = defaultState, action) => {
8 | switch (action.type) {
9 | case "TOGGLE-BUTTON":
10 | return {
11 | ...state,
12 | button: action.toggleButton
13 | };
14 | case "TOGGLE-BUTTON-HISTORY":
15 | return {
16 | ...state,
17 | buttonHistory: action.toggleButtonHistory
18 | };
19 | case "UPDATE-DATE":
20 | return {
21 | ...state,
22 | expireDate: action.expireDate
23 | };
24 | }
25 | return state;
26 | };
27 |
28 | export default settings;
29 |
--------------------------------------------------------------------------------
/src/pages/popup/app.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import "./app.css";
4 | import ListView from "./ListView.js";
5 | import Settings from "./Settings";
6 | import Dashboard from "./Dashboard";
7 | import { Route, BrowserRouter as Router, Switch } from "react-router-dom";
8 |
9 | class App extends React.Component {
10 | render() {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 | }
23 |
24 | export default App;
25 |
--------------------------------------------------------------------------------
/src/pages/background/store.js:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, createStore } from "redux";
2 | import { wrapStore, alias } from "react-chrome-redux";
3 | import { createLogger } from "redux-logger";
4 | import thunk from "redux-thunk";
5 | import reducer from "./reducers";
6 | import throttle from "lodash/throttle";
7 | import { saveState, loadState } from "./localStorage";
8 | const store = createStore(reducer, loadState());
9 |
10 | store.subscribe(
11 | throttle(() => {
12 | saveState({
13 | bookmark: store.getState().bookmark,
14 | settings: store.getState().settings,
15 | animation: store.getState().animation
16 | });
17 | }),
18 | 1000
19 | );
20 |
21 | wrapStore(store, {
22 | portName: "COUNTING"
23 | });
24 |
25 | export default store;
26 |
--------------------------------------------------------------------------------
/src/pages/popup/test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import * as actions from "../background/actions";
3 | import { shallow } from "enzyme";
4 | import Adapter from "enzyme-adapter-react-15";
5 | import App from "./App";
6 | import { configure } from "enzyme";
7 |
8 | configure({ adapter: new Adapter() });
9 | describe(" ", () => {
10 | it("renders 1 component", () => {
11 | const component = shallow( );
12 | expect(component).toHaveLength(1);
13 | });
14 | });
15 | describe("actions", () => {
16 | it("schould add a new tab", () => {
17 | const urlList = "http://www.google.com";
18 | const add = {
19 | type: "ADD",
20 | urlList,
21 | expiry: new Date().getTime()
22 | };
23 | expect(actions.addBookmark(urlList)).toEqual(add);
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/pages/popup/ListView.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import "./list-view.css";
3 | import randomId from "uuid/v4";
4 | import ItemView from "./ItemView";
5 |
6 | export default class ListView extends React.Component {
7 | renderTabs() {
8 | if (this.props.tabs.length < 1) {
9 | return
No tabs to show 😔 ;
10 | }
11 | return this.props.tabs.map((tab) => {
12 | return (
13 |
23 | );
24 | });
25 | }
26 |
27 | render() {
28 | return {this.renderTabs()}
;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Pier Roberto
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.
22 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Pin tabs - Tab Manager",
4 | "short_name": "Keep tabs history",
5 | "version": "1.3.1",
6 | "description": "Pin Tabs allows the user to pin one or more tabs for short periods, keep track of the expired tabs in the history and save energy",
7 | "browser_action": {
8 | "default_title": "Show the list of pinned tabs",
9 | "default_popup": "pages/popup.html"
10 | },
11 | "author": "Pier Roberto Lucisano",
12 | "background": {
13 | "page": "pages/background.html"
14 | },
15 | "content_scripts": [
16 | {
17 | "matches": [""],
18 | "js": ["pages/index.js"],
19 | "run_at": "document_end",
20 | "exclude_matches": ["https://www.youtube.com/*"]
21 | }
22 | ],
23 | "permissions": [
24 | "tabs",
25 | "activeTab",
26 | "http://*/*",
27 | "https://*/*",
28 | "storage",
29 | "alarms"
30 | ],
31 | "minimum_chrome_version": "60",
32 | "icons": {
33 | "16": "assets/list.png",
34 | "48": "assets/list.png",
35 | "128": "assets/list128.png"
36 | },
37 | "content_security_policy": "script-src 'self' https://ssl.google-analytics.com; object-src 'self'"
38 | }
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [](./LICENSE)
6 |
7 | A Google Chrome extension that allows the user to pin browser tabs and keep track of them for short periods of time.
8 | You can add tabs to your favourites either by clicking on the extension's icon, or through a keyboard shortcut (mac: `CTRL + OPT + s`, Windows: `ALT + s`, Linux: `CTRL + ALT + s`).
9 |
10 | Pinned tabs expire after a user-set timing (from 1 hour to 1 week). The last few expired tabs are still accessible through chronology.
11 |
12 |
13 |
14 |
15 |
16 | ## Installation
17 |
18 | * Clone the repo and cd into it
19 | * Run ```npm install```
20 | * Run ```npm run build```
21 |
22 | ## Tech Stack
23 |
24 | * [React](https://reactjs.org/)
25 | * [Redux](https://redux.js.org/)
26 | * [React-Chrome-Redux](https://github.com/tshaddix/react-chrome-redux/wiki)
27 |
28 | ## License
29 |
30 | Pin Tabs is licensed under the [MIT](https://opensource.org/licenses/mit-license.php) license.
31 |
--------------------------------------------------------------------------------
/src/pages/background/actions.js:
--------------------------------------------------------------------------------
1 | //BOOKMARKS ACTIONS
2 | export const refreshBookmark = (data, time) => ({
3 | type: "REFRESH",
4 | urlList: data,
5 | expiry: time
6 | });
7 |
8 | export const deleteAllBookmark = () => ({
9 | type: "DELETE-ALL"
10 | });
11 |
12 | export const deleteOneBookmark = url => ({
13 | type: "DELETE-ONE",
14 | url: url
15 | });
16 |
17 | export const addBookmark = url => ({
18 | type: "ADD",
19 | urlList: url,
20 | expiry: new Date().getTime()
21 | });
22 |
23 | export const addFromButton = flag => ({
24 | type: "DELETE-ONE",
25 | addFromButton: flag
26 | });
27 |
28 | export const searchBookmark = text => ({
29 | type: "SEARCH",
30 | textSearched: text
31 | });
32 |
33 | export const emptySearch = () => ({
34 | type: "EMPTY-SEARCH"
35 | });
36 |
37 | //SETTINGS actions
38 |
39 | export const toggleButton = flag => ({
40 | type: "TOGGLE-BUTTON",
41 | toggleButton: flag
42 | });
43 |
44 | export const expireDate = date => ({
45 | type: "UPDATE-DATE",
46 | expireDate: date
47 | });
48 |
49 | export const toggleButtonHistory = flag => ({
50 | type: "TOGGLE-BUTTON-HISTORY",
51 | toggleButtonHistory: flag
52 | });
53 |
54 | // ANIMATION ACTIONS
55 |
56 | export const buttonCog = flag => ({
57 | type: "TOGGLE-COG",
58 | buttonCog: flag
59 | });
60 |
61 | export const toggleSearch = classValue => ({
62 | type: "TOGGLE-SEARCH",
63 | toggleSearch: classValue
64 | });
65 |
--------------------------------------------------------------------------------
/src/pages/popup/Search.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import "./search.css";
4 | import {
5 | searchBookmark,
6 | emptySearch,
7 | toggleSearch
8 | } from "../background/actions";
9 | import ListView from "./ListView";
10 | class Search extends React.Component {
11 | findTab = () => {
12 | if (this.props.bookmark.search) {
13 | return (
14 |
15 |
Search results
16 |
22 |
23 | );
24 | }
25 | };
26 |
27 | componentDidMount() {
28 | this.props.resetSearch();
29 | }
30 |
31 | // ================== RENDERING
32 | render() {
33 | return (
34 |
35 | this.props.searchString(e.target.value)}
38 | placeholder="search..."
39 | />
40 | {this.findTab()}
41 |
42 | );
43 | }
44 | }
45 |
46 | const mapStateToProps = state => ({
47 | bookmark: state.bookmark,
48 | settings: state.settings,
49 | animation: state.animation
50 | });
51 |
52 | const mapDispatchToProps = dispatch => ({
53 | searchString: text => dispatch(searchBookmark(text)),
54 | resetSearch: () => dispatch(emptySearch()),
55 | displaySearch: classValue => dispatch(toggleSearch(classValue))
56 | });
57 |
58 | export default connect(
59 | mapStateToProps,
60 | mapDispatchToProps
61 | )(Search);
62 |
--------------------------------------------------------------------------------
/src/pages/content/index.css:
--------------------------------------------------------------------------------
1 | .circle {
2 | position: fixed;
3 | right: 20px;
4 | bottom: 20px;
5 | height: 50px;
6 | width: 50px;
7 | box-shadow: 6px 6px 18px 0 rgba(0, 0, 0, 0.3);
8 | border-radius: 100%;
9 | background-color: #33c3f0;
10 | cursor: pointer;
11 | z-index: 99999;
12 | }
13 |
14 | .hidden {
15 | visibility: hidden;
16 | }
17 |
18 | .popup {
19 | position: absolute;
20 | background-color: white;
21 | border-radius: 4px;
22 | padding: 10px 20px;
23 | right: 70px;
24 | top: 50%;
25 | color: #33c3f0;
26 | box-shadow: 6px 6px 18px 0 rgba(0, 0, 0, 0.1);
27 | font-size: 12px;
28 | text-transform: uppercase;
29 | letter-spacing: 3px;
30 | transform: translate(0, -50%);
31 | border: 1px solid #f3f3f3;
32 | }
33 |
34 | .popup:before {
35 | content: "";
36 | position: absolute;
37 | top: 50%;
38 | right: 0;
39 | transform: translate(100%, -50%);
40 | width: 0;
41 | height: 0;
42 | border-top: 7px solid transparent;
43 | border-bottom: 7px solid transparent;
44 | border-left: 7px solid white;
45 | }
46 |
47 | .popup-visibility-show {
48 | opacity: 1;
49 | visibility: visible;
50 | transition: all 0.35s ease;
51 | }
52 |
53 | .popup-visibility-hide {
54 | transition: all 0.35s ease;
55 | opacity: 0;
56 | visibility: hidden;
57 | }
58 |
59 | .visible {
60 | visibility: visible;
61 | }
62 |
63 | .x-line {
64 | position: absolute;
65 | top: 50%;
66 | left: 50%;
67 | transform: translate(-50%, -50%);
68 | width: 2px;
69 | height: 20px;
70 | background-color: white;
71 | }
72 |
73 | .y-line {
74 | position: absolute;
75 | top: 50%;
76 | left: 50%;
77 | transform: translate(-50%, -50%);
78 | width: 20px;
79 | height: 2px;
80 | background-color: white;
81 | }
82 |
--------------------------------------------------------------------------------
/src/pages/popup/dashboard.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css?family=Roboto:300");
2 | @import url("https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css");
3 |
4 | html,
5 | body {
6 | height: auto;
7 | width: 400px;
8 | margin: 0;
9 | padding: 0;
10 | font-family: "Roboto", sans-serif;
11 | }
12 |
13 | button {
14 | background-color: #33c3f0;
15 | border-radius: 3px;
16 | color: white;
17 | border: none;
18 | margin-right: 5px;
19 | margin-bottom: 10px;
20 | padding: 5px 10px;
21 | }
22 |
23 | .fa-cog:hover {
24 | color: #33c3f0;
25 | }
26 |
27 | .fa-trash-o:hover {
28 | color: red;
29 | cursor: pointer;
30 | }
31 |
32 | .footer {
33 | position: fixed;
34 | height: 40px;
35 | width: 360px;
36 | display: flex;
37 | align-items: center;
38 | justify-content: space-between;
39 | bottom: 0;
40 | background-color: #fff;
41 | }
42 |
43 | .footer__author {
44 | width: 50%;
45 | }
46 |
47 | .footer__github {
48 | width: 60px;
49 | display: flex;
50 | justify-content: space-between;
51 | }
52 |
53 | .footer__link {
54 | text-decoration: none;
55 | color: #000;
56 | }
57 |
58 | .footer__social {
59 | display: flex;
60 | justify-content: space-between;
61 | }
62 |
63 | h2 {
64 | margin-top: 0;
65 | }
66 |
67 | .header {
68 | display: flex;
69 | margin-top: 0;
70 | align-items: center;
71 | justify-content: space-between;
72 | font-size: 10px;
73 | }
74 |
75 | .history {
76 | margin-top: 20px;
77 | }
78 |
79 | .hidden {
80 | display: none;
81 | }
82 |
83 | .nav {
84 | display: flex;
85 | flex-direction: row-reverse;
86 | justify-content: flex-end;
87 | align-items: flex-start;
88 | /*justify-content: space-between;*/
89 | }
90 |
91 | .visible {
92 | display: block;
93 | }
94 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const CopyPlugin = require('copy-webpack-plugin')
3 | const HtmlPlugin = require('html-webpack-plugin')
4 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
5 |
6 | require("babel-core/register");
7 | require("babel-polyfill");
8 |
9 | const PAGES_PATH = './src/pages'
10 |
11 | function generateHtmlPlugins(items) {
12 | return items.map( (name) => new HtmlPlugin(
13 | {
14 | filename: `./${name}.html`,
15 | chunks: [ name ],
16 | }
17 | ))
18 | }
19 |
20 | module.exports = {
21 | entry: {
22 | background: [
23 | 'babel-polyfill',
24 | `${PAGES_PATH}/background`,
25 | ],
26 | popup: [
27 | 'babel-polyfill',
28 | `${PAGES_PATH}/popup`,
29 | ],
30 | index: [
31 | 'babel-polyfill',
32 | `${PAGES_PATH}/content`,
33 | ]
34 | },
35 | output: {
36 | path: path.resolve('dist/pages'),
37 | filename: '[name].js'
38 | },
39 |
40 | module: {
41 | rules: [
42 | {
43 | test: /\.js$/,
44 | use: [ 'babel-loader' ]
45 | },
46 | {
47 | test: /\.jpe?g$|\.gif$|\.png$|\.ttf$|\.eot$|\.svg$/,
48 | use: 'file-loader?name=[name].[ext]?[hash]'
49 | },
50 | {
51 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
52 | loader: 'url-loader?limit=10000&mimetype=application/fontwoff'
53 | },
54 | {
55 | test: /\.css$/,
56 | loaders: ["style-loader","css-loader"]
57 | }
58 | ]
59 | },
60 | plugins: [
61 | new ExtractTextPlugin(
62 | {
63 | filename: '[name].[contenthash].css',
64 | }
65 | ),
66 | new CopyPlugin(
67 | [
68 | {
69 | from: 'src',
70 | to: path.resolve('dist'),
71 | ignore: [ 'pages/**/*' ]
72 | }
73 | ]
74 | ),
75 | ...generateHtmlPlugins(
76 | [
77 | 'background',
78 | 'popup'
79 | ]
80 | )
81 | ]
82 | }
83 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chrome-redux-tutorial",
3 | "jest": {
4 | "verbose": true,
5 | "moduleNameMapper": {
6 | "\\.(css|less)$": "identity-obj-proxy"
7 | },
8 | "roots": [
9 | "/src/pages/popup/"
10 | ]
11 | },
12 | "description": "Redux Chrome extension example",
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/pierroberto/chrome-navigator.git"
16 | },
17 | "version": "0.0.1",
18 | "author": {
19 | "name": "Your Name",
20 | "email": "user@server.com"
21 | },
22 | "license": "MIT",
23 | "scripts": {
24 | "build": "webpack --watch --progress",
25 | "test": "jest --coverage",
26 | "test:watch": "npm test --watch"
27 | },
28 | "dependencies": {
29 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
30 | "babel-polyfill": "^6.23.0",
31 | "babel-preset-es2015": "^6.24.1",
32 | "babel-preset-stage-2": "^6.24.1",
33 | "file-loader": "^0.11.2",
34 | "lodash": "^4.17.11",
35 | "react": "^15.4.1",
36 | "react-chrome-redux": "^1.3.3",
37 | "react-dom": "^15.4.1",
38 | "react-redux": "^4.4.6",
39 | "react-router": "^4.2.0",
40 | "react-router-dom": "^4.2.2",
41 | "redux": "^3.6.0",
42 | "redux-logger": "^3.0.6",
43 | "redux-thunk": "^2.3.0",
44 | "truncate": "^2.0.0",
45 | "url-loader": "^0.5.9",
46 | "uuid": "^3.1.0",
47 | "webpack-css-loaders": "^1.0.0"
48 | },
49 | "devDependencies": {
50 | "babel-core": "^6.26.3",
51 | "babel-jest": "^22.0.4",
52 | "babel-loader": "^7.1.1",
53 | "babel-plugin-react-html-attrs": "^2.0.0",
54 | "babel-plugin-transform-async-to-generator": "^6.24.1",
55 | "babel-preset-react": "^6.24.1",
56 | "copy-webpack-plugin": "^4.0.1",
57 | "enzyme": "^3.9.0",
58 | "enzyme-adapter-react-15": "^1.3.1",
59 | "extract-text-webpack-plugin": "^3.0.0",
60 | "html-webpack-plugin": "^2.30.1",
61 | "identity-obj-proxy": "^3.0.0",
62 | "jest": "^22.0.4",
63 | "merge": "^1.2.1",
64 | "mime": "^1.4.1",
65 | "react-addons-test-utils": "^15.6.2",
66 | "react-test-renderer": "^15.5.4",
67 | "webpack": "^3.5.3",
68 | "write-file-webpack-plugin": "^3.4.2"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/pages/background/reducers/bookmarks.js:
--------------------------------------------------------------------------------
1 | const defaultState = {
2 | tabs: [],
3 | chronology: [],
4 | addFromButton: false,
5 | search: "",
6 | searchResult: []
7 | };
8 |
9 | const bookmark = (state = defaultState, action) => {
10 | switch (action.type) {
11 | case "ADD":
12 | return {
13 | ...state,
14 | tabs: [{ tab: action.urlList, expiry: action.expiry }, ...state.tabs]
15 | };
16 | case "ADD-FROM-BUTTON":
17 | return {
18 | ...state,
19 | addFromButton: action.addFromButton
20 | };
21 | case "REFRESH":
22 | return {
23 | ...state,
24 | tabs: [{ tab: action.urlList, expiry: action.expiry }, ...state.tabs]
25 | };
26 | case "DELETE-ALL":
27 | return {
28 | ...state,
29 | tabs: []
30 | };
31 | case "DELETE-ONE":
32 | return {
33 | ...state,
34 | tabs: [
35 | ...state.tabs.filter(element => element.tab[0].url !== action.url)
36 | ],
37 | addFromButton: false
38 | };
39 | case "EXPIRY":
40 | return {
41 | ...state,
42 | tabs: [
43 | ...state.tabs.filter(element => element.tab[0].url !== action.url)
44 | ],
45 | chronology: [
46 | ...state.tabs.filter(element => element.tab[0].url === action.url),
47 | ...state.chronology
48 | ]
49 | };
50 | case "SEARCH":
51 | return {
52 | ...state,
53 | searchResult: [
54 | ...state.tabs.filter(
55 | element =>
56 | element.tab[0].title
57 | .toLowerCase()
58 | .indexOf(action.textSearched.toLowerCase()) !== -1
59 | ),
60 | ...state.chronology.filter(
61 | element =>
62 | element.tab[0].title
63 | .toLowerCase()
64 | .indexOf(action.textSearched.toLowerCase()) !== -1
65 | )
66 | ],
67 | search: action.textSearched
68 | };
69 | case "EMPTY-SEARCH":
70 | return {
71 | ...state,
72 | searchResult: [],
73 | search: ""
74 | };
75 | }
76 | return state;
77 | };
78 |
79 | export default bookmark;
80 |
--------------------------------------------------------------------------------
/src/pages/content/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { render } from "react-dom";
3 | import "./index.css";
4 | import { Store } from "react-chrome-redux";
5 | import { Provider } from "react-redux";
6 | import { connect } from "react-redux";
7 |
8 | const store = new Store({
9 | portName: "COUNTING"
10 | });
11 |
12 | export default class InjectApp extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.classButtonDetail = "";
16 | this.classPopupDetail = "";
17 | }
18 | render() {
19 | if (!this.props.settings || !this.props.animation) return null;
20 | if (this.props.animation.buttonCog) {
21 | this.classButtonDetail = "circle faa-tada ";
22 | this.classPopupDetail = "popup popup-visibility-show ";
23 | setTimeout(function() {
24 | store.dispatch({ type: "TOGGLE-COG", buttonCog: false });
25 | }, 3500);
26 | }
27 | if (!this.props.animation.buttonCog) {
28 | this.classButtonDetail = "circle ";
29 | this.classPopupDetail = "popup popup-visibility-hide";
30 | }
31 | return (
32 |
33 |
{
41 | store.dispatch({ type: "ADD-FROM-BUTTON", addFromButton: true });
42 | store.dispatch({ type: "TOGGLE-COG", buttonCog: true });
43 | }}
44 | >
45 |
Saved
46 |
47 |
48 |
49 |
50 | );
51 | }
52 | }
53 |
54 | const mapStateToProps = state => ({
55 | settings: state.settings,
56 | animation: state.animation
57 | });
58 |
59 | const ConnectedInjectApp = connect(mapStateToProps)(InjectApp);
60 |
61 | window.addEventListener("load", () => {
62 | const injectDOM = document.createElement("div");
63 | injectDOM.className = "inject-react";
64 | injectDOM.style.textAlign = "center";
65 | document.body.appendChild(injectDOM);
66 | render(
67 |
68 |
69 | ,
70 | injectDOM
71 | );
72 | });
73 |
--------------------------------------------------------------------------------
/src/pages/popup/ItemView.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import "./item-view.css";
3 |
4 | export default class ItemView extends React.Component {
5 | checkDeleteButton() {
6 | if (this.props.deleteTab) {
7 | return (
8 |
9 | this.props.deleteTab(this.props.tab_url)}
12 | />
13 |
14 | );
15 | }
16 | }
17 |
18 | checkExpireDate() {
19 | let timeLeft;
20 | const time = new Date(
21 | this.props.expirySettings - (Date.now() - this.props.expiry)
22 | );
23 | const seconds = (time / 1000).toFixed(1);
24 | if (this.props.expired || seconds <= 0)
25 | return Expired
;
26 | const minutes = (time / (1000 * 60)).toFixed(1);
27 | const hours = (time / (1000 * 60 * 60)).toFixed(1);
28 | const days = (time / (1000 * 60 * 60 * 24)).toFixed(1);
29 |
30 | if (seconds < 60) {
31 | Math.floor(seconds) === 1
32 | ? (timeLeft = Math.floor(seconds) + " second")
33 | : (timeLeft = Math.floor(seconds) + " seconds");
34 | } else if (minutes < 60) {
35 | Math.floor(minutes) === 1
36 | ? (timeLeft = Math.floor(minutes) + " minute")
37 | : (timeLeft = Math.floor(minutes) + " minutes");
38 | } else if (hours < 24) {
39 | Math.floor(hours) === 1
40 | ? (timeLeft = Math.floor(hours) + " hour")
41 | : (timeLeft = Math.floor(hours) + " hours");
42 | } else {
43 | Math.floor(days) === 1
44 | ? (timeLeft = Math.floor(days) + " day")
45 | : (timeLeft = Math.floor(days) + " days");
46 | }
47 | return Expires in {timeLeft}
;
48 | }
49 |
50 | render() {
51 | return (
52 |
53 |
54 |
55 |
56 |
57 |
58 |
63 |
64 |
65 |
66 | {this.checkExpireDate()}
67 |
68 |
69 | {this.checkDeleteButton()}
70 |
71 | );
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/pages/popup/Settings.js:
--------------------------------------------------------------------------------
1 | import { Store } from "react-chrome-redux";
2 | import React, { Component } from "react";
3 | import "./settings.css";
4 | import "./App";
5 | import { Link } from "react-router-dom";
6 | import { connect } from "react-redux";
7 | import {
8 | toggleButton,
9 | toggleButtonHistory,
10 | expireDate
11 | } from "../background/actions";
12 |
13 | class Settings extends React.Component {
14 | // ======================== RENDERING
15 |
16 | render() {
17 | return (
18 |
19 |
20 |
Settings
21 |
22 |
23 |
24 |
25 |
26 |
63 |
64 | );
65 | }
66 | }
67 |
68 | const mapStateToProps = state => ({
69 | bookmark: state.bookmark,
70 | settings: state.settings
71 | });
72 |
73 | const mapDispatchToProps = dispatch => ({
74 | toggle: flag => dispatch(toggleButton(flag)),
75 | toggleHistory: flag => dispatch(toggleButtonHistory(flag)),
76 | expire: date => dispatch(expireDate(date))
77 | });
78 |
79 | export default connect(
80 | mapStateToProps,
81 | mapDispatchToProps
82 | )(Settings);
83 |
--------------------------------------------------------------------------------
/coverage/lcov-report/background/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Code coverage report for background
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
19 | All files background
20 |
21 |
22 |
23 | 54.17%
24 | Statements
25 | 13/24
26 |
27 |
28 | 100%
29 | Branches
30 | 0/0
31 |
32 |
33 | 8.33%
34 | Functions
35 | 1/12
36 |
37 |
38 | 100%
39 | Lines
40 | 12/12
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | File
50 |
51 | Statements
52 |
53 | Branches
54 |
55 | Functions
56 |
57 | Lines
58 |
59 |
60 |
61 |
62 | actions.js
63 |
64 | 54.17%
65 | 13/24
66 | 100%
67 | 0/0
68 | 8.33%
69 | 1/12
70 | 100%
71 | 12/12
72 |
73 |
74 |
75 |
76 |
77 |
78 |
82 |
83 |
84 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/coverage/lcov-report/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Code coverage report for All files
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
19 | All files
20 |
21 |
22 |
23 | 18.02%
24 | Statements
25 | 20/111
26 |
27 |
28 | 0%
29 | Branches
30 | 0/38
31 |
32 |
33 | 3.45%
34 | Functions
35 | 2/58
36 |
37 |
38 | 21.11%
39 | Lines
40 | 19/90
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | File
50 |
51 | Statements
52 |
53 | Branches
54 |
55 | Functions
56 |
57 | Lines
58 |
59 |
60 |
61 |
62 | background
63 |
64 | 54.17%
65 | 13/24
66 | 100%
67 | 0/0
68 | 8.33%
69 | 1/12
70 | 100%
71 | 12/12
72 |
73 |
74 |
75 | popup
76 |
77 | 8.05%
78 | 7/87
79 | 0%
80 | 0/38
81 | 2.17%
82 | 1/46
83 | 8.97%
84 | 7/78
85 |
86 |
87 |
88 |
89 |
90 |
91 |
95 |
96 |
97 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/coverage/lcov-report/popup/App.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Code coverage report for popup/App.js
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 | 100%
24 | Statements
25 | 1/1
26 |
27 |
28 | 100%
29 | Branches
30 | 0/0
31 |
32 |
33 | 100%
34 | Functions
35 | 1/1
36 |
37 |
38 | 100%
39 | Lines
40 | 1/1
41 |
42 |
43 |
44 |
45 |
46 | 1
47 | 2
48 | 3
49 | 4
50 | 5
51 | 6
52 | 7
53 | 8
54 | 9
55 | 10
56 | 11
57 | 12
58 | 13
59 | 14
60 | 15
61 | 16
62 | 17
63 | 18
64 | 19
65 | 20
66 | 21
67 | 22
68 | 23
69 | 24
70 | 25
71 | 26
72 | 27
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | 1x
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | import React, { Component } from 'react';
99 | import { connect } from 'react-redux';
100 | import './app.css';
101 | import ListView from './ListView.js';
102 | import Settings from './Settings';
103 | import Dashboard from './Dashboard';
104 | import {Route, BrowserRouter as Router,Switch} from 'react-router-dom'
105 |
106 |
107 | class App extends React.Component {
108 |
109 | render () {
110 | return (
111 | <Router>
112 | <div className='wrapper'>
113 | <Switch>
114 | <Route exact path='/pages/popup.html' component={Dashboard} />
115 | <Route path='/pages/settings' component={Settings} />
116 | </Switch>
117 | </div>
118 | </Router>
119 | )
120 | }
121 | }
122 |
123 | export default App;
124 |
125 |
126 |
127 |
128 |
132 |
133 |
134 |
141 |
142 |
143 |
144 |
--------------------------------------------------------------------------------
/src/pages/popup/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import {
4 | refreshBookmark,
5 | deleteAllBookmark,
6 | deleteOneBookmark,
7 | addBookmark,
8 | addFromButton
9 | } from "../background/actions";
10 | import "./dashboard.css";
11 | import ListView from "./ListView.js";
12 | import Settings from "./Settings";
13 | import Search from "./Search";
14 | import truncate from "truncate";
15 | import { Link } from "react-router-dom";
16 |
17 | class Dashboard extends React.Component {
18 | constructor(props) {
19 | super(props);
20 | }
21 |
22 | saveBookmark() {
23 | return new Promise((resolved, rejected) => {
24 | chrome.tabs.query({ active: true, lastFocusedWindow: true }, data => {
25 | resolved(data);
26 | });
27 | }).then(link => {
28 | if (!link[0].favIconUrl) link[0].favIconUrl = "../assets/nothing.png";
29 | if (link[0].title.length > 10)
30 | link[0].title = truncate(link[0].title.toString(), 50);
31 | const flag = this.checkUrl(link);
32 | if (flag) {
33 | this.props.add(link);
34 | } else {
35 | //If it already exists remove the old link and add a new one
36 | this.props.deleteOne(link[0].url);
37 | this.props.add(link);
38 | }
39 | });
40 | }
41 |
42 | checkUrl(link) {
43 | for (let i = 0; i < this.props.bookmark.tabs.length; i++) {
44 | if (this.props.bookmark.tabs[i].tab[0].url === link[0].url) return false;
45 | }
46 | return true;
47 | }
48 |
49 | clearAll() {
50 | this.props.deleteAll();
51 | }
52 |
53 | deleteTab = url => {
54 | this.props.deleteOne(url);
55 | return data;
56 | };
57 |
58 | componentDidMount() {
59 | // VERSION 1.0.2 Double check if the link has expireDate
60 |
61 | this.props.bookmark.tabs.map(tab => {
62 | if (tab.expiry >= this.props.settings.expireDate + Date.now()) {
63 | store.dispatch({ type: "EXPIRY", url: tab.tab[0].url });
64 | }
65 | });
66 | }
67 |
68 | checkSearch() {
69 | if (!this.props.bookmark.search) {
70 | return (
71 |
72 |
78 |
81 |
History
82 |
88 |
89 |
90 | );
91 | }
92 | }
93 |
94 | // ====================== RENDERING
95 |
96 | render() {
97 | // I check if the button in the content script has been clicked
98 | if (this.props.bookmark.addFromButton) {
99 | this.saveBookmark();
100 | this.props.addThroughButton(false);
101 | }
102 |
103 | return (
104 |
105 |
106 |
Pin Tabs
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | this.saveBookmark()}>Add
115 | {/* this.clearAll()}>Delete All */}
116 |
117 |
118 |
119 |
120 |
121 |
122 | {this.checkSearch()}
123 |
124 |
125 |
Pier Roberto Lucisano 📌 2020
126 |
135 |
147 |
148 |
149 | );
150 | }
151 | }
152 |
153 | const mapStateToProps = state => ({
154 | bookmark: state.bookmark,
155 | settings: state.settings,
156 | animation: state.animation
157 | });
158 |
159 | const mapDispatchToProps = dispatch => ({
160 | add: url => dispatch(addBookmark(url)),
161 | refresh: data => dispatch(refreshBookmark(data)),
162 | deleteAll: () => dispatch(deleteAllBookmark()),
163 | deleteOne: url => dispatch(deleteOneBookmark(url)),
164 | addThroughButton: flag => dispatch(addFromButton(flag))
165 | });
166 |
167 | export default connect(
168 | mapStateToProps,
169 | mapDispatchToProps
170 | )(Dashboard);
171 |
--------------------------------------------------------------------------------
/coverage/lcov-report/sorter.js:
--------------------------------------------------------------------------------
1 | var addSorting = (function () {
2 | "use strict";
3 | var cols,
4 | currentSort = {
5 | index: 0,
6 | desc: false
7 | };
8 |
9 | // returns the summary table element
10 | function getTable() { return document.querySelector('.coverage-summary'); }
11 | // returns the thead element of the summary table
12 | function getTableHeader() { return getTable().querySelector('thead tr'); }
13 | // returns the tbody element of the summary table
14 | function getTableBody() { return getTable().querySelector('tbody'); }
15 | // returns the th element for nth column
16 | function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; }
17 |
18 | // loads all columns
19 | function loadColumns() {
20 | var colNodes = getTableHeader().querySelectorAll('th'),
21 | colNode,
22 | cols = [],
23 | col,
24 | i;
25 |
26 | for (i = 0; i < colNodes.length; i += 1) {
27 | colNode = colNodes[i];
28 | col = {
29 | key: colNode.getAttribute('data-col'),
30 | sortable: !colNode.getAttribute('data-nosort'),
31 | type: colNode.getAttribute('data-type') || 'string'
32 | };
33 | cols.push(col);
34 | if (col.sortable) {
35 | col.defaultDescSort = col.type === 'number';
36 | colNode.innerHTML = colNode.innerHTML + ' ';
37 | }
38 | }
39 | return cols;
40 | }
41 | // attaches a data attribute to every tr element with an object
42 | // of data values keyed by column name
43 | function loadRowData(tableRow) {
44 | var tableCols = tableRow.querySelectorAll('td'),
45 | colNode,
46 | col,
47 | data = {},
48 | i,
49 | val;
50 | for (i = 0; i < tableCols.length; i += 1) {
51 | colNode = tableCols[i];
52 | col = cols[i];
53 | val = colNode.getAttribute('data-value');
54 | if (col.type === 'number') {
55 | val = Number(val);
56 | }
57 | data[col.key] = val;
58 | }
59 | return data;
60 | }
61 | // loads all row data
62 | function loadData() {
63 | var rows = getTableBody().querySelectorAll('tr'),
64 | i;
65 |
66 | for (i = 0; i < rows.length; i += 1) {
67 | rows[i].data = loadRowData(rows[i]);
68 | }
69 | }
70 | // sorts the table using the data for the ith column
71 | function sortByIndex(index, desc) {
72 | var key = cols[index].key,
73 | sorter = function (a, b) {
74 | a = a.data[key];
75 | b = b.data[key];
76 | return a < b ? -1 : a > b ? 1 : 0;
77 | },
78 | finalSorter = sorter,
79 | tableBody = document.querySelector('.coverage-summary tbody'),
80 | rowNodes = tableBody.querySelectorAll('tr'),
81 | rows = [],
82 | i;
83 |
84 | if (desc) {
85 | finalSorter = function (a, b) {
86 | return -1 * sorter(a, b);
87 | };
88 | }
89 |
90 | for (i = 0; i < rowNodes.length; i += 1) {
91 | rows.push(rowNodes[i]);
92 | tableBody.removeChild(rowNodes[i]);
93 | }
94 |
95 | rows.sort(finalSorter);
96 |
97 | for (i = 0; i < rows.length; i += 1) {
98 | tableBody.appendChild(rows[i]);
99 | }
100 | }
101 | // removes sort indicators for current column being sorted
102 | function removeSortIndicators() {
103 | var col = getNthColumn(currentSort.index),
104 | cls = col.className;
105 |
106 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
107 | col.className = cls;
108 | }
109 | // adds sort indicators for current column being sorted
110 | function addSortIndicators() {
111 | getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted';
112 | }
113 | // adds event listeners for all sorter widgets
114 | function enableUI() {
115 | var i,
116 | el,
117 | ithSorter = function ithSorter(i) {
118 | var col = cols[i];
119 |
120 | return function () {
121 | var desc = col.defaultDescSort;
122 |
123 | if (currentSort.index === i) {
124 | desc = !currentSort.desc;
125 | }
126 | sortByIndex(i, desc);
127 | removeSortIndicators();
128 | currentSort.index = i;
129 | currentSort.desc = desc;
130 | addSortIndicators();
131 | };
132 | };
133 | for (i =0 ; i < cols.length; i += 1) {
134 | if (cols[i].sortable) {
135 | // add the click event handler on the th so users
136 | // dont have to click on those tiny arrows
137 | el = getNthColumn(i).querySelector('.sorter').parentElement;
138 | if (el.addEventListener) {
139 | el.addEventListener('click', ithSorter(i));
140 | } else {
141 | el.attachEvent('onclick', ithSorter(i));
142 | }
143 | }
144 | }
145 | }
146 | // adds sorting functionality to the UI
147 | return function () {
148 | if (!getTable()) {
149 | return;
150 | }
151 | cols = loadColumns();
152 | loadData(cols);
153 | addSortIndicators();
154 | enableUI();
155 | };
156 | })();
157 |
158 | window.addEventListener('load', addSorting);
159 |
--------------------------------------------------------------------------------
/coverage/lcov-report/base.css:
--------------------------------------------------------------------------------
1 | body, html {
2 | margin:0; padding: 0;
3 | height: 100%;
4 | }
5 | body {
6 | font-family: Helvetica Neue, Helvetica, Arial;
7 | font-size: 14px;
8 | color:#333;
9 | }
10 | .small { font-size: 12px; }
11 | *, *:after, *:before {
12 | -webkit-box-sizing:border-box;
13 | -moz-box-sizing:border-box;
14 | box-sizing:border-box;
15 | }
16 | h1 { font-size: 20px; margin: 0;}
17 | h2 { font-size: 14px; }
18 | pre {
19 | font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
20 | margin: 0;
21 | padding: 0;
22 | -moz-tab-size: 2;
23 | -o-tab-size: 2;
24 | tab-size: 2;
25 | }
26 | a { color:#0074D9; text-decoration:none; }
27 | a:hover { text-decoration:underline; }
28 | .strong { font-weight: bold; }
29 | .space-top1 { padding: 10px 0 0 0; }
30 | .pad2y { padding: 20px 0; }
31 | .pad1y { padding: 10px 0; }
32 | .pad2x { padding: 0 20px; }
33 | .pad2 { padding: 20px; }
34 | .pad1 { padding: 10px; }
35 | .space-left2 { padding-left:55px; }
36 | .space-right2 { padding-right:20px; }
37 | .center { text-align:center; }
38 | .clearfix { display:block; }
39 | .clearfix:after {
40 | content:'';
41 | display:block;
42 | height:0;
43 | clear:both;
44 | visibility:hidden;
45 | }
46 | .fl { float: left; }
47 | @media only screen and (max-width:640px) {
48 | .col3 { width:100%; max-width:100%; }
49 | .hide-mobile { display:none!important; }
50 | }
51 |
52 | .quiet {
53 | color: #7f7f7f;
54 | color: rgba(0,0,0,0.5);
55 | }
56 | .quiet a { opacity: 0.7; }
57 |
58 | .fraction {
59 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
60 | font-size: 10px;
61 | color: #555;
62 | background: #E8E8E8;
63 | padding: 4px 5px;
64 | border-radius: 3px;
65 | vertical-align: middle;
66 | }
67 |
68 | div.path a:link, div.path a:visited { color: #333; }
69 | table.coverage {
70 | border-collapse: collapse;
71 | margin: 10px 0 0 0;
72 | padding: 0;
73 | }
74 |
75 | table.coverage td {
76 | margin: 0;
77 | padding: 0;
78 | vertical-align: top;
79 | }
80 | table.coverage td.line-count {
81 | text-align: right;
82 | padding: 0 5px 0 20px;
83 | }
84 | table.coverage td.line-coverage {
85 | text-align: right;
86 | padding-right: 10px;
87 | min-width:20px;
88 | }
89 |
90 | table.coverage td span.cline-any {
91 | display: inline-block;
92 | padding: 0 5px;
93 | width: 100%;
94 | }
95 | .missing-if-branch {
96 | display: inline-block;
97 | margin-right: 5px;
98 | border-radius: 3px;
99 | position: relative;
100 | padding: 0 4px;
101 | background: #333;
102 | color: yellow;
103 | }
104 |
105 | .skip-if-branch {
106 | display: none;
107 | margin-right: 10px;
108 | position: relative;
109 | padding: 0 4px;
110 | background: #ccc;
111 | color: white;
112 | }
113 | .missing-if-branch .typ, .skip-if-branch .typ {
114 | color: inherit !important;
115 | }
116 | .coverage-summary {
117 | border-collapse: collapse;
118 | width: 100%;
119 | }
120 | .coverage-summary tr { border-bottom: 1px solid #bbb; }
121 | .keyline-all { border: 1px solid #ddd; }
122 | .coverage-summary td, .coverage-summary th { padding: 10px; }
123 | .coverage-summary tbody { border: 1px solid #bbb; }
124 | .coverage-summary td { border-right: 1px solid #bbb; }
125 | .coverage-summary td:last-child { border-right: none; }
126 | .coverage-summary th {
127 | text-align: left;
128 | font-weight: normal;
129 | white-space: nowrap;
130 | }
131 | .coverage-summary th.file { border-right: none !important; }
132 | .coverage-summary th.pct { }
133 | .coverage-summary th.pic,
134 | .coverage-summary th.abs,
135 | .coverage-summary td.pct,
136 | .coverage-summary td.abs { text-align: right; }
137 | .coverage-summary td.file { white-space: nowrap; }
138 | .coverage-summary td.pic { min-width: 120px !important; }
139 | .coverage-summary tfoot td { }
140 |
141 | .coverage-summary .sorter {
142 | height: 10px;
143 | width: 7px;
144 | display: inline-block;
145 | margin-left: 0.5em;
146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
147 | }
148 | .coverage-summary .sorted .sorter {
149 | background-position: 0 -20px;
150 | }
151 | .coverage-summary .sorted-desc .sorter {
152 | background-position: 0 -10px;
153 | }
154 | .status-line { height: 10px; }
155 | /* dark red */
156 | .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
157 | .low .chart { border:1px solid #C21F39 }
158 | /* medium red */
159 | .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
160 | /* light red */
161 | .low, .cline-no { background:#FCE1E5 }
162 | /* light green */
163 | .high, .cline-yes { background:rgb(230,245,208) }
164 | /* medium green */
165 | .cstat-yes { background:rgb(161,215,106) }
166 | /* dark green */
167 | .status-line.high, .high .cover-fill { background:rgb(77,146,33) }
168 | .high .chart { border:1px solid rgb(77,146,33) }
169 |
170 |
171 | .medium .chart { border:1px solid #666; }
172 | .medium .cover-fill { background: #666; }
173 |
174 | .cbranch-no { background: yellow !important; color: #111; }
175 |
176 | .cstat-skip { background: #ddd; color: #111; }
177 | .fstat-skip { background: #ddd; color: #111 !important; }
178 | .cbranch-skip { background: #ddd !important; color: #111; }
179 |
180 | span.cline-neutral { background: #eaeaea; }
181 | .medium { background: #eaeaea; }
182 |
183 | .cover-fill, .cover-empty {
184 | display:inline-block;
185 | height: 12px;
186 | }
187 | .chart {
188 | line-height: 0;
189 | }
190 | .cover-empty {
191 | background: white;
192 | }
193 | .cover-full {
194 | border-right: none !important;
195 | }
196 | pre.prettyprint {
197 | border: none !important;
198 | padding: 0 !important;
199 | margin: 0 !important;
200 | }
201 | .com { color: #999 !important; }
202 | .ignore-none { color: #999; font-weight: normal; }
203 |
204 | .wrapper {
205 | min-height: 100%;
206 | height: auto !important;
207 | height: 100%;
208 | margin: 0 auto -48px;
209 | }
210 | .footer, .push {
211 | height: 48px;
212 | }
213 |
--------------------------------------------------------------------------------
/coverage/lcov.info:
--------------------------------------------------------------------------------
1 | TN:
2 | SF:/Users/pierroberto/Documents/personal projects/chrome-navigator/src/pages/background/actions.js
3 | FN:3,(anonymous_0)
4 | FN:9,(anonymous_1)
5 | FN:13,(anonymous_2)
6 | FN:18,(anonymous_3)
7 | FN:24,(anonymous_4)
8 | FN:29,(anonymous_5)
9 | FN:34,(anonymous_6)
10 | FN:40,(anonymous_7)
11 | FN:45,(anonymous_8)
12 | FN:50,(anonymous_9)
13 | FN:58,(anonymous_10)
14 | FN:63,(anonymous_11)
15 | FNF:12
16 | FNH:1
17 | FNDA:0,(anonymous_0)
18 | FNDA:0,(anonymous_1)
19 | FNDA:0,(anonymous_2)
20 | FNDA:1,(anonymous_3)
21 | FNDA:0,(anonymous_4)
22 | FNDA:0,(anonymous_5)
23 | FNDA:0,(anonymous_6)
24 | FNDA:0,(anonymous_7)
25 | FNDA:0,(anonymous_8)
26 | FNDA:0,(anonymous_9)
27 | FNDA:0,(anonymous_10)
28 | FNDA:0,(anonymous_11)
29 | DA:3,1
30 | DA:9,1
31 | DA:13,1
32 | DA:18,1
33 | DA:24,1
34 | DA:29,1
35 | DA:34,1
36 | DA:40,1
37 | DA:45,1
38 | DA:50,1
39 | DA:58,1
40 | DA:63,1
41 | LF:12
42 | LH:12
43 | BRF:0
44 | BRH:0
45 | end_of_record
46 | TN:
47 | SF:/Users/pierroberto/Documents/personal projects/chrome-navigator/src/pages/popup/App.js
48 | FN:12,(anonymous_0)
49 | FNF:1
50 | FNH:1
51 | FNDA:1,(anonymous_0)
52 | DA:13,1
53 | LF:1
54 | LH:1
55 | BRF:0
56 | BRH:0
57 | end_of_record
58 | TN:
59 | SF:/Users/pierroberto/Documents/personal projects/chrome-navigator/src/pages/popup/Dashboard.js
60 | FN:18,(anonymous_0)
61 | FN:22,(anonymous_1)
62 | FN:23,(anonymous_2)
63 | FN:24,(anonymous_3)
64 | FN:27,(anonymous_4)
65 | FN:42,(anonymous_5)
66 | FN:49,(anonymous_6)
67 | FN:53,(anonymous_7)
68 | FN:58,(anonymous_8)
69 | FN:61,(anonymous_9)
70 | FN:68,(anonymous_10)
71 | FN:83,(anonymous_11)
72 | FN:101,(anonymous_12)
73 | FN:142,(anonymous_13)
74 | FN:148,(anonymous_14)
75 | FN:149,(anonymous_15)
76 | FN:150,(anonymous_16)
77 | FN:151,(anonymous_17)
78 | FN:152,(anonymous_18)
79 | FN:153,(anonymous_19)
80 | FNF:20
81 | FNH:0
82 | FNDA:0,(anonymous_0)
83 | FNDA:0,(anonymous_1)
84 | FNDA:0,(anonymous_2)
85 | FNDA:0,(anonymous_3)
86 | FNDA:0,(anonymous_4)
87 | FNDA:0,(anonymous_5)
88 | FNDA:0,(anonymous_6)
89 | FNDA:0,(anonymous_7)
90 | FNDA:0,(anonymous_8)
91 | FNDA:0,(anonymous_9)
92 | FNDA:0,(anonymous_10)
93 | FNDA:0,(anonymous_11)
94 | FNDA:0,(anonymous_12)
95 | FNDA:0,(anonymous_13)
96 | FNDA:0,(anonymous_14)
97 | FNDA:0,(anonymous_15)
98 | FNDA:0,(anonymous_16)
99 | FNDA:0,(anonymous_17)
100 | FNDA:0,(anonymous_18)
101 | FNDA:0,(anonymous_19)
102 | DA:19,0
103 | DA:23,0
104 | DA:24,0
105 | DA:25,0
106 | DA:28,0
107 | DA:29,0
108 | DA:30,0
109 | DA:31,0
110 | DA:32,0
111 | DA:33,0
112 | DA:36,0
113 | DA:37,0
114 | DA:43,0
115 | DA:44,0
116 | DA:46,0
117 | DA:50,0
118 | DA:54,0
119 | DA:55,0
120 | DA:61,0
121 | DA:62,0
122 | DA:63,0
123 | DA:69,0
124 | DA:70,0
125 | DA:85,0
126 | DA:86,0
127 | DA:87,0
128 | DA:90,0
129 | DA:101,0
130 | DA:142,1
131 | DA:148,1
132 | DA:149,0
133 | DA:150,0
134 | DA:151,0
135 | DA:152,0
136 | DA:153,0
137 | LF:35
138 | LH:2
139 | BRDA:28,0,0,0
140 | BRDA:28,0,1,0
141 | BRDA:29,1,0,0
142 | BRDA:29,1,1,0
143 | BRDA:32,2,0,0
144 | BRDA:32,2,1,0
145 | BRDA:44,3,0,0
146 | BRDA:44,3,1,0
147 | BRDA:62,4,0,0
148 | BRDA:62,4,1,0
149 | BRDA:69,5,0,0
150 | BRDA:69,5,1,0
151 | BRDA:85,6,0,0
152 | BRDA:85,6,1,0
153 | BRDA:112,7,0,0
154 | BRDA:112,7,1,0
155 | BRF:16
156 | BRH:0
157 | end_of_record
158 | TN:
159 | SF:/Users/pierroberto/Documents/personal projects/chrome-navigator/src/pages/popup/ItemView.js
160 | FN:6,(anonymous_0)
161 | FN:12,(anonymous_1)
162 | FN:20,(anonymous_2)
163 | FN:45,(anonymous_3)
164 | FNF:4
165 | FNH:0
166 | FNDA:0,(anonymous_0)
167 | FNDA:0,(anonymous_1)
168 | FNDA:0,(anonymous_2)
169 | FNDA:0,(anonymous_3)
170 | DA:7,0
171 | DA:8,0
172 | DA:12,0
173 | DA:21,0
174 | DA:23,0
175 | DA:24,0
176 | DA:25,0
177 | DA:26,0
178 | DA:27,0
179 | DA:29,0
180 | DA:30,0
181 | DA:31,0
182 | DA:32,0
183 | DA:33,0
184 | DA:34,0
185 | DA:36,0
186 | DA:38,0
187 | DA:46,0
188 | LF:18
189 | LH:0
190 | BRDA:7,0,0,0
191 | BRDA:7,0,1,0
192 | BRDA:21,1,0,0
193 | BRDA:21,1,1,0
194 | BRDA:29,2,0,0
195 | BRDA:29,2,1,0
196 | BRDA:30,3,0,0
197 | BRDA:30,3,1,0
198 | BRDA:31,4,0,0
199 | BRDA:31,4,1,0
200 | BRDA:32,5,0,0
201 | BRDA:32,5,1,0
202 | BRDA:33,6,0,0
203 | BRDA:33,6,1,0
204 | BRDA:34,7,0,0
205 | BRDA:34,7,1,0
206 | BRDA:36,8,0,0
207 | BRDA:36,8,1,0
208 | BRF:18
209 | BRH:0
210 | end_of_record
211 | TN:
212 | SF:/Users/pierroberto/Documents/personal projects/chrome-navigator/src/pages/popup/ListView.js
213 | FN:8,(anonymous_0)
214 | FN:14,(anonymous_1)
215 | FN:30,(anonymous_2)
216 | FNF:3
217 | FNH:0
218 | FNDA:0,(anonymous_0)
219 | FNDA:0,(anonymous_1)
220 | FNDA:0,(anonymous_2)
221 | DA:9,0
222 | DA:10,0
223 | DA:14,0
224 | DA:15,0
225 | DA:31,0
226 | LF:5
227 | LH:0
228 | BRDA:9,0,0,0
229 | BRDA:9,0,1,0
230 | BRF:2
231 | BRH:0
232 | end_of_record
233 | TN:
234 | SF:/Users/pierroberto/Documents/personal projects/chrome-navigator/src/pages/popup/Search.js
235 | FN:9,(anonymous_0)
236 | FN:21,(anonymous_1)
237 | FN:26,(anonymous_2)
238 | FN:29,(anonymous_3)
239 | FN:37,(anonymous_4)
240 | FN:43,(anonymous_5)
241 | FN:44,(anonymous_6)
242 | FN:45,(anonymous_7)
243 | FN:46,(anonymous_8)
244 | FNF:9
245 | FNH:0
246 | FNDA:0,(anonymous_0)
247 | FNDA:0,(anonymous_1)
248 | FNDA:0,(anonymous_2)
249 | FNDA:0,(anonymous_3)
250 | FNDA:0,(anonymous_4)
251 | FNDA:0,(anonymous_5)
252 | FNDA:0,(anonymous_6)
253 | FNDA:0,(anonymous_7)
254 | FNDA:0,(anonymous_8)
255 | DA:10,0
256 | DA:11,0
257 | DA:22,0
258 | DA:27,0
259 | DA:29,0
260 | DA:37,1
261 | DA:43,1
262 | DA:44,0
263 | DA:45,0
264 | DA:46,0
265 | LF:10
266 | LH:2
267 | BRDA:10,0,0,0
268 | BRDA:10,0,1,0
269 | BRF:2
270 | BRH:0
271 | end_of_record
272 | TN:
273 | SF:/Users/pierroberto/Documents/personal projects/chrome-navigator/src/pages/popup/Settings.js
274 | FN:16,(anonymous_0)
275 | FN:32,(anonymous_1)
276 | FN:50,(anonymous_2)
277 | FN:58,(anonymous_3)
278 | FN:68,(anonymous_4)
279 | FN:73,(anonymous_5)
280 | FN:74,(anonymous_6)
281 | FN:75,(anonymous_7)
282 | FN:76,(anonymous_8)
283 | FNF:9
284 | FNH:0
285 | FNDA:0,(anonymous_0)
286 | FNDA:0,(anonymous_1)
287 | FNDA:0,(anonymous_2)
288 | FNDA:0,(anonymous_3)
289 | FNDA:0,(anonymous_4)
290 | FNDA:0,(anonymous_5)
291 | FNDA:0,(anonymous_6)
292 | FNDA:0,(anonymous_7)
293 | FNDA:0,(anonymous_8)
294 | DA:17,0
295 | DA:32,0
296 | DA:50,0
297 | DA:58,0
298 | DA:68,1
299 | DA:73,1
300 | DA:74,0
301 | DA:75,0
302 | DA:76,0
303 | LF:9
304 | LH:2
305 | BRF:0
306 | BRH:0
307 | end_of_record
308 |
--------------------------------------------------------------------------------
/coverage/lcov-report/popup/ListView.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Code coverage report for popup/ListView.js
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 | 0%
24 | Statements
25 | 0/5
26 |
27 |
28 | 0%
29 | Branches
30 | 0/2
31 |
32 |
33 | 0%
34 | Functions
35 | 0/3
36 |
37 |
38 | 0%
39 | Lines
40 | 0/5
41 |
42 |
43 |
44 |
45 |
46 | 1
47 | 2
48 | 3
49 | 4
50 | 5
51 | 6
52 | 7
53 | 8
54 | 9
55 | 10
56 | 11
57 | 12
58 | 13
59 | 14
60 | 15
61 | 16
62 | 17
63 | 18
64 | 19
65 | 20
66 | 21
67 | 22
68 | 23
69 | 24
70 | 25
71 | 26
72 | 27
73 | 28
74 | 29
75 | 30
76 | 31
77 | 32
78 | 33
79 | 34
80 | 35
81 | 36
82 | 37
83 | 38
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | import React, { Component } from 'react';
121 | import './list-view.css';
122 | import randomId from 'uuid/v4';
123 | import ItemView from './ItemView';
124 |
125 | export default class ListView extends React.Component {
126 |
127 | re nderTabs () {
128 | if (this.props.tabs.length < 1) {
129 | return (
130 | <h2 className='empty-list'>No tabs to show :(</h2>
131 | )
132 | }
133 | return this.props.tabs.map (ta b => {
134 | return (
135 | <ItemView
136 | key={randomId()}
137 | tab_url={tab.tab[0].url}
138 | tab_title={tab.tab[0].title}
139 | tab_icon={tab.tab[0].favIconUrl}
140 | deleteTab={this.props.deleteTab}
141 | expiry={tab.expiry}
142 | expirySettings={this.props.expirySettings}
143 | expired={this.props.expired}
144 | ></ItemView>
145 | )
146 | });
147 | }
148 |
149 | re nder () {
150 | return (
151 | <div>
152 | {this.renderTabs()}
153 | </div>
154 | )
155 | }
156 | }
157 |
158 |
159 |
160 |
161 |
165 |
166 |
167 |
174 |
175 |
176 |
177 |
--------------------------------------------------------------------------------
/coverage/lcov-report/popup/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Code coverage report for popup
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 | 8.05%
24 | Statements
25 | 7/87
26 |
27 |
28 | 0%
29 | Branches
30 | 0/38
31 |
32 |
33 | 2.17%
34 | Functions
35 | 1/46
36 |
37 |
38 | 8.97%
39 | Lines
40 | 7/78
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | File
50 |
51 | Statements
52 |
53 | Branches
54 |
55 | Functions
56 |
57 | Lines
58 |
59 |
60 |
61 |
62 | App.js
63 |
64 | 100%
65 | 1/1
66 | 100%
67 | 0/0
68 | 100%
69 | 1/1
70 | 100%
71 | 1/1
72 |
73 |
74 |
75 | Dashboard.js
76 |
77 | 5.13%
78 | 2/39
79 | 0%
80 | 0/16
81 | 0%
82 | 0/20
83 | 5.71%
84 | 2/35
85 |
86 |
87 |
88 | ItemView.js
89 |
90 | 0%
91 | 0/19
92 | 0%
93 | 0/18
94 | 0%
95 | 0/4
96 | 0%
97 | 0/18
98 |
99 |
100 |
101 | ListView.js
102 |
103 | 0%
104 | 0/5
105 | 0%
106 | 0/2
107 | 0%
108 | 0/3
109 | 0%
110 | 0/5
111 |
112 |
113 |
114 | Search.js
115 |
116 | 16.67%
117 | 2/12
118 | 0%
119 | 0/2
120 | 0%
121 | 0/9
122 | 20%
123 | 2/10
124 |
125 |
126 |
127 | Settings.js
128 |
129 | 18.18%
130 | 2/11
131 | 100%
132 | 0/0
133 | 0%
134 | 0/9
135 | 22.22%
136 | 2/9
137 |
138 |
139 |
140 |
141 |
142 |
143 |
147 |
148 |
149 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/coverage/clover.xml:
--------------------------------------------------------------------------------
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 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/coverage/lcov-report/popup/Search.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Code coverage report for popup/Search.js
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 | 16.67%
24 | Statements
25 | 2/12
26 |
27 |
28 | 0%
29 | Branches
30 | 0/2
31 |
32 |
33 | 0%
34 | Functions
35 | 0/9
36 |
37 |
38 | 20%
39 | Lines
40 | 2/10
41 |
42 |
43 |
44 |
45 |
46 | 1
47 | 2
48 | 3
49 | 4
50 | 5
51 | 6
52 | 7
53 | 8
54 | 9
55 | 10
56 | 11
57 | 12
58 | 13
59 | 14
60 | 15
61 | 16
62 | 17
63 | 18
64 | 19
65 | 20
66 | 21
67 | 22
68 | 23
69 | 24
70 | 25
71 | 26
72 | 27
73 | 28
74 | 29
75 | 30
76 | 31
77 | 32
78 | 33
79 | 34
80 | 35
81 | 36
82 | 37
83 | 38
84 | 39
85 | 40
86 | 41
87 | 42
88 | 43
89 | 44
90 | 45
91 | 46
92 | 47
93 | 48
94 | 49
95 | 50
96 | 51
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | 1x
133 |
134 |
135 |
136 |
137 |
138 | 1x
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 | import React, { Component } from 'react';
147 | import { connect } from 'react-redux';
148 | import './search.css';
149 | import {searchBookmark, emptySearch, toggleSearch} from '../background/actions';
150 | import ListView from './ListView';
151 | class Search extends React.Component {
152 |
153 |
154 | findTab = () => {
155 | if (this.props.bookmark.search) {
156 | return (
157 | <ListView className={this.props.animation.toggleSearch}
158 | tabs={this.props.bookmark.searchResult}
159 | action='renderBookmark'
160 | expirySettings={this.props.settings.expireDate}
161 | />
162 | )
163 | }
164 | }
165 |
166 | co mponentDidMount() {
167 | this.props.resetSearch()
168 | }
169 |
170 | // ================== RENDERING
171 | re nder () {
172 | return (
173 | <div>
174 | <input type='text' onChange={(e ) => this.props.searchString(e.target.value)} placeholder='search...' ></input>
175 | {this.findTab()}
176 | </div>
177 | )
178 | }
179 |
180 | }
181 |
182 | const mapStateToProps = (s tate) => ({
183 | bookmark : state.bookmark,
184 | settings : state.settings,
185 | animation: state.animation
186 | });
187 |
188 | const mapDispatchToProps = (d ispatch) => ({
189 | searchString: (t ext) => dispatch(searchBookmark(text)),
190 | resetSearch: () => dispatch(emptySearch()),
191 | displaySearch: (c lassValue) => dispatch(toggleSearch(classValue))
192 |
193 | });
194 |
195 | export default connect(mapStateToProps, mapDispatchToProps)(Search);
196 |
197 |
198 |
199 |
200 |
204 |
205 |
206 |
213 |
214 |
215 |
216 |
--------------------------------------------------------------------------------
/coverage/lcov-report/background/actions.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Code coverage report for background/actions.js
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 | 54.17%
24 | Statements
25 | 13/24
26 |
27 |
28 | 100%
29 | Branches
30 | 0/0
31 |
32 |
33 | 8.33%
34 | Functions
35 | 1/12
36 |
37 |
38 | 100%
39 | Lines
40 | 12/12
41 |
42 |
43 |
44 |
45 |
46 | 1
47 | 2
48 | 3
49 | 4
50 | 5
51 | 6
52 | 7
53 | 8
54 | 9
55 | 10
56 | 11
57 | 12
58 | 13
59 | 14
60 | 15
61 | 16
62 | 17
63 | 18
64 | 19
65 | 20
66 | 21
67 | 22
68 | 23
69 | 24
70 | 25
71 | 26
72 | 27
73 | 28
74 | 29
75 | 30
76 | 31
77 | 32
78 | 33
79 | 34
80 | 35
81 | 36
82 | 37
83 | 38
84 | 39
85 | 40
86 | 41
87 | 42
88 | 43
89 | 44
90 | 45
91 | 46
92 | 47
93 | 48
94 | 49
95 | 50
96 | 51
97 | 52
98 | 53
99 | 54
100 | 55
101 | 56
102 | 57
103 | 58
104 | 59
105 | 60
106 | 61
107 | 62
108 | 63
109 | 64
110 | 65
111 | 66
112 | 67
113 |
114 | 1x
115 |
116 |
117 |
118 |
119 |
120 | 1x
121 |
122 |
123 |
124 | 1x
125 |
126 |
127 |
128 |
129 | 1x
130 |
131 |
132 |
133 |
134 |
135 | 1x
136 |
137 |
138 |
139 |
140 | 1x
141 |
142 |
143 |
144 |
145 | 1x
146 |
147 |
148 |
149 |
150 |
151 | 1x
152 |
153 |
154 |
155 |
156 | 1x
157 |
158 |
159 |
160 |
161 | 1x
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 | 1x
170 |
171 |
172 |
173 |
174 | 1x
175 |
176 |
177 |
178 |
179 | //BOOKMARKS ACTIONS
180 | export const refreshBookmark = (d ata, time) => ({
181 | type: 'REFRESH',
182 | urlList: data,
183 | expiry: time
184 | })
185 |
186 | export const deleteAllBookmark = () => ({
187 | type: 'DELETE-ALL',
188 | })
189 |
190 | export const deleteOneBookmark = (u rl) => ({
191 | type: 'DELETE-ONE',
192 | url: url
193 | })
194 |
195 | export const addBookmark = (url) => ({
196 | type: 'ADD',
197 | urlList: url,
198 | expiry: new Date().getTime()
199 | })
200 |
201 | export const addFromButton = (f lag) => ({
202 | type: 'DELETE-ONE',
203 | addFromButton: flag
204 | })
205 |
206 | export const searchBookmark = (t ext) => ({
207 | type: 'SEARCH',
208 | textSearched: text
209 | })
210 |
211 | export const emptySearch = () => ({
212 | type: 'EMPTY-SEARCH'
213 | })
214 |
215 | //SETTINGS actions
216 |
217 | export const toggleButton = (f lag) => ({
218 | type: 'TOGGLE-BUTTON',
219 | toggleButton: flag
220 | })
221 |
222 | export const expireDate = (d ate) => ({
223 | type: 'UPDATE-DATE',
224 | expireDate: date
225 | })
226 |
227 | export const toggleButtonHistory = (f lag) => ({
228 | type: 'TOGGLE-BUTTON-HISTORY',
229 | toggleButtonHistory: flag
230 | })
231 |
232 |
233 | // ANIMATION ACTIONS
234 |
235 | export const buttonCog = (f lag) => ({
236 | type: 'TOGGLE-COG',
237 | buttonCog: flag
238 | })
239 |
240 | export const toggleSearch = (c lassValue) => ({
241 | type: 'TOGGLE-SEARCH',
242 | toggleSearch: classValue
243 | })
244 |
245 |
246 |
247 |
248 |
252 |
253 |
254 |
261 |
262 |
263 |
264 |
--------------------------------------------------------------------------------
/coverage/lcov-report/popup/ItemView.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Code coverage report for popup/ItemView.js
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 | 0%
24 | Statements
25 | 0/19
26 |
27 |
28 | 0%
29 | Branches
30 | 0/18
31 |
32 |
33 | 0%
34 | Functions
35 | 0/4
36 |
37 |
38 | 0%
39 | Lines
40 | 0/18
41 |
42 |
43 |
44 |
45 |
46 | 1
47 | 2
48 | 3
49 | 4
50 | 5
51 | 6
52 | 7
53 | 8
54 | 9
55 | 10
56 | 11
57 | 12
58 | 13
59 | 14
60 | 15
61 | 16
62 | 17
63 | 18
64 | 19
65 | 20
66 | 21
67 | 22
68 | 23
69 | 24
70 | 25
71 | 26
72 | 27
73 | 28
74 | 29
75 | 30
76 | 31
77 | 32
78 | 33
79 | 34
80 | 35
81 | 36
82 | 37
83 | 38
84 | 39
85 | 40
86 | 41
87 | 42
88 | 43
89 | 44
90 | 45
91 | 46
92 | 47
93 | 48
94 | 49
95 | 50
96 | 51
97 | 52
98 | 53
99 | 54
100 | 55
101 | 56
102 | 57
103 | 58
104 | 59
105 | 60
106 | 61
107 | 62
108 | 63
109 | 64
110 | 65
111 | 66
112 | 67
113 | 68
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 | import React, { Component } from 'react';
181 | import './item-view.css';
182 |
183 | export default class ItemView extends React.Component {
184 |
185 | ch eckDeleteButton() {
186 | if (this.props.deleteTab) {
187 | return (
188 | <div className='col-2'>
189 | <i
190 | className="fa fa-trash-o fa-2x"
191 | onClick={() =>this.props.deleteTab(this.props.tab_url)} >
192 | </i>
193 | </div>
194 | )
195 |
196 | }
197 | }
198 |
199 | ch eckExpireDate () {
200 | if (this.props.expired) return <div className='col-1-2 warning'>Expired</div>
201 | let timeLeft;
202 | const time = new Date (this.props.expirySettings - (Date.now() - this.props.expiry))
203 | const seconds = (time / 1000).toFixed(1);
204 | const minutes = (time / (1000 * 60)).toFixed(1);
205 | const hours = (time / (1000 * 60 * 60)).toFixed(1);
206 | const days = (time / (1000 * 60 * 60 * 24)).toFixed(1);
207 |
208 | if (seconds < 60) {
209 | (Math.floor(seconds) === 1) ? timeLeft = Math.floor(seconds) + " second" : timeLeft = Math.floor(seconds) + " seconds";
210 | } else if (minutes < 60) {
211 | (Math.floor(minutes) === 1) ? timeLeft = Math.floor(minutes) + " minute" : timeLeft = Math.floor(minutes) + " minutes";
212 | } else if (hours < 24) {
213 | (Math.floor(hours) === 1) ? timeLeft = Math.floor(hours) + " hour" : timeLeft = Math.floor(hours) + " hours";
214 | } else {
215 | (Math.floor(days) === 1) ? timeLeft = Math.floor(days) + " day" : timeLeft = Math.floor(days) + " days"
216 | }
217 | return (
218 | <div className='col-1-2 warning'>
219 | Expires in {timeLeft}
220 | </div>
221 | )
222 | }
223 |
224 | re nder () {
225 | return (
226 | <div className='content-container'>
227 | <div className='col-1'>
228 | <div className='row'>
229 | <div className='col-1-1'>
230 | <img className='icon' src={this.props.tab_icon} />
231 | </div>
232 | <div className='col-1-2'>
233 | <a className='title' href={this.props.tab_url} target='_blank'>{this.props.tab_title}</a>
234 | </div>
235 |
236 | </div>
237 | <div className='row'>
238 | <div className='col-1-1'></div>
239 | {this.checkExpireDate()}
240 | </div>
241 | </div>
242 | {this.checkDeleteButton()}
243 | </div>
244 | )
245 | }
246 | }
247 |
248 |
249 |
250 |
251 |
255 |
256 |
257 |
264 |
265 |
266 |
267 |
--------------------------------------------------------------------------------
/coverage/lcov-report/popup/Settings.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Code coverage report for popup/Settings.js
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 | 18.18%
24 | Statements
25 | 2/11
26 |
27 |
28 | 100%
29 | Branches
30 | 0/0
31 |
32 |
33 | 0%
34 | Functions
35 | 0/9
36 |
37 |
38 | 22.22%
39 | Lines
40 | 2/9
41 |
42 |
43 |
44 |
45 |
46 | 1
47 | 2
48 | 3
49 | 4
50 | 5
51 | 6
52 | 7
53 | 8
54 | 9
55 | 10
56 | 11
57 | 12
58 | 13
59 | 14
60 | 15
61 | 16
62 | 17
63 | 18
64 | 19
65 | 20
66 | 21
67 | 22
68 | 23
69 | 24
70 | 25
71 | 26
72 | 27
73 | 28
74 | 29
75 | 30
76 | 31
77 | 32
78 | 33
79 | 34
80 | 35
81 | 36
82 | 37
83 | 38
84 | 39
85 | 40
86 | 41
87 | 42
88 | 43
89 | 44
90 | 45
91 | 46
92 | 47
93 | 48
94 | 49
95 | 50
96 | 51
97 | 52
98 | 53
99 | 54
100 | 55
101 | 56
102 | 57
103 | 58
104 | 59
105 | 60
106 | 61
107 | 62
108 | 63
109 | 64
110 | 65
111 | 66
112 | 67
113 | 68
114 | 69
115 | 70
116 | 71
117 | 72
118 | 73
119 | 74
120 | 75
121 | 76
122 | 77
123 | 78
124 | 79
125 | 80
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 | 1x
193 |
194 |
195 |
196 |
197 | 1x
198 |
199 |
200 |
201 |
202 |
203 |
204 | import { Store } from "react-chrome-redux";
205 | import React, { Component } from "react";
206 | import "./settings.css";
207 | import "./App";
208 | import { Link } from "react-router-dom";
209 | import { connect } from "react-redux";
210 | import {
211 | toggleButton,
212 | toggleButtonHistory,
213 | expireDate
214 | } from "../background/actions";
215 |
216 | class Settings extends React.Component {
217 | // ======================== RENDERING
218 |
219 | re nder() {
220 | return (
221 | <div>
222 | <div className="header">
223 | <h1>Settings</h1>
224 | <Link to={"/pages/popup.html"} style={{ color: "black" }}>
225 | <i class="fa fa-check fa-2x" />
226 | </Link>
227 | </div>
228 |
229 | <form>
230 | <ul>
231 | <li>
232 | <label>Keep bookmarks for</label>
233 | <select
234 | value={this.props.settings.expireDate}
235 | onChange={e => this.props.expire(parseInt(e.target.value))}
236 | >
237 | <option value="3600000">1 hour</option>
238 | <option value="43200000">12 hours</option>
239 | <option value="86400000">1 day</option>
240 | <option value="172800000">2 days</option>
241 | <option value="259200000">3 days</option>
242 | <option value="345600000">4 days</option>
243 | <option value="432000000">5 days</option>
244 | <option value="518400000">6 days</option>
245 | <option value="604800000">1 week</option>
246 | </select>
247 | </li>
248 | <li>
249 | <label>Show icon</label>
250 | <input
251 | type="checkbox"
252 | checked={this.props.settings.button}
253 | onChange={e => this.props.toggle(e.target.checked)}
254 | />
255 | </li>
256 | <li>
257 | <label>Show history</label>
258 | <input
259 | type="checkbox"
260 | checked={this.props.settings.buttonHistory}
261 | onChange={e => this.props.toggleHistory(e.target.checked)}
262 | />
263 | </li>
264 | </ul>
265 | </form>
266 | </div>
267 | );
268 | }
269 | }
270 |
271 | const mapStateToProps = st ate => ({
272 | bookmark: state.bookmark,
273 | settings: state.settings
274 | });
275 |
276 | const mapDispatchToProps = di spatch => ({
277 | toggle: fl ag => dispatch(toggleButton(flag)),
278 | toggleHistory: fl ag => dispatch(toggleButtonHistory(flag)),
279 | expire: da te => dispatch(expireDate(date))
280 | });
281 |
282 | export default connect(mapStateToProps, mapDispatchToProps)(Settings);
283 |
284 |
285 |
286 |
287 |
291 |
292 |
293 |
300 |
301 |
302 |
303 |
--------------------------------------------------------------------------------
/coverage/lcov-report/prettify.js:
--------------------------------------------------------------------------------
1 | window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^
524 |
531 |
532 |