├── src
├── components
│ ├── header
│ │ ├── index.js
│ │ └── style.css
│ ├── input
│ │ ├── index.js
│ │ └── style.css
│ ├── empty
│ │ ├── style.css
│ │ └── index.js
│ ├── url
│ │ ├── style.css
│ │ └── index.js
│ ├── list
│ │ ├── style.css
│ │ └── index.js
│ ├── arrow
│ │ ├── index.js
│ │ └── style.css
│ ├── message
│ │ ├── style.css
│ │ └── index.js
│ ├── material-button
│ │ ├── index.js
│ │ └── style.css
│ ├── icon-button
│ │ ├── index.js
│ │ └── style.css
│ ├── footer
│ │ ├── index.js
│ │ └── style.css
│ ├── sorter
│ │ ├── index.js
│ │ └── style.css
│ ├── github
│ │ └── index.js
│ └── row
│ │ ├── index.js
│ │ └── style.css
├── containers
│ ├── results
│ │ ├── style.css
│ │ ├── index.js
│ │ ├── store.js
│ │ └── getResults.js
│ ├── app
│ │ ├── index.js
│ │ ├── style.css
│ │ ├── store.js
│ │ └── variables.css
│ └── add
│ │ ├── index.js
│ │ ├── store.js
│ │ └── style.css
├── lib
│ ├── constants.js
│ └── misc.js
├── public
│ ├── popup.html
│ ├── manifest.json
│ └── img
│ │ ├── github.svg
│ │ ├── icon.svg
│ │ ├── 32icon_dark.svg
│ │ └── 32icon_light.svg
└── search.js
├── nodemon.json
├── .env-template
├── prettier.config.js
├── screenshots
└── screenshot-1.png
├── postcss.config.js
├── .editorconfig
├── web-ext-config.js
├── .babelrc
├── .eslintrc.js
├── CHANGELOG.md
├── index.js
├── README.md
├── webpack.config.js
├── .gitignore
└── package.json
/src/components/header/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/header/style.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/input/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/empty/style.css:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/input/style.css:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "ignore": ["*.test.js", "src/*", "dist/*"]
3 | }
4 |
--------------------------------------------------------------------------------
/.env-template:
--------------------------------------------------------------------------------
1 | WEB_EXT_API_KEY=""
2 | WEB_EXT_API_SECRET=""
3 | PORT=3000
4 |
--------------------------------------------------------------------------------
/src/containers/results/style.css:
--------------------------------------------------------------------------------
1 | .results {
2 | background: var(--dark-gray);
3 | }
4 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | singleQuote: true,
3 | trailingComma: 'es5',
4 | };
5 |
--------------------------------------------------------------------------------
/screenshots/screenshot-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afk-mario/busca/HEAD/screenshots/screenshot-1.png
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | const autoprefixer = require('autoprefixer');
2 |
3 | module.exports = {
4 | plugins: [autoprefixer],
5 | };
6 |
--------------------------------------------------------------------------------
/src/components/url/style.css:
--------------------------------------------------------------------------------
1 | .url {
2 | display: flex;
3 | padding: var(--half-margin);
4 | width: 100%;
5 | flex-flow: column nowrap;
6 | }
7 |
--------------------------------------------------------------------------------
/src/lib/constants.js:
--------------------------------------------------------------------------------
1 | export const ORDER_OPTIONS = ['VOTES', 'COMMENTS', 'DATE'];
2 | export const DEFAULT_SEARCH_URL = 'https://www.youtube.com/watch?v=QrR_gm6RqCo';
3 |
--------------------------------------------------------------------------------
/src/components/empty/index.js:
--------------------------------------------------------------------------------
1 | import html from 'choo/html';
2 |
3 | import './style.css';
4 |
5 | export default () => html`
6 |
7 |
8 | `;
9 |
--------------------------------------------------------------------------------
/src/components/list/style.css:
--------------------------------------------------------------------------------
1 | .list {
2 | width: 100%;
3 | padding: 0;
4 | margin: 0;
5 | list-style: none;
6 | overflow-x: hidden;
7 | overflow-y: auto;
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/arrow/index.js:
--------------------------------------------------------------------------------
1 | import html from 'choo/html';
2 |
3 | import './style.css';
4 |
5 | export default direction => html`
6 |
7 | `;
8 |
--------------------------------------------------------------------------------
/src/components/message/style.css:
--------------------------------------------------------------------------------
1 | .message {
2 | padding: var(--margin);
3 | }
4 |
5 | .message .wrapper {
6 | padding: var(--margin);
7 | border: 2px solid var(--light-blue);
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/material-button/index.js:
--------------------------------------------------------------------------------
1 | import html from 'choo/html';
2 | import './style.css';
3 |
4 | export default (icon, onclick) => html`
5 |
6 | ${icon}
7 |
8 | `;
9 |
--------------------------------------------------------------------------------
/src/components/message/index.js:
--------------------------------------------------------------------------------
1 | import html from 'choo/html';
2 |
3 | import './style.css';
4 |
5 | export default message => html`
6 |
7 |
8 | ${message}
9 |
10 |
11 | `;
12 |
--------------------------------------------------------------------------------
/src/components/url/index.js:
--------------------------------------------------------------------------------
1 | import html from 'choo/html';
2 |
3 | import './style.css';
4 |
5 | export default url => html`
6 |
7 |
8 |
url:
9 | ${url}
10 |
11 |
12 | `;
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | iroot = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 | end_of_line = lf
10 | # editorconfig-tools is unable to ignore longs strings or urls
11 | max_line_length = null
12 |
--------------------------------------------------------------------------------
/src/public/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | busca
5 |
6 |
8 |
9 |
10 |
11 | Loading ...
12 |
13 |
14 |
--------------------------------------------------------------------------------
/web-ext-config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | verbose: true,
3 | sourceDir: 'dist',
4 | build: {
5 | overwriteDest: true,
6 | },
7 | run: {
8 | firefox: 'firefox-developer-edition',
9 | browserConsole: true,
10 | startUrl: [
11 | 'about:debugging',
12 | 'https://www.youtube.com/watch?v=C4Uc-cztsJo',
13 | ],
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/src/components/material-button/style.css:
--------------------------------------------------------------------------------
1 | .material {
2 | color: var(light-gray);
3 | cursor: pointer;
4 | transition: all 0.2s ease;
5 | margin: 4px 0 0 0;
6 | }
7 |
8 | .material .material-icons {
9 | font-size: 20px;
10 | }
11 |
12 | .material:hover {
13 | color: var(--light-blue);
14 | transform: translate(0, -1px);
15 | box-shadow: var(--box-shadow-2);
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/icon-button/index.js:
--------------------------------------------------------------------------------
1 | import html from 'choo/html';
2 |
3 | import './style.css';
4 |
5 | export default ({ active, icon, value, className, onclick }) => html`
6 |
14 | `;
15 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/react"],
3 | "plugins": [
4 | "@babel/plugin-proposal-class-properties",
5 | "@babel/plugin-syntax-export-default-from",
6 | "transform-export-extensions",
7 | "@babel/plugin-proposal-object-rest-spread"
8 | ],
9 | "compact" : false,
10 | "env": {
11 | "production": {
12 | "plugins": [ "transform-react-remove-prop-types" ]
13 |
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/search.js:
--------------------------------------------------------------------------------
1 | import choo from 'choo';
2 | import devtools from 'choo-devtools';
3 |
4 | import App from './containers/app';
5 | import Store from './containers/app/store';
6 | import ResultsStore from './containers/results/store';
7 |
8 | const app = choo();
9 |
10 | if (process.env.NODE_ENV !== 'production') {
11 | app.use(devtools());
12 | }
13 | app.use(ResultsStore);
14 | app.use(Store);
15 | app.route('/*', App);
16 | app.mount('body');
17 |
--------------------------------------------------------------------------------
/src/components/list/index.js:
--------------------------------------------------------------------------------
1 | import html from 'choo/html';
2 |
3 | import Row from '../row';
4 | import Empty from '../empty';
5 | import './style.css';
6 |
7 | export default items => {
8 | const empty = Empty();
9 | const rows = items.map(item => Row(item));
10 | const content = items.length > 0 ? rows : empty;
11 |
12 | return html`
13 |
18 | `;
19 | };
20 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['airbnb', 'prettier'],
3 | rules: {
4 | 'no-console': 0,
5 | },
6 | globals: {
7 | browser: true,
8 | document: true,
9 | localStorage: true,
10 | chrome: true,
11 | fetch: true,
12 | Request: true,
13 | Headers: true,
14 | location: true,
15 | },
16 | settings: {
17 | 'import/resolver': {
18 | webpack: {
19 | config: 'webpack.config.js',
20 | },
21 | },
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/src/components/footer/index.js:
--------------------------------------------------------------------------------
1 | import html from 'choo/html';
2 |
3 | import github from '~components/github';
4 | import './style.css';
5 |
6 | export default () => html`
7 |
20 | `;
21 |
--------------------------------------------------------------------------------
/src/components/icon-button/style.css:
--------------------------------------------------------------------------------
1 | .icon-button {
2 | color: currentcolor;
3 | transition: color 0.2s ease;
4 | cursor: pointer;
5 | padding: 5px;
6 | padding-left: var(--margin);
7 | padding-right: var(--margin);
8 | }
9 |
10 | .icon-button.-active.votes,
11 | .icon-button.votes:hover {
12 | color: var(--red);
13 | }
14 |
15 | .icon-button.-active.comments,
16 | .icon-button.comments:hover {
17 | color: var(--yellow);
18 | }
19 |
20 | .icon-button.-active.date,
21 | .icon-button.date:hover {
22 | color: var(--light-blue);
23 | }
24 |
--------------------------------------------------------------------------------
/src/containers/app/index.js:
--------------------------------------------------------------------------------
1 | import html from 'choo/html';
2 |
3 | import Results from '~containers/results';
4 |
5 | import Footer from '~components/footer';
6 | import Message from '~components/message';
7 |
8 | import './variables.css';
9 | import './style.css';
10 |
11 | export default function app(state, emit) {
12 | const { url, message } = state;
13 |
14 | const messageBlank = typeof message === 'undefined' || !message;
15 | const content = messageBlank ? Results(state, emit) : Message(message);
16 |
17 | return html`
18 |
19 |
20 | ${content}
21 |
22 | ${Footer(url)}
23 |
24 | `;
25 | }
26 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [Unreleased]
8 |
9 | ## [1.3.0] - 2019-02-19
10 | ### Added
11 | - Buttons for changing the order of the search results
12 | - Add web-ext config file for easier build toolchain
13 | - Add express config for easier debugging
14 |
15 | ## [1.2.1] - 2019-02-18
16 | ### Added
17 | - web-ext as a dependency
18 | - changelog file
19 |
20 | ### Changed
21 | - Webpack now cleans dist dir
22 | - upgrade dependencies
23 | - simplier build and start command
24 |
--------------------------------------------------------------------------------
/src/containers/add/index.js:
--------------------------------------------------------------------------------
1 | import html from 'choo/html';
2 |
3 | import './style.css';
4 |
5 | export default ({ url, tags, updateUrl, updateTags, submit }) => html`
6 |
27 | `;
28 |
--------------------------------------------------------------------------------
/src/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "applications": {
3 | "gecko": {
4 | "id": "busca@ellugar.co",
5 | "strict_min_version": "42.0"
6 | }
7 | },
8 | "manifest_version": 2,
9 | "name": "busca.afk",
10 | "description": "busca.afk extension",
11 | "author": "Mario Carballo Zama",
12 | "version": "1.3.0",
13 | "icons": {
14 | "16": "img/icon.svg",
15 | "19": "img/icon.svg",
16 | "38": "img/icon.svg",
17 | "48": "img/icon.svg",
18 | "128": "img/icon.svg",
19 | "256": "img/icon.svg",
20 | "512": "img/icon.svg"
21 | },
22 | "browser_action": {
23 | "default_icon": "img/icon.svg",
24 | "default_popup": "popup.html"
25 | },
26 | "permissions": ["https://www.reddit.com/", "webRequest", "tabs", "storage"]
27 | }
28 |
--------------------------------------------------------------------------------
/src/public/img/github.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/arrow/style.css:
--------------------------------------------------------------------------------
1 | .arrow.up {
2 | width: 0;
3 | height: 0;
4 | border-left: 10px solid transparent;
5 | border-right: 10px solid transparent;
6 |
7 | border-bottom: 10px solid white;
8 | }
9 |
10 | .arrow.down {
11 | width: 0;
12 | height: 0;
13 | border-left: 10px solid transparent;
14 | border-right: 10px solid transparent;
15 |
16 | border-top: 10px solid white;
17 | }
18 |
19 | .arrow.right {
20 | width: 0;
21 | height: 0;
22 | border-top: 10px solid transparent;
23 | border-bottom: 10px solid transparent;
24 |
25 | border-left: 10px solid white;
26 | }
27 |
28 | .arrow.left {
29 | width: 0;
30 | height: 0;
31 | border-top: 10px solid transparent;
32 | border-bottom: 10px solid transparent;
33 |
34 | border-right: 10px solid white;
35 | }
36 |
--------------------------------------------------------------------------------
/src/containers/app/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | font-family: var(--font);
4 | scrollbar-color: var(--medium-gray) var(--black);
5 | scrollbar-width: thin;
6 | }
7 |
8 | html,
9 | body {
10 | margin: 0;
11 | padding: 0;
12 | min-width: var(--popup-width);
13 | background: var(--dark-gray);
14 | color: var(--light-gray);
15 | overflow-x: hidden;
16 |
17 | max-width: var(--popup-width);
18 | max-height: var(--popup-height);
19 | }
20 |
21 | h1 {
22 | font-size: 1.5em;
23 | font-weight: 100;
24 | margin: 0;
25 | padding: 0;
26 | }
27 |
28 | h2 {
29 | font-size: 1em;
30 | font-weight: 100;
31 | margin: 0;
32 | padding: 0;
33 | }
34 |
35 | .wrapper {
36 | margin: auto;
37 | width: var(--popup-width);
38 | width: 100%;
39 | }
40 |
41 | main {
42 | padding-bottom: var(--footer-height);
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/footer/style.css:
--------------------------------------------------------------------------------
1 | #footer {
2 | color: var(--white);
3 | position: fixed;
4 | bottom: 0;
5 | left: 0;
6 | right: 0;
7 |
8 | height: var(--footer-height);
9 | background: var(--dark-gray);
10 | border-top: 2px solid var(--medium-gray);
11 | padding: var(--half-margin);
12 |
13 | max-width: var(--popup-width);
14 |
15 | display: flex;
16 | flex-flow: row;
17 | align-items: center;
18 | justify-content: center;
19 | }
20 |
21 | #footer .wrapper {
22 | display: flex;
23 | flex-flow: row nowrap;
24 | align-items: center;
25 | justify-content: space-between;
26 | }
27 |
28 | #footer a {
29 | color: currentcolor;
30 | transition: color 0.2s ease;
31 | width: 20px;
32 | }
33 |
34 | #footer a:hover {
35 | color: var(--light-blue);
36 | }
37 |
38 | #footer svg {
39 | width: 100%;
40 | height: auto;
41 | display: block;
42 | }
43 |
--------------------------------------------------------------------------------
/src/containers/app/store.js:
--------------------------------------------------------------------------------
1 | export default (
2 | state = {
3 | link: '',
4 | token: '',
5 | },
6 | emitter
7 | ) => {
8 | const mState = state;
9 | mState.url = '';
10 | mState.token = '';
11 |
12 | emitter.on('DOMContentLoaded', () => {
13 | // restoreOptions(emitter);
14 |
15 | emitter.on('url:update', url => {
16 | mState.url = url;
17 | emitter.emit('render');
18 | });
19 |
20 | emitter.on('token:update', token => {
21 | mState.token = token;
22 |
23 | // if (typeof token === 'undefined' || !token) {
24 | // emitter.emit('message:update', 'no token');
25 | // }
26 |
27 | emitter.emit('render');
28 | });
29 |
30 | emitter.on('message:update', message => {
31 | mState.message = message;
32 | emitter.emit('render');
33 | });
34 |
35 | emitter.emit('message:update', 'loading ...');
36 | });
37 | };
38 |
--------------------------------------------------------------------------------
/src/components/sorter/index.js:
--------------------------------------------------------------------------------
1 | import html from 'choo/html';
2 | import IconButton from '~components/icon-button';
3 |
4 | import './style.css';
5 |
6 | const arrow = up => {
7 | if (up)
8 | return html`
9 | arrow_drop_down
10 | `;
11 | return html`
12 | arrow_drop_up
13 | `;
14 | };
15 |
16 | export default ({ order, sorters, onclick }) => {
17 | const buttons = sorters.map(item => {
18 | const active = item.value === order;
19 | return html`
20 |
21 | ${IconButton({
22 | ...item,
23 | active,
24 | onclick,
25 | })}
26 |
27 | `;
28 | });
29 |
30 | return html`
31 |
32 |
sort
33 |
${buttons}
34 |
35 | `;
36 | };
37 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const dotenv = require('dotenv');
3 | const express = require('express');
4 | const webpack = require('webpack');
5 | const middleware = require('webpack-dev-middleware');
6 | const webpackConfig = require('./webpack.config.js');
7 |
8 | const compiler = webpack({ ...webpackConfig, mode: 'development' });
9 |
10 | dotenv.config();
11 |
12 | const DEV = process.env.NODE_ENV !== 'production';
13 | const PORT = process.env.PORT || 3000;
14 | const app = express();
15 |
16 | app.use(
17 | middleware(compiler, {
18 | noInfo: true,
19 | writeToDisk: true,
20 | })
21 | );
22 |
23 | app.get('*', (req, res) => {
24 | res.sendFile(path.join(__dirname, 'dist', 'popup.html'));
25 | });
26 |
27 | app.listen(PORT, err => {
28 | const info = `==> Listening on port ${PORT} Open up http://127.0.0.1:${PORT} dev:${DEV}`;
29 | if (err) {
30 | console.error(err);
31 | }
32 | console.info(info);
33 | });
34 |
--------------------------------------------------------------------------------
/src/components/github/index.js:
--------------------------------------------------------------------------------
1 | import html from 'choo/html';
2 |
3 | export default () => html`
4 |
16 | `;
17 |
--------------------------------------------------------------------------------
/src/components/sorter/style.css:
--------------------------------------------------------------------------------
1 | .sort-list {
2 | display: flex;
3 | flex-flow: row;
4 | align-items: center;
5 | justify-content: space-between;
6 | border-bottom: 2px solid var(--medium-gray);
7 | }
8 |
9 | .sort-title {
10 | width: var(--side-width);
11 |
12 | display: flex;
13 | flex: 0 0 auto;
14 | flex-flow: column;
15 | align-items: center;
16 | }
17 |
18 | .sort-buttons {
19 | display: flex;
20 | flex: 1 0 auto;
21 | flex-flow: row;
22 | align-items: center;
23 | justify-content: space-between;
24 | color: white;
25 | border-left: 2px solid var(--medium-gray);
26 | }
27 |
28 | .sort-buttons button {
29 | background: transparent;
30 | border-radius: 0;
31 | border: none;
32 | }
33 |
34 | .sort-buttons .sort-button {
35 | flex: 1 0 auto;
36 | display: flex;
37 | flex-flow: row;
38 | align-items: center;
39 | justify-content: center;
40 | }
41 |
42 | .sort-buttons .sort-button:nth-child(2) {
43 | border-right: 2px solid var(--medium-gray);
44 | border-left: 2px solid var(--medium-gray);
45 | }
46 |
--------------------------------------------------------------------------------
/src/containers/app/variables.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Fira+Mono:400,500,700');
2 |
3 | :root {
4 | --font: 'Fira Mono', monospace;
5 |
6 | /* sizes */
7 | --border-division: 2px;
8 | --popup-width: 450px;
9 | --popup-height: 500px;
10 | --side-width: 70px;
11 | --footer-height: 50px;
12 |
13 | /* colors */
14 |
15 | --white: #ffffff;
16 | --black: #000000;
17 | --dark-gray: #0c0c0d;
18 | --medium-gray: #323234;
19 | --light-gray: #b2b2b2;
20 | --light-gray: #f2f2f2;
21 |
22 | --dark-blue: #185869;
23 | --light-blue: #98d1cf;
24 | --blue: #0080ff;
25 |
26 | --red: #c33126;
27 | --green: #2ab38d;
28 | --yellow: #f1b731;
29 | --magenta: #7a7e9d;
30 |
31 | /* margins */
32 |
33 | --margin: 20px;
34 | --half-margin: calc(var(--margin) * 0.5);
35 | --double-margin: calc(var(--margin) * 2);
36 |
37 | /* shadows */
38 | --text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2);
39 | --box-shadow: 0 2px 4px rgba(0, 0, 0, 0.18);
40 | --box-shadow-2: 0 7px 14px rgba(50, 50, 93, 0.1),
41 | 0 3px 6px rgba(0, 0, 0, 0.08);
42 | }
43 |
--------------------------------------------------------------------------------
/src/containers/results/index.js:
--------------------------------------------------------------------------------
1 | import html from 'choo/html';
2 |
3 | import List from '~components/list';
4 | import Sorter from '~components/sorter';
5 | import { ORDER_OPTIONS } from '~lib/constants';
6 |
7 | import { getSortedResults } from '~lib/misc';
8 |
9 | import './style.css';
10 |
11 | const sortbuttons = [
12 | {
13 | className: 'votes',
14 | value: 'VOTES',
15 | icon: 'unfold_more',
16 | },
17 | {
18 | className: 'comments',
19 | value: 'COMMENTS',
20 | icon: 'mode_comment',
21 | },
22 | {
23 | className: 'date',
24 | value: 'DATE',
25 | icon: 'date_range',
26 | },
27 | ];
28 |
29 | export default function ResultsList(state, emit) {
30 | const { results = [], order = ORDER_OPTIONS[0] } = state;
31 | const sorted = getSortedResults(order, results);
32 | const onclick = value => {
33 | emit('order:set', value);
34 | };
35 |
36 | return html`
37 |
38 | ${Sorter({
39 | order,
40 | sorters: sortbuttons,
41 | onclick,
42 | })}
43 | ${List(sorted)}
44 |
45 | `;
46 | }
47 |
--------------------------------------------------------------------------------
/src/containers/add/store.js:
--------------------------------------------------------------------------------
1 | import { getCurrentTabUrl } from '../../lib/misc';
2 |
3 | export default (
4 | state = {
5 | link: '',
6 | tags: '',
7 | token: '',
8 | },
9 | emitter
10 | ) => {
11 | const mState = state;
12 | mState.url = '';
13 | mState.tags = '';
14 | mState.token = '';
15 |
16 | emitter.on('DOMContentLoaded', () => {
17 | getCurrentTabUrl(url => {
18 | mState.url = url;
19 | emitter.emit('render');
20 | });
21 |
22 | // restoreOptions(emitter);
23 |
24 | emitter.on('url:update', url => {
25 | mState.url = url;
26 | emitter.emit('render');
27 | });
28 |
29 | emitter.on('tags:update', tags => {
30 | mState.tags = tags;
31 | emitter.emit('render');
32 | });
33 |
34 | emitter.on('token:update', token => {
35 | mState.token = token;
36 |
37 | if (typeof token === 'undefined' || !token) {
38 | emitter.emit('message:update', 'no token');
39 | }
40 |
41 | emitter.emit('render');
42 | });
43 |
44 | emitter.on('message:update', message => {
45 | mState.message = message;
46 | emitter.emit('render');
47 | });
48 | });
49 | };
50 |
--------------------------------------------------------------------------------
/src/public/img/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/src/public/img/32icon_dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # busca
2 |
3 | 
4 |
5 | A small web-extension to search the current tab on reddit.
6 |
7 | # Issues
8 |
9 | If the icon doesn't match the theme color make sure you have enable `svg.context-properties.content.enabled` on `about:config`
10 |
11 | Inspired by [Reddit Submission Finder](https://addons.mozilla.org/en-US/firefox/addon/reddit-submission-finder/) , [Socialite](https://addons.mozilla.org/en-US/firefox/addon/socialite/) and [Reddit Check](https://github.com/hsbakshi/reddit-check)
12 |
13 | # Screenshot
14 |
15 | 
16 |
17 | # Running and building from source
18 |
19 | Currently using webpack and web-ext to build the addon, if you want to try it yourself follow this steps:
20 |
21 | To install the project dependencies run:
22 |
23 | `yarn install` or `npm install`
24 |
25 | To start webpack on dev mode run:
26 |
27 | `yarn start` or `npm run start`
28 |
29 | To run the addon on firefox run:
30 |
31 | `web-ext run -s dist --firefox=PATH_TO_YOUR_FIREFOX_BIN --pre-install`
32 |
33 | To build the production webpack bundle run:
34 |
35 | `yarn build:prod` or `npm run build:prod`
36 |
37 | To build the addon zip run:
38 |
39 | `yarn build:fox` or `npm run build:fox`
40 |
--------------------------------------------------------------------------------
/src/containers/results/store.js:
--------------------------------------------------------------------------------
1 | import { getCurrentTabUrl, handleErrors, removeDuplicatesBy } from '~lib/misc';
2 |
3 | import getAllSubmissions from '~containers/results/getResults';
4 |
5 | import { ORDER_OPTIONS } from '~lib/constants';
6 |
7 | const store = (
8 | state = {
9 | results: [],
10 | order: ORDER_OPTIONS[0],
11 | },
12 | emitter
13 | ) => {
14 | const mState = state;
15 | mState.results = [];
16 |
17 | emitter.on('DOMContentLoaded', () => {
18 | emitter.on('results:got', results => {
19 | mState.results = results;
20 | emitter.emit('message:update', results.length > 0 ? '' : 'nothing found');
21 | emitter.emit('render');
22 | });
23 |
24 | emitter.on('order:set', order => {
25 | mState.order = order;
26 | emitter.emit('render');
27 | });
28 |
29 | getCurrentTabUrl(url => {
30 | mState.url = url;
31 | // return;
32 | fetch('https://www.reddit.com/api/me.json')
33 | .then(handleErrors)
34 | .then(response => response.json())
35 | .then(() => getAllSubmissions(url))
36 | .then(results => {
37 | const filtered = removeDuplicatesBy(x => x.fullname, results);
38 | emitter.emit('results:got', filtered);
39 | })
40 | .catch(error => console.error(error));
41 |
42 | emitter.emit('render');
43 | });
44 | });
45 | };
46 |
47 | export default store;
48 |
--------------------------------------------------------------------------------
/src/components/row/index.js:
--------------------------------------------------------------------------------
1 | import html from 'choo/html';
2 |
3 | import Arrow from '../arrow';
4 | import './style.css';
5 |
6 | const dateFormat = {
7 | year: '2-digit',
8 | month: '2-digit',
9 | day: '2-digit',
10 | };
11 |
12 | export default ({
13 | link,
14 | fullname,
15 | title,
16 | score,
17 | age,
18 | date,
19 | comments,
20 | subreddit,
21 | likes,
22 | user,
23 | }) => html`
24 |
25 |
26 | ${Arrow('up')}
27 | ${score}
28 | ${Arrow('down')}
29 |
30 |
36 |
39 |
40 |
44 |
45 |
46 | date_range
47 |
48 | ${date.toLocaleDateString(undefined, dateFormat)}
49 |
50 |
51 | r/${subreddit}
52 | u/${user}
53 |
54 |
55 | ${age}
56 | ${likes}
57 | ${link}
58 |
59 | `;
60 |
--------------------------------------------------------------------------------
/src/public/img/32icon_light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const CleanWebpackPlugin = require('clean-webpack-plugin');
3 | const CopyWebpackPlugin = require('copy-webpack-plugin');
4 |
5 | const output = path.resolve(__dirname, 'dist/');
6 |
7 | module.exports = {
8 | devtool: 'cheap-module-source-map',
9 | entry: { search: './src/search.js' },
10 | output: {
11 | filename: '[name].js',
12 | path: output,
13 | sourceMapFilename: '[name].map',
14 | },
15 | module: {
16 | rules: [
17 | {
18 | test: /\.css$/,
19 | exclude: /node_modules/,
20 | use: [
21 | {
22 | loader: 'style-loader',
23 | },
24 | {
25 | loader: 'css-loader',
26 | options: {
27 | importLoaders: 1,
28 | },
29 | },
30 | {
31 | loader: 'postcss-loader',
32 | },
33 | ],
34 | },
35 | {
36 | test: /\.js$/,
37 | exclude: /(node_modules|bower_components)/,
38 | use: {
39 | loader: 'babel-loader',
40 | },
41 | },
42 | ],
43 | },
44 |
45 | plugins: [
46 | new CleanWebpackPlugin('dist'),
47 | new CopyWebpackPlugin([
48 | {
49 | from: path.resolve(__dirname, './src/public'),
50 | to: path.resolve(__dirname, 'dist'),
51 | },
52 | ]),
53 | ],
54 |
55 | resolve: {
56 | modules: ['node_modules'],
57 | alias: {
58 | '~lib': path.resolve('./src/lib/'),
59 | '~components': path.resolve('./src/components'),
60 | '~containers': path.resolve('./src/containers'),
61 | '~public': path.resolve('./src/public'),
62 | },
63 | },
64 | };
65 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/vim,osx,linux
3 |
4 | ### Vim ###
5 | # swap
6 | [._]*.s[a-v][a-z]
7 | [._]*.sw[a-p]
8 | [._]s[a-v][a-z]
9 | [._]sw[a-p]
10 | # session
11 | Session.vim
12 | # temporary
13 | .netrwhist
14 | *~
15 | # auto-generated tag files
16 | tags
17 |
18 |
19 | ### OSX ###
20 | *.DS_Store
21 | .AppleDouble
22 | .LSOverride
23 |
24 | # Icon must end with two \r
25 | Icon
26 | # Thumbnails
27 | ._*
28 | # Files that might appear in the root of a volume
29 | .DocumentRevisions-V100
30 | .fseventsd
31 | .Spotlight-V100
32 | .TemporaryItems
33 | .Trashes
34 | .VolumeIcon.icns
35 | .com.apple.timemachine.donotpresent
36 | # Directories potentially created on remote AFP share
37 | .AppleDB
38 | .AppleDesktop
39 | Network Trash Folder
40 | Temporary Items
41 | .apdisk
42 |
43 |
44 | ### Linux ###
45 |
46 | # temporary files which can be created if a process still has a handle open of a deleted file
47 | .fuse_hidden*
48 |
49 | # KDE directory preferences
50 | .directory
51 |
52 | # Linux trash folder which might appear on any partition or disk
53 | .Trash-*
54 |
55 | # .nfs files are created when an open file is removed but is still being accessed
56 | .nfs*
57 |
58 | ### Node ###
59 | # Logs
60 | logs
61 | *.log
62 | npm-debug.log*
63 |
64 | # Runtime data
65 | pids
66 | *.pid
67 | *.seed
68 | *.pid.lock
69 |
70 | # Directory for instrumented libs generated by jscoverage/JSCover
71 | lib-cov
72 |
73 | # Coverage directory used by tools like istanbul
74 | coverage
75 |
76 | # nyc test coverage
77 | .nyc_output
78 |
79 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
80 | .grunt
81 |
82 | # node-waf configuration
83 | .lock-wscript
84 |
85 | # Compiled binary addons (http://nodejs.org/api/addons.html)
86 | build/Release
87 |
88 | # Dependency directories
89 | node_modules
90 | jspm_packages
91 |
92 | # Optional npm cache directory
93 | .npm
94 |
95 | # Optional eslint cache
96 | .eslintcache
97 |
98 | # Optional REPL history
99 | .node_repl_history
100 |
101 | # Output of 'npm pack'
102 | *.tgz
103 |
104 | # Yarn Integrity file
105 | .yarn-integrityA
106 |
107 | .web-ext-artifacts
108 | key.pem
109 |
110 | .env*
111 | !.env-template
112 |
113 | web-ext-artifacts
114 | dist
115 | .tern*
116 |
--------------------------------------------------------------------------------
/src/containers/add/style.css:
--------------------------------------------------------------------------------
1 | .submit {
2 | background: var(--dark-gray);
3 | }
4 |
5 | form,
6 | .form {
7 | display: flex;
8 | padding: var(--half-margin);
9 | width: 100%;
10 | flex-flow: column nowrap;
11 | }
12 |
13 | input {
14 | width: 100%;
15 | height: auto;
16 | padding: var(--half-margin) 0 2px 0;
17 | font-size: 1em;
18 | background: transparent;
19 | border: none;
20 | box-shadow: none;
21 | color: var(--light-gray);
22 | border-bottom: 2px solid var(--medium-gray);
23 | transition: all 0.2s ease;
24 | color: var(--light-blue);
25 | }
26 |
27 | input[type='number']::-webkit-outer-spin-button,
28 | input[type='number']::-webkit-inner-spin-button {
29 | -webkit-appearance: none;
30 | margin: 0;
31 | }
32 |
33 | input[type='number'] {
34 | -moz-appearance: textfield;
35 | text-align: right;
36 | }
37 |
38 | input[type='color'] {
39 | height: 50px;
40 | border: none;
41 | }
42 |
43 | input:focus {
44 | /* color: var(--light-blue); */
45 | color: var(--white);
46 | border-bottom-color: var(--light-blue);
47 | }
48 |
49 | form > *:not(:first-child) {
50 | margin: var(--margin) 0 0 0;
51 | }
52 |
53 | label {
54 | margin: 0 0 var(--half-margin) 0;
55 | display: block;
56 | color: var(--dark-blue);
57 | }
58 |
59 | button,
60 | .form .button,
61 | form .button {
62 | padding: var(--half-margin);
63 | }
64 |
65 | .fieldset {
66 | border: none;
67 | padding: 0;
68 | }
69 |
70 | .button {
71 | border: none;
72 | margin: var(--margin) 0 0 0;
73 | font-size: 1em;
74 | font-weight: 100;
75 | letter-spacing: 0.2em;
76 | cursor: pointer;
77 | display: flex;
78 | justify-content: center;
79 | align-items: center;
80 | text-align: center;
81 | text-decoration: none;
82 | transition: all 0.2s ease;
83 | text-shadow: none;
84 | cursor: pointer;
85 |
86 | color: var(--light-blue);
87 | border: 2px solid var(--medium-gray);
88 | text-shadow: var(--text-shadow);
89 | background: transparent;
90 | }
91 |
92 | .button:focus {
93 | outline: none;
94 | box-shadow: none;
95 | }
96 |
97 | .button:hover {
98 | transform: translate(0, -1px);
99 | box-shadow: var(--box-shadow-2);
100 |
101 | color: var(--white);
102 | border-color: var(--light-blue);
103 | }
104 |
105 | .button .marterial {
106 | opacity: 0.8;
107 | }
108 |
--------------------------------------------------------------------------------
/src/components/row/style.css:
--------------------------------------------------------------------------------
1 | .row {
2 | display: flex;
3 | flex-flow: row nowrap;
4 | border-bottom: 2px solid var(--medium-gray);
5 | font-size: 0.9em;
6 | align-items: center;
7 | }
8 |
9 | .row:last-child {
10 | border-bottom: 0;
11 | }
12 |
13 | .row .link,
14 | .row .age,
15 | .row .likes {
16 | display: none;
17 | }
18 |
19 | .row .-w-icon {
20 | display: flex;
21 | flex-flow: row;
22 | align-items: center;
23 | }
24 |
25 | .row .-w-icon .material-icons {
26 | font-size: 15px;
27 | margin: 0 5px 0 0;
28 | }
29 |
30 | .row .comments {
31 | color: var(--yellow);
32 | }
33 |
34 | .row .date {
35 | color: var(--light-blue);
36 | }
37 | .row .date span {
38 | padding-top: 3px;
39 | }
40 |
41 | .row .score {
42 | margin: calc(var(--half-margin) / 2) 0 calc(var(--half-margin) / 2) 0;
43 | }
44 | .row .subreddit {
45 | color: var(--magenta);
46 | display: block;
47 | }
48 | .row .title {
49 | width: 100%;
50 | margin: 0 0 var(--half-margin) 0;
51 | display: block;
52 | }
53 |
54 | .row .user {
55 | color: var(--dark-blue);
56 | }
57 |
58 | .row .score-container {
59 | width: var(--side-width);
60 | flex: 0 0 auto;
61 | height: 100%;
62 | display: flex;
63 | flex-flow: column;
64 | align-items: center;
65 | }
66 |
67 | .row .info-container {
68 | width: 100%;
69 | padding: var(--half-margin);
70 | display: flex;
71 | color: var(--white);
72 | text-decoration: none;
73 | font-size: 1em;
74 | flex-flow: column;
75 | align-items: flex-start;
76 | justify-content: space-between;
77 | padding-left: var(--half-margin);
78 | border-left: 2px solid var(--medium-gray);
79 | }
80 |
81 | .row .main-info-container {
82 | display: flex;
83 | width: 100%;
84 | flex-flow: row nowrap;
85 | }
86 |
87 | .row .additional-info-container {
88 | display: flex;
89 | flex-flow: row wrap;
90 | font-size: 0.9em;
91 | width: 100%;
92 | align-items: center;
93 | justify-content: flex-start;
94 | line-height: 1.6em;
95 | font-size: 0.8em;
96 | }
97 |
98 | .row .additional-info-container > *:not(:last-child) {
99 | margin-right: var(--margin);
100 | }
101 |
102 | .row .additional-info-container:last-child {
103 | margin-right: 0;
104 | }
105 |
106 | .row .arrow.up {
107 | border-bottom-color: var(--medium-gray);
108 | }
109 |
110 | .row .arrow.down {
111 | border-top-color: var(--medium-gray);
112 | }
113 |
--------------------------------------------------------------------------------
/src/lib/misc.js:
--------------------------------------------------------------------------------
1 | import { ORDER_OPTIONS, DEFAULT_SEARCH_URL } from '~lib/constants';
2 |
3 | export function restoreOptions(emitter) {
4 | function setCurrentChoice(result) {
5 | const { token = '' } = result;
6 | emitter.emit('token:update', token);
7 | }
8 |
9 | function onError(error) {
10 | console.error(`Error: ${error}`);
11 | }
12 |
13 | const getting = browser.storage.local.get('token');
14 | getting.then(setCurrentChoice, onError);
15 | }
16 |
17 | export function sortByScore(a, b) {
18 | const sortA = a ? parseInt(a.score, 10) : 0;
19 | const sortB = b ? parseInt(b.score, 10) : 0;
20 | if (sortA < sortB) return 1;
21 | if (sortA > sortB) return -1;
22 | return 0;
23 | }
24 |
25 | export function sortByComments(a, b) {
26 | const sortA = a ? parseInt(a.comments, 10) : 0;
27 | const sortB = b ? parseInt(b.comments, 10) : 0;
28 | if (sortA < sortB) return 1;
29 | if (sortA > sortB) return -1;
30 | return 0;
31 | }
32 |
33 | export function sortByDate(a, b) {
34 | const sortA = a ? parseInt(a.age, 10) : 0;
35 | const sortB = b ? parseInt(b.age, 10) : 0;
36 | if (sortA < sortB) return 1;
37 | if (sortA > sortB) return -1;
38 | return 0;
39 | }
40 |
41 | export function getSortedResults(order, results) {
42 | switch (order) {
43 | case ORDER_OPTIONS[0]:
44 | return results.sort(sortByScore);
45 | case ORDER_OPTIONS[1]:
46 | return results.sort(sortByComments);
47 | case ORDER_OPTIONS[2]:
48 | return results.sort(sortByDate);
49 | default:
50 | return results.sort(sortByScore);
51 | }
52 | }
53 |
54 | export const flatten = list =>
55 | list.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
56 |
57 | export function handleErrors(response) {
58 | if (!response.ok) {
59 | throw Error(response.statusText);
60 | }
61 | return response;
62 | }
63 |
64 | export function parseTags(_tags) {
65 | const tags = _tags.split(',');
66 | tags.push('fromBrowser');
67 | return tags;
68 | }
69 |
70 | export function getCurrentTabUrl(callback) {
71 | // https://developer.chrome.com/extensions/tabs#method-query
72 | const queryInfo = {
73 | active: true,
74 | currentWindow: true,
75 | };
76 |
77 | if (!chrome.tabs) callback(DEFAULT_SEARCH_URL);
78 | else
79 | chrome.tabs.query(queryInfo, tabs => {
80 | const tab = tabs[0];
81 | const { url } = tab;
82 | // console.assert(typeof url == 'string', 'tab.url should be a string');
83 | callback(url);
84 | });
85 | }
86 |
87 | export function removeDuplicatesBy(keyFn, array) {
88 | const mySet = new Set();
89 | return array.filter(x => {
90 | const key = keyFn(x);
91 | const isNew = !mySet.has(key);
92 | if (isNew) mySet.add(key);
93 | return isNew;
94 | });
95 | }
96 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "busca.afk",
3 | "version": "1.3.0",
4 | "description": "busca.afk extension to search links on reddit or more",
5 | "main": "js/script.js",
6 | "scripts": {
7 | "serve": "nodemon index.js",
8 | "start": "web-ext run",
9 | "start:macos": "web-ext run -p default --firefox=/Applications/FirefoxDeveloperEdition.app/Contents/MacOS/firefox-bin",
10 | "lint": "eslint src/**/*.js",
11 | "lint:web": "web-ext lint",
12 | "build": "webpack --watch --progress",
13 | "build:chrome": "crx pack dist -o build/ligo.crx",
14 | "build:fox": "webpack -p && web-ext build",
15 | "sign:fox": "env-cmd ./.env web-ext sign",
16 | "build:prod": "webpack -p",
17 | "precommit": "lint-staged"
18 | },
19 | "repository": "https://github.com/afk-mcz/reddit-submission-finder",
20 | "keywords": [
21 | "extension",
22 | "arlefreak",
23 | "afk",
24 | "ligo",
25 | "link"
26 | ],
27 | "author": "AFK",
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/afk-mcz/busca/issues"
31 | },
32 | "homepage": "https://github.com/afk-mcz/busca",
33 | "lint-staged": {
34 | "src/**/*.{js,json,css}": [
35 | "prettier --single-quote --trailing-comma es5 --write",
36 | "git add"
37 | ]
38 | },
39 | "devDependencies": {
40 | "crx": "^4.0.1",
41 | "eslint": "^5.14.1",
42 | "eslint-config-airbnb": "^17.1.0",
43 | "eslint-import-resolver-webpack": "^0.11.0",
44 | "eslint-plugin-import": "^2.2.0",
45 | "eslint-plugin-jsx-a11y": "^6.0.2",
46 | "eslint-plugin-react": "^7.5.1",
47 | "express": "^4.16.4",
48 | "husky": "^1.3.1",
49 | "is": "^3.2.1",
50 | "lint-staged": "^8.1.4",
51 | "nodemon": "^1.18.10",
52 | "prettier": "1.16.4",
53 | "webpack-cli": "^3.2.3",
54 | "webpack-dev-middleware": "^3.6.0"
55 | },
56 | "dependencies": {
57 | "@babel/cli": "^7.2.3",
58 | "@babel/core": "^7.3.3",
59 | "@babel/plugin-proposal-class-properties": "^7.3.3",
60 | "@babel/plugin-proposal-object-rest-spread": "^7.3.2",
61 | "@babel/plugin-syntax-export-default-from": "^7.2.0",
62 | "@babel/preset-env": "^7.3.1",
63 | "@babel/preset-react": "^7.0.0",
64 | "archiver": "^3.0.0",
65 | "autoprefixer": "^9.4.7",
66 | "babel-loader": "^8.0.5",
67 | "babel-plugin-transform-export-extensions": "^6.22.0",
68 | "choo": "^6.6.0",
69 | "choo-devtools": "^2.2.0",
70 | "clean-webpack-plugin": "^1.0.1",
71 | "copy-webpack-plugin": "^4.6.0",
72 | "css-loader": "^2.1.0",
73 | "dotenv": "^6.2.0",
74 | "env-cmd": "^8.0.2",
75 | "eslint-config-prettier": "^4.0.0",
76 | "postcss-loader": "^3.0.0",
77 | "precss": "^4.0.0",
78 | "style-loader": "^0.23.1",
79 | "urijs": "^1.19.0",
80 | "web-ext": "^2.9.3",
81 | "webpack": "^4.29.5"
82 | },
83 | "peerDependencies": {}
84 | }
85 |
--------------------------------------------------------------------------------
/src/containers/results/getResults.js:
--------------------------------------------------------------------------------
1 | // import Uri from 'urijs';
2 | import { handleErrors, flatten } from '../../lib/misc';
3 |
4 | function getYoutubeURLs(url) {
5 | let gotVidId = false;
6 | let videoId = '';
7 | let urls = [];
8 | if (url.indexOf('v=') !== -1) {
9 | [, videoId] = url.split('v=');
10 | if (videoId !== '') gotVidId = true;
11 | const ampersandPosition = videoId.indexOf('&');
12 |
13 | if (ampersandPosition !== -1) {
14 | videoId = videoId.substring(0, ampersandPosition);
15 | }
16 |
17 | // some youtube id's contain a dash at the start and reddit search interprets that as NOT
18 | // workaround is to search without the dash in the id
19 | if (videoId.indexOf('-') === 0) {
20 | videoId = videoId.substring(1);
21 | }
22 | }
23 |
24 | if (gotVidId) {
25 | const prefixes = [
26 | 'http://www.youtube.com/watch?v=',
27 | 'https://www.youtube.com/watch?v=',
28 | 'http://www.youtu.be/',
29 | 'https://www.youtu.be/',
30 | ];
31 |
32 | urls = prefixes
33 | .map(prefix => `${prefix}${videoId}`)
34 | .filter(item => item !== url);
35 | }
36 |
37 | return urls;
38 | }
39 |
40 | function constructUrls(URL) {
41 | let url = URL;
42 | if (url.indexOf('http') === -1) {
43 | return [];
44 | }
45 |
46 | let urls = [url];
47 | if (url.indexOf('youtube.com') !== -1) {
48 | urls = urls.concat(getYoutubeURLs(url));
49 | } else {
50 | // remove query string
51 | [url] = url.split('?');
52 | // console.log(url);
53 | }
54 |
55 | if (url.startsWith('https')) {
56 | urls = urls.concat(url.replace('https', 'http'));
57 | }
58 | return urls;
59 | }
60 |
61 | function getAllURLVersions(URL) {
62 | let url = URL;
63 |
64 | // remove firefox reader
65 | if (url.indexOf('about:reader?url=') === 0) {
66 | url = decodeURIComponent(url.substring('about:reader?url='.length));
67 | }
68 |
69 | const urls = constructUrls(url);
70 | const result = urls.map(item => {
71 | const query = encodeURIComponent(item);
72 | const redditUrl = `https://www.reddit.com/api/info.json?url=${query}`;
73 | return redditUrl;
74 | });
75 |
76 | return result;
77 | }
78 |
79 | function handleResponse(response) {
80 | const now = new Date();
81 | const timestamp = now.getTime();
82 | const {
83 | data: { children },
84 | } = response;
85 | console.log(children);
86 |
87 | const submissions = children.map(({ data }) => ({
88 | fullname: data.name,
89 | link: `https://reddit.com${data.permalink}`,
90 | title: data.title,
91 | score: data.score.toString(),
92 | age: timestamp - data.created_utc * 1000,
93 | date: new Date(data.created_utc * 1000),
94 | comments: data.num_comments,
95 | subreddit: data.subreddit,
96 | likes: data.likes,
97 | user: data.author,
98 | }));
99 |
100 | return submissions;
101 | }
102 |
103 | function getURLSubmissions(path) {
104 | return fetch(path)
105 | .then(handleErrors)
106 | .then(response => response.json())
107 | .then(result => handleResponse(result))
108 | .catch(error => console.error(error));
109 | }
110 |
111 | export default function getAllSubmissions(url) {
112 | const redditUrls = getAllURLVersions(url);
113 | const allPromises = redditUrls.map(redditUrl => getURLSubmissions(redditUrl));
114 |
115 | // flatten the array
116 | return Promise.all(allPromises).then(results => flatten(results));
117 | }
118 |
--------------------------------------------------------------------------------