├── .gitignore
├── LICENSE
├── README.md
├── chrome
├── package.json
├── src
│ ├── App.js
│ ├── background.js
│ ├── components
│ │ ├── ClickHandler.js
│ │ ├── CollectionsDropdown.js
│ │ └── NoteField.js
│ ├── content.js
│ ├── css
│ │ ├── bootstrap.min.css
│ │ ├── content.css
│ │ └── popup.css
│ ├── images
│ │ ├── archived
│ │ │ ├── icon128-1.png
│ │ │ ├── icon128-old.png
│ │ │ ├── icon128.png
│ │ │ ├── icon16-old.png
│ │ │ ├── icon16.png
│ │ │ ├── icon24-old.png
│ │ │ ├── icon24.png
│ │ │ ├── icon32-old.png
│ │ │ ├── icon32.png
│ │ │ └── logo-network
│ │ │ │ ├── icon128.png
│ │ │ │ ├── icon16.png
│ │ │ │ ├── icon24.png
│ │ │ │ └── icon32.png
│ │ ├── icon-screenshot.png
│ │ ├── icon-slack.png
│ │ ├── icon128.png
│ │ ├── icon16.png
│ │ ├── icon24.png
│ │ ├── icon32.png
│ │ └── spinner.gif
│ ├── index.js
│ ├── main.js
│ ├── manifest.json
│ ├── popup.html
│ ├── popup.js
│ └── vendor
│ │ ├── jquery-3.4.1.min.js
│ │ ├── tesseract.min.js
│ │ └── tiny-tfidf.js
└── webpack.config.js
└── firefox
├── package.json
├── src
├── background.js
├── content.js
├── css
│ ├── bootstrap.min.css
│ ├── content.css
│ └── popup.css
├── images
│ ├── icon-screenshot.png
│ ├── icon-slack.png
│ ├── icon128.png
│ ├── icon16.png
│ ├── icon24.png
│ ├── icon32.png
│ └── spinner.gif
├── index.js
├── main.js
├── manifest.json
├── popup.html
├── popup.js
└── vendor
│ └── jquery-3.4.1.min.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | chrome/package-lock.json
2 | firefox/package-lock.json
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Rumin
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rumin Web Clipper
2 | Browser extension for [Rumin](https://getrumin.com) which allows you to save learnings from online resources. ([Chrome Web Store link](https://chrome.google.com/webstore/detail/rumin/eboiffdknchlbeboepkadciilgmaojbj), [Firefox Add-on link](https://addons.mozilla.org/en-US/firefox/addon/rumin/))
3 |
4 | It comes with site-specific logic for automatically extracting key information, such as video playing time on YouTube, course info and reference on edX, metadata on Kindle notes etc. This can be easily extended to add extraction logic to any web page.
5 |
6 | This is useful for learning, research, and saving interesting ideas for creative work.
7 |
8 | You can also search / look up existing content (defaulting to the Rumin backend) using the "Lookup" tab.
9 |
10 | ### Demo video
11 | [](https://www.youtube.com/watch?v=auZGwCc1B_o)
12 |
13 |
14 | ### Saving with one click
15 | You can use the extension to bookmark a page, or select a particular passage by simply highlighting it.
16 |
17 | By default, the extension can be opened using the `Ctrl / Cmd + K` keyboard shortcut.
18 |
19 | ### Screenshot
20 | Take a screenshot in one click, using the "Screenshot" button. It captures the content in the current browser window.
21 |
22 |
23 | ### Page-specific Parsing Logic
24 | The extension comes with page-specific logic extracts the key metadata, and turns page content into structured data. This way, you don't have to copy and paste back and forth.
25 |
26 | Currently supported sites include:
27 | - YouTube video
28 | - Skillshare
29 | - edX
30 | - Linkedin Learning
31 | - Netflix
32 | - Messages on Slack
33 | - Comments on Reddit
34 | and more
35 |
36 | ### Lookup
37 | The "Lookup" tab fetches existing captured notes/snippets from the current url by default. You can also use it to search your knowledge base.
38 |
39 | ## Extensible
40 | It is easy to add support for automatic extraction on more sites. All you need is logic to check the url (or content) of the current page, and add logic to update `customFields` accordingly.
41 |
42 | For example, it can be extended to save the top Hacker News comments on a thread, or save metadata of an answer on Quora.
43 |
44 | ## Custom Backend
45 | To use this extension for a different project, simply swap out the getrumin.com API calls with your custom endpoints.
46 |
47 | ## Upcoming work
48 | - Replace jQuery code with React components.
49 | - Support custom logic for extraction on each page. e.g. saved extraction scripts
50 | - Allow editing of automatically extracted fields
51 | - Support multi-capture on the same page
52 | - Allow for adjustment of the dimensions of the screenshot
53 | - Add OCR to captured images
54 | - Support PDF format
55 | - Faster fetching of existing collections
56 | - Navigate nested collections (similar to nested folders)
57 |
--------------------------------------------------------------------------------
/chrome/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Rumin_Chrome_extension",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "webpack --mode development --output ./src/main.js --watch",
8 | "build": "webpack --mode production --output ./src/main.js --watch"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "devDependencies": {
14 | "@babel/core": "^7.8.4",
15 | "@babel/plugin-proposal-class-properties": "^7.8.3",
16 | "@babel/plugin-transform-async-to-generator": "^7.8.3",
17 | "@babel/plugin-transform-regenerator": "^7.8.3",
18 | "@babel/preset-env": "^7.8.4",
19 | "@babel/preset-react": "^7.8.3",
20 | "babel-loader": "^8.0.6",
21 | "react": "^16.13.1",
22 | "react-datepicker": "^2.13.0",
23 | "react-dom": "^16.13.1",
24 | "tesseract.js": "^2.1.1",
25 | "tiny-tfidf": "^0.9.1",
26 | "webpack": "^4.41.5",
27 | "webpack-cli": "^3.3.11"
28 | },
29 | "dependencies": {
30 | "slate": "^0.57.2",
31 | "slate-react": "^0.57.2"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/chrome/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { render } from 'react-dom'
3 | import { CollectionsDropdown } from './components/CollectionsDropdown'
4 |
5 | const App = (props) => {
6 | return(
7 |
8 |
9 |
10 | )
11 | // }
12 | }
13 |
14 | // export default App
15 |
16 | const container = document.getElementById("add_to_collection")
17 | render( , container)
18 |
--------------------------------------------------------------------------------
/chrome/src/background.js:
--------------------------------------------------------------------------------
1 | // Called when the user clicks on the browser action. but doesn't work if there's a popup
2 | // chrome.browserAction.onClicked.addListener(function(tab) {
3 | // console.log("browserAction onClicked");
4 | // });
5 |
6 | function sendMsgToContentScript(msg) {
7 | chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
8 | chrome.tabs.sendMessage(tabs[0].id, msg, function(response) {})
9 | });
10 | }
11 |
12 | // chrome.runtime.onConnect.addListener(() => {
13 | // })
14 |
15 | // chrome.permissions.contains({
16 | // permissions: ['storage']
17 | // }, function(result) {
18 | // if (result) {
19 | // // notify content script
20 | // // sendMsgToContentScript({hasStoragePermission: true})
21 | // }
22 | // })
23 |
24 |
25 | chrome.runtime.onMessage.addListener(function(message, sender, sendResponse){
26 | if (message.popupOpen) {
27 | // console.log('popup is open');
28 |
29 | sendMsgToContentScript({"message": "clicked_browser_action"})
30 | }
31 |
32 | if (message.takeScreenshot) {
33 | // console.log('TODO - take a screenshot')
34 |
35 | chrome.tabs.captureVisibleTab(null, {
36 | format : "png"
37 | }, function(data) {
38 | // screenshot.data = data;
39 | // console.log('screenshot data', data)
40 |
41 | chrome.runtime.sendMessage({screenshotCaptured: true, screenshotData: data});
42 | });
43 | }
44 | });
45 |
46 |
--------------------------------------------------------------------------------
/chrome/src/components/ClickHandler.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react"
2 |
3 | export class ClickHandler extends PureComponent {
4 | constructor(props, context) {
5 | super(props, context)
6 |
7 | this.handleClickOutside = this.handleClickOutside.bind(this)
8 | }
9 |
10 | componentDidMount() {
11 | document.addEventListener("mousedown", this.handleClickOutside)
12 | }
13 |
14 | componentWillUnmount() {
15 | document.removeEventListener("mousedown", this.handleClickOutside)
16 | }
17 |
18 | handleClickOutside(e) {
19 | if (
20 | this.wrapperRef &&
21 | !this.wrapperRef.contains(e.target)
22 | ) {
23 | this.props.close()
24 | }
25 | }
26 |
27 | render() {
28 | return(
29 | (this.wrapperRef = node)}
32 | style={{width: '100%', height: '100%', display: `${this.props.displayBlock ? 'block' : 'inline'}`}}
33 | >{this.props.children}
34 | )
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/chrome/src/components/CollectionsDropdown.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { ClickHandler } from './ClickHandler'
3 |
4 | export const CollectionsDropdown = (props) => {
5 | const [query, setQuery] = useState('')
6 | const [selectedSpaces, setSelectedSpaces] = useState([])
7 | const [prevSelectedSpaces, setPrevSelectedSpaces] = useState([])
8 | const [prevSpaceSuggestions, setPrevSpaceSuggestions] = useState([])
9 | const [hasStoragePermission, setHasStoragePermission] = useState(false)
10 | const [queryTimer, setQueryTimer] = useState(null)
11 |
12 | const [isFetchingSuggestions, setIsFetchingSuggestions] = useState(false)
13 | const [hasFetchedSuggestions, setHasFetchedSuggestions] = useState(false)
14 | const [showSuggestionsDropdown, setShowSuggestionsDropdown] = useState(false)
15 |
16 | const [spaceSuggestions, setSpaceSuggestions] = useState([]) // results
17 |
18 | useEffect(() => {
19 | chrome.permissions.contains({
20 | permissions: ['storage']
21 | }, result => {
22 | if (result) {
23 | setHasStoragePermission(true)
24 |
25 | chrome.storage.local.get(['selectedSpaces', 'prevSelectedSpaces', 'prevSpaceSuggestions'], result => {
26 | if (result.selectedSpaces && result.selectedSpaces.length > 0 && selectedSpaces.length === 0) {
27 | setSelectedSpaces(result.selectedSpaces)
28 | window.selectedSpaces = result.selectedSpaces
29 | }
30 |
31 | if (result.prevSelectedSpaces && result.prevSelectedSpaces.length > 0) {
32 | setPrevSelectedSpaces(result.prevSelectedSpaces)
33 | }
34 | // if (result.prevSpaceSuggestions && result.prevSpaceSuggestions.length > 0) {
35 | // setPrevSpaceSuggestions(result.prevSpaceSuggestions)
36 | // }
37 | })
38 | }
39 | })
40 | }, [])
41 |
42 | const uniqueSuggestions = (suggestions) => {
43 | let results = []
44 | let seen = {}
45 | suggestions.forEach(s => {
46 | if (seen[s.id] === true) {
47 | return
48 | }
49 |
50 | results.push(s)
51 | seen[s.id] = true
52 | })
53 |
54 | return results
55 | }
56 |
57 | const updateSelectedSpaces = (selected) => {
58 | setSelectedSpaces(selected)
59 | window.selectedSpaces = selected
60 |
61 | if (hasStoragePermission) {
62 | chrome.storage.local.set({ selectedSpaces: selected })
63 |
64 | // cache the previously selected spaces
65 | chrome.storage.local.get(['prevSelectedSpaces'], result => {
66 | const cachedResults = result.prevSelectedSpaces || []
67 | const suggestionsToCache = uniqueSuggestions([...cachedResults.slice(0, 100), ...selected])
68 | chrome.storage.local.set({ prevSelectedSpaces: suggestionsToCache })
69 | })
70 | }
71 | }
72 |
73 | const handleSuggestionClick = (space) => {
74 | const updatedSelection = [...selectedSpaces, space]
75 | updateSelectedSpaces(updatedSelection)
76 | setQuery('')
77 | setShowSuggestionsDropdown(false)
78 | }
79 |
80 | const handleRemoveToken = (space) => {
81 | const updatedSelection = selectedSpaces.filter(s => s.id !== space.id)
82 | updateSelectedSpaces(updatedSelection)
83 | }
84 |
85 | const fetchAutosuggest = () => {
86 | if (query.length < 2) return
87 |
88 | setHasFetchedSuggestions(false)
89 | setIsFetchingSuggestions(true)
90 |
91 | fetch(`https://getrumin.com/api/v1/search/?q=${query}&content_type=Space&lite=true`, {
92 | method: 'GET',
93 | headers: {
94 | 'Content-type': 'application/json'
95 | }
96 | })
97 | .then(res => {
98 | if (!res.ok) throw new Error(res.status)
99 | else return res.json()
100 | })
101 | .then(data => {
102 | setShowSuggestionsDropdown(true)
103 | setSpaceSuggestions(data.results)
104 |
105 | setIsFetchingSuggestions(false)
106 | setHasFetchedSuggestions(true)
107 |
108 | // cache the response
109 | // if (hasStoragePermission) {
110 | // chrome.storage.local.get(['prevSpaceSuggestions'], result => {
111 | // const cachedResults = result.prevSpaceSuggestions || []
112 |
113 | // const suggestionsToCache = uniqueSuggestions([...cachedResults.slice(0, 100), ...data.results])
114 | // chrome.storage.local.set({ prevSpaceSuggestions: suggestionsToCache })
115 | // })
116 | // }
117 | })
118 | .catch(error => {
119 | console.log('error: ' + error)
120 | })
121 | }
122 |
123 | const handleInputFocus = () => {
124 |
125 | // if there is no query, use previously selected spaces
126 | if (query.length === 0 && prevSelectedSpaces.length > 0) {
127 | setShowSuggestionsDropdown(true)
128 |
129 | const suggestions = prevSelectedSpaces.slice(0, 20).filter(s => {
130 | return !selectedSpaces.map(ss => ss.id).includes(s.id)
131 | })
132 | // console.log('in handleInputFocus', suggestions, spaceSuggestions.map(ss => ss.id), prevSelectedSpaces)
133 |
134 | setSpaceSuggestions(suggestions)
135 | }
136 | }
137 |
138 | const handleQueryChange = (e) => {
139 | setQuery(e.target.value)
140 | }
141 |
142 | const handleQueryKeyDown = (e) => {
143 | if (e.key === 'Backspace' && query.length === 0) {
144 | handleRemoveToken(selectedSpaces[selectedSpaces.length-1])
145 | }
146 |
147 | if (e.key === 'Escape') {
148 | props.closeDropdown()
149 | }
150 | }
151 |
152 | const handleQueryKeyUp = () => {
153 | clearTimeout(queryTimer)
154 | const timer = setTimeout(() => { fetchAutosuggest() }, 750)
155 | setQueryTimer(timer)
156 | }
157 |
158 | const handleNewSpaceClick = () => {
159 | const body = {
160 | title: query
161 | }
162 |
163 | fetch(`https://getrumin.com/api/v1/spaces/`, {
164 | method: 'POST',
165 | headers: {
166 | 'Content-type': 'application/json'
167 | },
168 | body: JSON.stringify(body)
169 | })
170 | .then(res => {
171 | if (!res.ok) throw new Error(res.status)
172 | else return res.json()
173 | })
174 | .then(space => {
175 | // success
176 | const updatedSelection = [...selectedSpaces, space]
177 | updateSelectedSpaces(updatedSelection)
178 | setQuery('')
179 | })
180 | .catch(error => {
181 | console.log('error: ' + error)
182 | })
183 | }
184 |
185 | const buildResults = () => {
186 | // console.log(query.length, hasFetchedSuggestions, spaceSuggestions.length, spaceSuggestions)
187 |
188 | if (query.length > 0 && hasFetchedSuggestions && spaceSuggestions.length === 0) {
189 | return(
190 |
193 | No matching results
194 |
195 | )
196 | }
197 |
198 | // if (query.length < 2) return ''
199 | return spaceSuggestions.map(space => {
200 | return(
201 |
206 | );
207 | });
208 | }
209 |
210 | const buildSelectedTokens = () => {
211 | return selectedSpaces.map(space => {
212 | return(
213 |
214 |
215 | { space.title }
216 |
217 |
handleRemoveToken(space)}
220 | >
221 |
222 |
223 |
224 | )
225 | })
226 | }
227 |
228 | const buildFetchingMoreMsg = () => {
229 | if (!isFetchingSuggestions) return ''
230 |
231 | return(
232 |
235 | fetching more results...
236 |
237 | )
238 | }
239 |
240 | const buildASResultsSection = () => {
241 | if (!showSuggestionsDropdown) return ''
242 | // if (!showSuggestionsDropdown || spaceSuggestions.length < 1) return ''
243 |
244 | return(
245 | setShowSuggestionsDropdown(false)}
247 | >
248 |
249 |
250 | { buildCreateCollection() }
251 | { buildResults() }
252 | { buildFetchingMoreMsg() }
253 |
254 |
255 |
256 | )
257 | }
258 |
259 | const buildCreateCollection = () => {
260 | if (query.length < 1) return ''
261 |
262 | return(
263 |
268 | + New page "{ query }"
269 |
270 | )
271 | }
272 |
273 | return(
274 |
275 |
Add to collections
276 |
277 |
278 | { buildSelectedTokens() }
279 |
280 |
281 |
290 |
291 |
292 |
293 | { buildASResultsSection() }
294 |
295 | );
296 | }
297 |
298 | const SuggestionResult = (props) => {
299 | const handleClick = () => {
300 | props.handleSuggestionClick(props.space)
301 | }
302 |
303 | return(
304 |
308 | { props.space.title }
309 |
310 | )
311 | }
312 |
313 | const SuggestedLinkToken = (props) => {
314 | return(
315 |
316 | { props.space.title }
317 |
322 |
323 | )
324 | }
325 |
--------------------------------------------------------------------------------
/chrome/src/components/NoteField.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 |
3 | export const NoteField = (props) => {
4 | return(
5 | )
6 | }
7 |
--------------------------------------------------------------------------------
/chrome/src/content.js:
--------------------------------------------------------------------------------
1 | const MOUSE_VISITED_CLASSNAME = 'crx_mouse_visited';
2 | let prevDOM = null
3 | let selectingDOM = false
4 | let selectedElements = []
5 | let hasStoragePermission = false
6 |
7 | // function isElementVisible(el) {
8 | // const o = new IntersectionObserver(([entry]) => {
9 | // return entry.intersectionRatio === 1;
10 | // });
11 | // o.observe(el)
12 | // }
13 |
14 | const getTopTermsInDoc = () => {
15 | const corpus = new TinyTFIDF.Corpus(['doc1'], [$('body').text()])
16 | return corpus.getTopTermsForDocument('doc1').slice(0, 15)
17 | }
18 |
19 | const initSelectedElements = () => {
20 | // get fetch existing selectedElements
21 | chrome.storage.local.get(['selectedElements'], function(result) {
22 | console.log('result.selectedElements', result.selectedElements)
23 |
24 | selectedElements = result.selectedElements || []
25 | })
26 | }
27 |
28 | const clearSelection = () => {
29 | unstyleSelectedElements()
30 |
31 | selectedElements = []
32 | $(document).unbind('mousemove')
33 | }
34 |
35 | const unstyleSelectedElements = () => {
36 | if (selectedElements.length > 0) {
37 | selectedElements.forEach(el => {
38 | // console.log('el in selectedElements', el)
39 | el.classList.remove(MOUSE_VISITED_CLASSNAME);
40 | })
41 | }
42 | }
43 |
44 | const styleSelectedElements = () => {
45 | if (selectedElements.length > 0) {
46 | selectedElements.forEach(el => {
47 | // console.log('el in selectedElements', el)
48 | el.classList.add(MOUSE_VISITED_CLASSNAME);
49 | })
50 | }
51 | }
52 |
53 | // Extract selected fields for Slack
54 | const slackFields = () => {
55 | const messages = selectedElements.map(el => {
56 | let selector = $(el)
57 | if (!el.classList.contains('c-virtual_list__item') && !el.classList.contains('c-message_kit__gutter')) {
58 | selector = $(el).closest('.c-virtual_list__item')
59 | }
60 |
61 | if (selector.length === 0) return null
62 |
63 | const sender = selector.find('.c-message__sender').text()
64 | const link = selector.find('a.c-link.c-timestamp')[0]['href']
65 | const text = selector.find('.p-rich_text_block').text()
66 |
67 | return({
68 | sender_name: sender,
69 | message_link: link,
70 | message_text: text
71 | })
72 | })
73 |
74 | return { messages: messages.filter((m) => m) }
75 | }
76 |
77 | const redditSelectedFields = () => {
78 | const comments = selectedElements.map(el => {
79 | let selector = $(el)
80 |
81 | let comment = {}
82 |
83 | const commentBody = $(el).find('div[data-test-id="comment"]')
84 | if (commentBody.length === 0) return null
85 | comment.body = commentBody.text()
86 |
87 | const timestamp = $(el).find('[id^=CommentTopMeta--Created--]')
88 | if (timestamp.length > 0) {
89 | comment.url = timestamp[0]['href']
90 | }
91 |
92 | return comment
93 | })
94 |
95 | return { comments: comments.filter(c => c) }
96 | }
97 |
98 | // parse '(hh):mm:ss' string into seconds
99 | const timeStringToSeconds = (str) => {
100 | return str.split(':').reverse().reduce((prev, curr, i) => prev + curr*Math.pow(60, i), 0)
101 | }
102 |
103 | const youtubeFields = () => {
104 | const currentTime = $('.ytp-time-current')[0].innerText
105 | const channelName = $('.ytd-channel-name yt-formatted-string')[0].innerText
106 | const channelUrl = $('.ytd-channel-name yt-formatted-string a')[0]['href']
107 | const publishedDate = $('#date yt-formatted-string.ytd-video-primary-info-renderer')[0].innerText.replace('Premiered ', '') // FIXME - this can break for other languages
108 |
109 | return ({
110 | current_time: currentTime,
111 | channel_name: channelName,
112 | channel_url: channelUrl,
113 | published_date: publishedDate
114 | })
115 | }
116 |
117 | const linkedinLearningFields = () => {
118 | const classTitle = $('.classroom-nav__details h1').text()
119 | const currentTime = $('.vjs-current-time').text()
120 | const teacherName = $('.authors-entity__name-text').text().trim().split("\n")[0]
121 | const teacherUrl = $('a.course-author-entity__lockup').attr('href')
122 | const sessionTitle = $('.classroom-toc-item--selected').text()
123 | const sessionTranscript = $('.transcripts-component__sections').text().trim()
124 |
125 | return({
126 | class_title: classTitle.trim(),
127 | current_time: currentTime,
128 | teacher_name: teacherName.trim(),
129 | teacher_url: teacherUrl,
130 | session_title: sessionTitle.trim(),
131 | session_transcript: sessionTranscript
132 | })
133 | }
134 |
135 | const skillShareFields = () => {
136 | const classTitle = $('.class-details-header-name').text()
137 | const currentTime = $('.vjs-current-time-display').text()
138 | const teacherName = $('.class-details-header-teacher-link').text()
139 | const teacherUrl = $('.class-details-header-teacher-link').attr('href')
140 | const sessionTitle = $('.session-item.active .session-item-title').text()
141 |
142 | return({
143 | class_title: classTitle.trim(),
144 | current_time: currentTime,
145 | teacher_name: teacherName.trim(),
146 | teacher_url: teacherUrl,
147 | session_title: sessionTitle.trim()
148 | })
149 | }
150 |
151 | const netflixFields = () => {
152 | const videoTitle = $('.video-title h4').text()
153 | const episodeTitle = $('.video-title span').text()
154 | const currentTime = $('.scrubber-head').attr('aria-valuetext')
155 |
156 | return({
157 | video_title: videoTitle,
158 | episode_title: episodeTitle,
159 | current_time: currentTime
160 | })
161 | }
162 |
163 | const edxLectureFields = () => {
164 | const provider = $('.course-header .provider').text()
165 | const courseCode = $('.course-header .course-number').text()
166 | const courseName = $('.course-header .course-name').text()
167 |
168 | const videoUrl = $('.video-sources').length > 0 ? $('.video-sources').get(0)['href'] : null
169 | const slidesUrl = $('a[href$=pdf]').length > 0 ? $('a[href$=pdf]').get(0)['href'] : null
170 |
171 | const vidTime = $('.vidtime').length > 0 ? $('.vidtime').text().split('/')[0].trim() : null
172 |
173 | return({
174 | course_provider: provider,
175 | course_code: courseCode,
176 | course_name: courseName,
177 | video_url: videoUrl,
178 | slides_url: slidesUrl,
179 | current_time: vidTime
180 | })
181 | }
182 |
183 | const isLinkedinLearningPage = () => {
184 | return location.href.startsWith('https://www.linkedin.com/learning/') && $('.classroom-layout__content').length > 0
185 | }
186 |
187 | const isSkillshareVideoPage = () => {
188 | return location.href.startsWith('https://www.skillshare.com/classes/')
189 | }
190 |
191 | const isNetflixVideoPage = () => {
192 | return location.href.startsWith('https://www.netflix.com/watch/')
193 | }
194 |
195 | const isYoutubeVideoPage = () => {
196 | return location.href.startsWith('https://www.youtube.com/watch?v=')
197 | }
198 |
199 | const isKindleCloudReaderPage = () => {
200 | return location.href.startsWith('https://read.amazon.com') && !location.href.includes('notebook')
201 | }
202 |
203 | const isKindleNotebookPage = () => {
204 | return location.href.startsWith('https://read.amazon.com/notebook')
205 | }
206 |
207 | const isSlackPage = () => {
208 | return location.href.startsWith('https://app.slack.com/client/')
209 | }
210 |
211 | const isRedditPage = () => {
212 | return location.href.includes('reddit.com/r/') && location.href.includes('comments')
213 | }
214 |
215 | const isEdxLecturePage = () => {
216 | return location.href.startsWith('https://courses.edx.org/courses/') && location.href.includes('courseware')
217 | }
218 |
219 | const parseSelectedElements = () => {
220 | if (isSlackPage()) {
221 | return slackFields()
222 | }
223 |
224 | if (isRedditPage()) {
225 | return redditSelectedFields()
226 | }
227 | }
228 |
229 | chrome.runtime.onMessage.addListener(
230 | function(request, sender, sendResponse) {
231 | // console.log(request.message);
232 |
233 | // if (request.disconnect === true) {
234 | // if (hasStoragePermission) {
235 | // chrome.storage.local.clear()
236 | // chrome.storage.local.remove('selectedElements')
237 | // }
238 | // }
239 |
240 | if (request.hasStoragePermission === true) {
241 | hasStoragePermission = true
242 | sendResponse({ack: true})
243 | }
244 |
245 | if (request.clearSelection === true) {
246 | clearSelection()
247 | sendResponse({ack: true})
248 | }
249 |
250 | if (request.message === "clicked_browser_action") {
251 | const sel = window.getSelection()
252 | const selectionText = sel.toString()
253 |
254 | // console.log('selectionText', selectionText)
255 |
256 | let titleOverride = null
257 | let urlOverride = null
258 | let customFields = {}
259 | let closestId = ''
260 |
261 | if (sel && sel.rangeCount > 0) {
262 | const selectionEl = sel.getRangeAt(0).startContainer.parentNode
263 |
264 | if (selectionEl.id) {
265 | closestId = selectionEl.id
266 | }
267 | else {
268 | const prevSibling = $(selectionEl).prev('[id]')
269 | const prevParent = $(selectionEl).closest('[id]')
270 |
271 | if (prevSibling.length > 0) {
272 | closestId = prevSibling[0].id
273 | }
274 | else if (prevParent.length > 0) {
275 | closestId = prevParent[0].id
276 | }
277 | }
278 |
279 | if (closestId) {
280 | urlOverride = `${location.href}#${closestId}`
281 | }
282 | }
283 |
284 | // Index the DOM
285 | getTopTermsInDoc()
286 |
287 |
288 | // Youtube video
289 | if (isYoutubeVideoPage()) {
290 | const fields = youtubeFields()
291 | Object.assign(customFields, fields)
292 |
293 | // TODO - replace an existing t parameter
294 | if (location.search.includes('t=')) {
295 | urlOverride = `${location.origin}${location.pathname}${location.search.replace(/t=[0-9]+s/, 't=' + timeStringToSeconds(fields.current_time) + 's')}`
296 | }
297 | else {
298 | urlOverride = `${location.href}&t=${timeStringToSeconds(fields.current_time)}`
299 | }
300 | }
301 |
302 | // Netflix Video
303 | if (isNetflixVideoPage()) {
304 | const fields = netflixFields()
305 | Object.assign(customFields, fields)
306 | }
307 |
308 | // Skillshare video
309 | if (isSkillshareVideoPage()) {
310 | const fields = skillShareFields()
311 | Object.assign(customFields, fields)
312 | }
313 |
314 | // Linkedin Learning
315 | if (isLinkedinLearningPage()) {
316 | const fields = linkedinLearningFields()
317 | Object.assign(customFields, fields)
318 | }
319 |
320 |
321 | // Kindle Cloud reader
322 | if (isKindleCloudReaderPage()) {
323 |
324 | }
325 |
326 | // Page title
327 | if ($('h1').length > 0) {
328 | customFields.page_title = $('h1')[0].textContent.trim()
329 | }
330 |
331 | // edX
332 | if (isEdxLecturePage()) {
333 | Object.assign(customFields, edxLectureFields())
334 | }
335 |
336 | // if (isMedium)
337 | // Kindle Notes and Highlights: https://read.amazon.com/notebook
338 | // Go to the first book
339 | // $('.kp-notebook-library-each-book a.a-link-normal')[0].click()
340 | // document in the first kindle iframe
341 | // $('#KindleReaderIFrame').get(0).contentDocument
342 | if (isKindleNotebookPage()) {
343 |
344 | console.log('is kindle notebook page!')
345 |
346 | titleOverride = $('h3').text()
347 | customFields.page_title = $('h3').text()
348 | customFields.book_title = $('h3').text()
349 | customFields.book_author = $('p.kp-notebook-metadata')[1].innerText
350 |
351 | let currRow
352 |
353 | if (sel && sel.rangeCount > 0) {
354 | const selectionEl = sel.getRangeAt(0).startContainer.parentNode
355 |
356 | if (selectionEl.classList.contains('a-row')) {
357 | currRow = selectionEl
358 | // closestId = selectionEl.id
359 | }
360 | else {
361 | const prevSibling = $(selectionEl).prev('.a-row')
362 | const prevParent = $(selectionEl).closest('.a-row')
363 |
364 | if (prevSibling.length > 0) {
365 | // closestId = prevSibling[0].id
366 | currRow = prevSibling
367 | }
368 | else if (prevParent.length > 0) {
369 | // closestId = prevParent[0].id
370 | currRow = prevParent
371 | }
372 | }
373 |
374 | console.log('currRow', currRow)
375 | const prevRow = $(selectionEl).closest('.kp-notebook-row-separator')
376 | console.log('prevRow', prevRow)
377 |
378 |
379 | customFields.book_location = prevRow.find('#annotationHighlightHeader')[0].innerText
380 |
381 | }
382 | }
383 |
384 |
385 | if (selectedElements.length > 0) {
386 |
387 | const selectedFields = parseSelectedElements()
388 |
389 | customFields = {
390 | ...customFields,
391 | ...selectedFields
392 | }
393 | }
394 |
395 | const pageContext = {
396 | pageContext: {
397 | urlOverride: urlOverride,
398 | titleOverride: titleOverride,
399 | selection: selectionText,
400 | // closestId: closestId,
401 | // page_dom: document.documentElement.outerHTML,
402 | customFields: customFields
403 | }
404 | };
405 |
406 | // console.log('sending pageContext', pageContext, window.getSelection().toString())
407 |
408 | chrome.runtime.sendMessage(pageContext, function(response) {
409 | });
410 | }
411 |
412 | if (request.addSelection) {
413 | selectingDOM = true
414 |
415 | console.log('in addSelection in content script')
416 | sendResponse({ack: true})
417 |
418 | $(document).mousemove(function(e) {
419 | var target = e.target;
420 |
421 | // console.log('target', target)
422 | const whiteListedNodes = ['DIV', 'IMG', 'A', 'P', 'SPAN', 'H1', 'H2', 'H3', 'H4', 'H5']
423 |
424 | // if (whiteListedClasses && target.class)
425 | // TODO - perhaps we should restrict what elements can be added?
426 | // do it by source
427 |
428 | if (whiteListedNodes.includes(target.nodeName)) {
429 | // For NPE checking, we check safely. We need to remove the class name
430 | // Since we will be styling the new one after.
431 | if (prevDOM != null && !selectedElements.includes(prevDOM)) {
432 | prevDOM.classList.remove(MOUSE_VISITED_CLASSNAME);
433 | }
434 | // Add a visited class name to the element. So we can style it.
435 | target.classList.add(MOUSE_VISITED_CLASSNAME);
436 | // The current element is now the previous. So we can remove the class
437 | // during the next iteration.
438 | prevDOM = target;
439 | }
440 | })
441 | }
442 |
443 | if (request.cancelSelection) {
444 | selectingDOM = false
445 | $(document).unbind('mousemove')
446 | }
447 | }
448 | );
449 |
450 |
451 | $(function() {
452 | $(document).click(function(e) {
453 | if (selectingDOM) {
454 | if (hasStoragePermission) {
455 | let element = e.target
456 |
457 | if (selectedElements && selectedElements.includes(element.outerHTML)) return
458 |
459 | selectedElements.push(element)
460 | }
461 | }
462 | })
463 | })
464 |
--------------------------------------------------------------------------------
/chrome/src/css/content.css:
--------------------------------------------------------------------------------
1 | .crx_mouse_visited {
2 | background-color: #cbe6ff !important;
3 | outline: 1px solid #5166bb !important;
4 | }
5 |
--------------------------------------------------------------------------------
/chrome/src/css/popup.css:
--------------------------------------------------------------------------------
1 | body {
2 | min-width: 250px;
3 | position: relative;
4 | }
5 |
6 | button {
7 | height: 30px;
8 | width: 30px;
9 | outline: none;
10 | }
11 |
12 | .flex {
13 | display: flex;
14 | }
15 |
16 |
17 | ::-webkit-scrollbar {
18 | width: 0px;
19 | }
20 |
21 | ::-webkit-scrollbar-corner {
22 | background: transparent;
23 | }
24 |
25 | ::-webkit-scrollbar-thumb {
26 | background-color: rgba(0,0,0,.2);
27 | background-clip: padding-box;
28 | border: solid transparent;
29 | border-width: 1px 1px 1px 6px;
30 | min-height: 28px;
31 | padding: 100px 0 0;
32 | }
33 |
34 | .hidden {
35 | display: none !important;
36 | }
37 |
38 | .section-heading {
39 | cursor: pointer;
40 | }
41 |
42 | .gray-text {
43 | color: #777;
44 | }
45 |
46 | .section-heading .text {
47 | font-weight: 600;
48 | font-size: 0.7em;
49 | text-transform: uppercase;
50 | margin-bottom: 8px;
51 | }
52 |
53 | .btn.btn-primary {
54 | background-color: #0f8bff;
55 | }
56 |
57 | .inline-block {
58 | display: inline-block;
59 | }
60 |
61 | i.small-icon {
62 | font-size: 0.8em;
63 | }
64 |
65 | .field-label {
66 | font-size: 0.85em;
67 | color: #777;
68 | margin-bottom: 4px;
69 | }
70 |
71 | #screenshot_img {
72 | width: 100%;
73 | }
74 |
75 | .capture-form-container {
76 | padding-bottom: 2em;
77 | overflow-y: scroll;
78 | overflow: visible;
79 | }
80 |
81 | /*.capture-form-container::-webkit-scrollbar {
82 | width: 12px;
83 | }*/
84 |
85 | .save-btn-container {
86 | position: fixed;
87 | width: 100%;
88 | left: 0;
89 | right: 0;
90 | bottom: 0;
91 | padding: 1em;
92 | background-color: white;
93 | }
94 |
95 | .permission-bar {
96 | font-size: 0.9em;
97 | text-align: center;
98 | background-color: #CBE6FF;
99 | padding: 0.5em 0;
100 | }
101 |
102 | .inline-block {
103 | display: inline-block;
104 | }
105 |
106 | .btn-icon {
107 | width: 14px;
108 | margin-top: -4px;
109 | }
110 |
111 | .tabs {
112 | white-space: nowrap;
113 | border-bottom: 1px solid #eee;
114 | }
115 |
116 | .tabs .tab {
117 | display: inline-block;
118 | width: 50%;
119 | text-align: center;
120 | box-sizing: border-box;
121 | padding: 0.5em 0;
122 | color: #777;
123 | cursor: pointer;
124 | }
125 |
126 | .tabs .tab:hover {
127 | background-color: #f0f0f0;
128 | }
129 |
130 | .tab.active {
131 | border-bottom: 2px solid #0f8bff;
132 | }
133 |
134 | .section-container,
135 | .capture-container {
136 | width: 350px;
137 | }
138 |
139 | .capture-container {
140 | padding: 1em;
141 | }
142 |
143 | .captured-selection {
144 | padding: 1em;
145 | font-size: 0.8em;
146 | background-color: #f9f9f9;
147 | margin-bottom: 1em;
148 |
149 | max-height: 200px;
150 | overflow-y: scroll;
151 | }
152 |
153 | .captured-selection::-webkit-scrollbar {
154 | width: 12px;
155 | }
156 |
157 | .capture-container textarea {
158 | resize: none;
159 | min-height: 5em;
160 | overflow: hidden;
161 | }
162 |
163 | textarea.note-field {
164 | min-height: 7em;
165 | }
166 |
167 | .search-box-container {
168 | padding: 1em;
169 | border-bottom: 1px solid #eee;
170 | }
171 |
172 | .search-box {
173 | width: 100%;
174 | border: none;
175 | border-bottom: 1px solid #eee;
176 | /*margin: 1em;*/
177 | }
178 |
179 | .search-box:focus {
180 | outline: none;
181 | }
182 |
183 | .search-results {
184 | min-height: 100px;
185 | background-color: #f0f0f0;
186 | padding: 1em;
187 | max-height: 550px;
188 | overflow-y: scroll;
189 | }
190 |
191 | .search-results .list-item {
192 | background-color: white;
193 | padding: 1em;
194 | border-radius: 12px;
195 | margin-bottom: 1em;
196 | -webkit-box-shadow: 0px 5px 4px 0px rgba(171,171,171,1);
197 | -moz-box-shadow: 0px 5px 4px 0px rgba(171,171,171,1);
198 | box-shadow: 0px 5px 4px 0px rgba(171,171,171,1);
199 | overflow-wrap: break-word;
200 | }
201 |
202 | .list-item .title {
203 | margin-bottom: 0.5em;
204 | }
205 |
206 | .list-item .title a {
207 | font-size: 1.15em;
208 | }
209 |
210 | .list-item .text-body {
211 | color: #666;
212 | font-size: 0.9em;
213 | }
214 |
215 | .search-results .heading {
216 | font-weight: 600;
217 | font-size: 0.7em;
218 | text-transform: uppercase;
219 | margin-bottom: 8px;
220 | }
221 |
222 | .list-item .timestamp {
223 | font-size: 0.8em;
224 | margin-bottom: 0.5em;
225 | color: #888;
226 | }
227 |
228 | #custom_fields {
229 | /*overflow-wrap: break-word;*/
230 | color: #666;
231 | max-height: 200px;
232 | overflow-y: scroll;
233 | }
234 |
235 | #custom_fields::-webkit-scrollbar {
236 | width: 14px;
237 | }
238 |
239 | #custom_fields .prop-value {
240 | overflow-wrap: break-word;
241 | white-space: pre-wrap;
242 | font-size: 0.9em
243 | }
244 |
245 | #custom_fields .prop-name {
246 | font-size: 0.9em;
247 | font-weight: 550;
248 | }
249 |
250 | #custom_fields .divider {
251 | height: 1px;
252 | background-color: #ddd;
253 | margin: 1em;
254 | }
255 |
256 | .collections-dropdown-container {
257 | position: relative;
258 | }
259 |
260 | .collections-dropdown {
261 | /*padding: 0.75em;*/
262 | position: absolute;
263 | width: 100%;
264 | border: 1px solid #ccc;
265 | border-radius: 8px;
266 | background-color: white;
267 | z-index: 2;
268 |
269 | border-top-left-radius: 0;
270 | border-top-right-radius: 0;
271 |
272 | -webkit-box-shadow: 0px 5px 4px 0px rgba(171,171,171,1);
273 | -moz-box-shadow: 0px 5px 4px 0px rgba(171,171,171,1);
274 | box-shadow: 0px 5px 4px 0px rgba(171,171,171,1);
275 | }
276 |
277 | .as-msg {
278 | padding: 0.5em 0.75em;
279 | font-size: 0.9em;
280 | }
281 |
282 | .as-result {
283 | padding: 0.5em 0.75em;
284 | cursor: pointer;
285 | color: #666;
286 | font-size: 0.9em;
287 | }
288 |
289 | .as-result:hover {
290 | background-color: #eff7ff;
291 | outline: none;
292 | }
293 |
294 | .collections-selected {
295 | display: flex;
296 | flex-shrink: 0;
297 | max-height: 240px;
298 |
299 | flex-wrap: wrap;
300 | align-items: flex-start;
301 |
302 | border: 1px solid #ced4da;
303 | border-radius: 0.25rem;
304 | padding: .375rem .75rem;
305 | }
306 |
307 | .collections-selected .token {
308 | display: flex;
309 | flex-shrink: 1;
310 | height: 18px;
311 | align-items: center;
312 | background-color: #eff7ff;
313 | padding: 2px 6px;
314 | margin-right: 6px;
315 | margin-bottom: 6px;
316 | }
317 |
318 | .token .remove-btn {
319 | cursor: pointer;
320 | display: flex;
321 | align-items: center;
322 | justify-content: center;
323 | flex-grow: 0;
324 | flex-shrink: 0;
325 | margin-left: 2px;
326 | margin-right: 2px;
327 | width: 18px;
328 | height: 18px;
329 | }
330 |
331 | .results {
332 | max-height: 200px;
333 | overflow-y: scroll;
334 | }
335 |
336 | .results::-webkit-scrollbar {
337 | width: 10px;
338 | }
339 |
340 | .collections-dropdown .collections-search-container {
341 | display: flex;
342 | align-items: center;
343 | min-width: 100px;
344 | flex: 1;
345 | }
346 |
347 | .collections-search {
348 | font-family: sans-serif;
349 | font-size: 1em;
350 | width: 100%;
351 | border: none;
352 | padding-bottom: 0.5em;
353 | }
354 |
355 | .collections-search:focus {
356 | outline: none;
357 | }
358 |
--------------------------------------------------------------------------------
/chrome/src/images/archived/icon128-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/archived/icon128-1.png
--------------------------------------------------------------------------------
/chrome/src/images/archived/icon128-old.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/archived/icon128-old.png
--------------------------------------------------------------------------------
/chrome/src/images/archived/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/archived/icon128.png
--------------------------------------------------------------------------------
/chrome/src/images/archived/icon16-old.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/archived/icon16-old.png
--------------------------------------------------------------------------------
/chrome/src/images/archived/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/archived/icon16.png
--------------------------------------------------------------------------------
/chrome/src/images/archived/icon24-old.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/archived/icon24-old.png
--------------------------------------------------------------------------------
/chrome/src/images/archived/icon24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/archived/icon24.png
--------------------------------------------------------------------------------
/chrome/src/images/archived/icon32-old.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/archived/icon32-old.png
--------------------------------------------------------------------------------
/chrome/src/images/archived/icon32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/archived/icon32.png
--------------------------------------------------------------------------------
/chrome/src/images/archived/logo-network/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/archived/logo-network/icon128.png
--------------------------------------------------------------------------------
/chrome/src/images/archived/logo-network/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/archived/logo-network/icon16.png
--------------------------------------------------------------------------------
/chrome/src/images/archived/logo-network/icon24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/archived/logo-network/icon24.png
--------------------------------------------------------------------------------
/chrome/src/images/archived/logo-network/icon32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/archived/logo-network/icon32.png
--------------------------------------------------------------------------------
/chrome/src/images/icon-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/icon-screenshot.png
--------------------------------------------------------------------------------
/chrome/src/images/icon-slack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/icon-slack.png
--------------------------------------------------------------------------------
/chrome/src/images/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/icon128.png
--------------------------------------------------------------------------------
/chrome/src/images/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/icon16.png
--------------------------------------------------------------------------------
/chrome/src/images/icon24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/icon24.png
--------------------------------------------------------------------------------
/chrome/src/images/icon32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/icon32.png
--------------------------------------------------------------------------------
/chrome/src/images/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/chrome/src/images/spinner.gif
--------------------------------------------------------------------------------
/chrome/src/index.js:
--------------------------------------------------------------------------------
1 | // import App from "../components/App";
2 | import App from './App'
--------------------------------------------------------------------------------
/chrome/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Rumin",
3 | "version": "0.0.0.15",
4 | "description": "The fastest way to collect information from diverse sources on the web - Rumin helps you capture any content instantly.",
5 | "permissions": ["activeTab"],
6 | "optional_permissions": ["storage"],
7 | "background": {
8 | "scripts": ["background.js"]
9 | },
10 | "content_scripts": [
11 | {
12 | "matches": [
13 | "*://*/*"
14 | ],
15 | "js": ["vendor/jquery-3.4.1.min.js", "vendor/tiny-tfidf.js", "content.js"],
16 | "css": ["css/content.css"]
17 | }
18 | ],
19 | "browser_action": {
20 | "default_popup": "popup.html",
21 | "default_icon": {
22 | "16": "images/icon16.png",
23 | "24": "images/icon24.png",
24 | "32": "images/icon32.png"
25 | }
26 | },
27 | "commands": {
28 | "_execute_browser_action": {
29 | "suggested_key": {
30 | "default": "Ctrl+Shift+K",
31 | "windows": "Ctrl+Shift+K",
32 | "mac": "Command+Shift+K"
33 | }
34 | }
35 | },
36 | "icons": {
37 | "16": "images/icon16.png",
38 | "24": "images/icon24.png",
39 | "32": "images/icon32.png",
40 | "128": "images/icon128.png"
41 | },
42 | "manifest_version": 2
43 | }
44 |
--------------------------------------------------------------------------------
/chrome/src/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Using local storage helps us serve you better
12 |
13 |
Allow Why?
14 |
15 |
16 |
17 |
18 | Save
19 |
20 |
21 | Lookup
22 |
23 |
24 |
25 |
26 |
110 |
111 |
114 |
115 |
116 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/chrome/src/popup.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | let favIconUrl;
3 | let title;
4 | let pageUrl;
5 | let selection;
6 | let note;
7 | let page_dom;
8 | let hasStoragePermission = false;
9 | let searchQuery = '';
10 | let customFields = {};
11 |
12 | function delay(fn, ms) {
13 | let timer = 0
14 | return function(...args) {
15 | clearTimeout(timer)
16 | timer = setTimeout(fn.bind(this, ...args), ms || 0)
17 | }
18 | }
19 |
20 | function escapeHtml(unsafe) {
21 | return unsafe
22 | .replace(/&/g, "&")
23 | .replace(//g, ">")
25 | .replace(/"/g, """)
26 | .replace(/'/g, "'");
27 | }
28 |
29 | const serializeHTML = (node) => {
30 | if (typeof node.text === 'string') {
31 | return escapeHtml(node.text)
32 | }
33 |
34 | const children = node.children.map(n => serializeHTML(n)).join('')
35 |
36 | let url
37 | if (node.url) {
38 | if (node.url.startsWith('/spaces/') || node.url.startsWith('/activities/')) {
39 | url = `https://getrumin.com${node.url}`
40 | }
41 | else {
42 | url = node.url
43 | }
44 | }
45 |
46 | switch (node.type) {
47 | case 'quote':
48 | return `${children}
`
49 | case 'paragraph':
50 | return `${children}
`
51 | case 'link':
52 | return `${children} `
53 | case 'list-item':
54 | return `${children} `
55 | case 'heading-one':
56 | return `${children} `
57 | case 'heading-two':
58 | return `${children} `
59 | case 'heading-three':
60 | return `${children} `
61 | default:
62 | return children
63 | }
64 | }
65 |
66 | function activityData() {
67 | title = $('#captured_title_field').val();
68 | note = $('#captured_note_field').val();
69 |
70 | let params = {
71 | title: title,
72 | url: pageUrl,
73 | selection: selection,
74 | favicon_url: favIconUrl,
75 | note: note || '',
76 | // page_dom: page_dom,
77 | screenshot: $('#screenshot_img').attr('src'),
78 | custom_fields: customFields
79 | }
80 |
81 | if (window.selectedSpaces) {
82 | params.in_collections = window.selectedSpaces
83 | }
84 |
85 | return params
86 | }
87 |
88 | function buildActivity(activity) {
89 | let youtubeString = ''
90 | if (activity.url && activity.url.startsWith('https://www.youtube.com') && activity.custom_fields && activity.custom_fields.current_time) {
91 | youtubeString = `Video at ${activity.custom_fields.current_time}
`
92 | }
93 |
94 | return(`
95 |
96 |
99 |
100 | created ${ new Date(activity.created_at).toISOString().slice(0,10) } last updated ${ new Date(activity.updated_at).toISOString().slice(0,10) }
101 |
102 | ${ youtubeString }
103 |
104 |
105 |
106 | ${ activity.selection }
107 |
108 |
109 |
110 |
111 |
${ serializeHTML({ children: activity.json_body }) }
112 |
113 |
114 | `)
115 | }
116 |
117 | function buildSpace(space) {
118 | return(`
119 |
120 |
123 |
124 | created ${ new Date(space.created_at).toISOString().slice(0,10) } last updated ${ new Date(space.created_at).toISOString().slice(0,10) }
125 |
126 |
127 |
${ space.text_body.length > 500 ? space.text_body.slice(0, 500) + '...' : space.text_body }
128 |
129 |
130 | `)
131 | }
132 |
133 | function buildCustomField(name, value) {
134 | if (typeof value === 'object') {
135 | value = JSON.stringify(value, null, 2)
136 | }
137 |
138 | return(`
139 |
140 |
${ name }:
141 |
${ value }
142 |
143 | `)
144 | }
145 |
146 | function fetchMatchingPages() {
147 | $.ajax({
148 | url: `https://getrumin.com/api/v1/search/?url=${encodeURIComponent(encodeURIComponent(pageUrl))}`,
149 | method: 'GET',
150 | contentType: 'application/json',
151 | success: function(data) {
152 | if (data.length === 0) {
153 | $('#search_results').html('No matching results on this page
')
154 | return
155 | }
156 |
157 | const resultElements = data.map(obj => {
158 | // console.log('obj', obj.content_type, obj.title, obj)
159 |
160 | if (obj.content_type === 'Activity') {
161 | return buildActivity(obj)
162 | }
163 | else {
164 | return buildSpace(obj)
165 | }
166 | }).join('')
167 |
168 |
169 | $('#search_results').html(`Related to this page
${resultElements}`)
170 | },
171 | error: function(error) {
172 | console.log('API error', error);
173 | }
174 | });
175 | }
176 |
177 | function sendMsgToContentScript(msg) {
178 | chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
179 | chrome.tabs.sendMessage(tabs[0].id, msg, function(response) {})
180 | });
181 | }
182 |
183 | chrome.runtime.sendMessage({popupOpen: true});
184 |
185 | chrome.runtime.onMessage.addListener(function(req, sender, sendResponse) {
186 |
187 | if (hasStoragePermission) {
188 | chrome.storage.local.get(['noteValue', 'titleValue'], function(result) {
189 | if (result.titleValue) {
190 | title = result.titleValue
191 | $('#captured_title_field').val(result.titleValue)
192 | }
193 |
194 | if (result.noteValue) {
195 | $('#captured_note_field').val(result.noteValue)
196 | }
197 | })
198 | }
199 |
200 | if (req.pageContext) {
201 |
202 | favIconUrl = sender.tab.favIconUrl;
203 |
204 | // no saved title from local storage
205 | if (!title || (title && title.trim() === '')) {
206 | title = req.pageContext.titleOverride ? req.pageContext.titleOverride : sender.tab.title;
207 | title = title.slice(0, 300)
208 | }
209 |
210 | pageUrl = req.pageContext.urlOverride ? req.pageContext.urlOverride : sender.tab.url;
211 | selection = req.pageContext.selection;
212 | // page_dom = req.pageContext.page_dom;
213 | customFields = req.pageContext.customFields;
214 |
215 | // get it for the Lookup tab
216 | fetchMatchingPages()
217 |
218 | if (title.trim() !== '') {
219 | $('#captured_title_field').val(title.trim());
220 | }
221 | else {
222 | $('#captured_title_field').val(pageUrl);
223 | }
224 |
225 | if (selection.trim()) {
226 | $('#captured_selection').text(selection.slice(0, 300));
227 | }
228 | else {
229 | $('.captured-selection').hide()
230 | }
231 |
232 | if (Object.keys(customFields).length > 0) {
233 | $('#custom_fields_section').removeClass('hidden')
234 |
235 | const divider = '
'
236 |
237 | const customFieldElements = Object.keys(customFields).map(fieldName => {
238 |
239 | return buildCustomField(fieldName, customFields[fieldName])
240 | }).join(divider)
241 |
242 | $('#custom_fields').html(`
243 |
244 | ${ customFieldElements }
245 |
246 | `)
247 | }
248 |
249 | // Site-specific selection and actions
250 | if (pageUrl.startsWith('https://app.slack.com/client/')) {
251 | $('#add_slack_selection').removeClass('hidden')
252 | }
253 |
254 | if (pageUrl.includes('reddit.com/r/') && pageUrl.includes('comments')) {
255 | // console.log('showing reddit btn')
256 | $('#add_reddit_selection').removeClass('hidden')
257 | }
258 | }
259 |
260 | if (req.screenshotCaptured) {
261 | $('#screenshot_img').removeClass('hidden')
262 | $('#screenshot_img').attr('src', req.screenshotData)
263 |
264 | // Tesseract.recognize(
265 | // req.screenshotData,
266 | // 'eng',
267 | // { logger: m => console.log(m) }
268 | // ).then(({ data: { text } }) => {
269 | // console.log('recognized text from screenshot', text);
270 | // })
271 |
272 | // TODO - convert OCR here
273 | }
274 | });
275 |
276 | $(function() {
277 | var noteSelectionStart;
278 | var noteSelectionEnd;
279 |
280 | chrome.permissions.contains({
281 | permissions: ['storage']
282 | }, function(result) {
283 | if (result) {
284 | $('#permission_bar').hide()
285 | hasStoragePermission = true
286 |
287 | // notify content script
288 | sendMsgToContentScript({hasStoragePermission: true})
289 | }
290 | })
291 |
292 | $('#save_btn').click(function() {
293 | // disable button
294 | $('#save_btn').html('Saving...Do not close this
')
295 | $('#save_btn').removeClass()
296 |
297 |
298 | if (hasStoragePermission) {
299 | // chrome.storage.local.clear()
300 | chrome.storage.local.remove(['titleValue', 'noteValue'])
301 | sendMsgToContentScript({ cancelSelection: true })
302 | sendMsgToContentScript({clearSelection: true})
303 | }
304 |
305 | $.ajax({
306 | url: 'https://getrumin.com/api/v1/activities/', //'http://127.0.0.1:8000/api/v1/activities/',//'https://getrumin.com/api/v1/activities/',
307 | method: 'POST',
308 | contentType: 'application/json',
309 | data: JSON.stringify(activityData()),
310 | success: function(data) {
311 | $('.capture-container').html(`
312 | The content is successfully saved.
313 |
316 | `)
317 | },
318 | error: function(error) {
319 | console.log('API error', error);
320 | }
321 | });
322 | });
323 |
324 | $('#custom_fields_heading').click(function() {
325 | $('.custom-fields-content').toggleClass('hidden')
326 | $('#custom_fields_display_icon').toggleClass('hidden')
327 | })
328 |
329 | $('#screenshot_btn').click(function() {
330 | chrome.runtime.sendMessage({takeScreenshot: true});
331 | })
332 |
333 | // TODO - finish implementing, so that the user doesn't remove the selection
334 | // $('#captured_note_field').keydown(function(e) {
335 | // var cursorStart = e.target.selectionStart
336 | // var cursorEnd = e.target.selectionEnd
337 |
338 | // if (cursorEnd > cursorStart && e.key === '{') {
339 | // noteSelectionStart = cursorStart
340 | // noteSelectionEnd = cursorEnd
341 | // }
342 | // })
343 |
344 |
345 | $('#captured_title_field').change(function(e) {
346 | if (hasStoragePermission) {
347 | chrome.storage.local.set({titleValue: e.target.value}, function() { console.log('setting noteValue in storage') })
348 | }
349 | })
350 |
351 | $('#captured_note_field').change(function(e) {
352 | if (hasStoragePermission) {
353 | chrome.storage.local.set({noteValue: e.target.value}, function() {})
354 | }
355 | })
356 |
357 |
358 | $('#captured_note_field').keyup(function(e) {
359 | // console.log('selection start', e.target.selectionStart, 'selection end', e.target.selectionEnd)
360 |
361 | if (e.key === '{') {
362 | // this is wrong
363 | // console.log(noteSelectionStart, noteSelectionEnd)
364 |
365 | var cursorStart = e.target.selectionStart
366 | var cursorEnd = e.target.selectionEnd
367 | var value = e.target.value
368 |
369 |
370 | e.target.value = value.slice(0, cursorStart) + '}' + value.slice(cursorStart,)
371 | e.target.selectionEnd = cursorStart
372 | }
373 | })
374 |
375 | // fetch search results
376 | $('#search_box').keyup(delay(function(e) {
377 | searchQuery = e.target.value
378 | $('#search_results').html('fetching...
')
379 |
380 | $.ajax({
381 | url: `https://getrumin.com/api/v1/search?q=${searchQuery}&is_as=true/`,
382 | method: 'GET',
383 | contentType: 'application/json',
384 | success: function(data) {
385 | const resultsElements = data.results.map(obj => {
386 | if (obj.content_type === 'Activity') {
387 | return buildActivity(obj)
388 | }
389 | else {
390 | return buildSpace(obj)
391 | }
392 | }).join('')
393 |
394 | $('#search_results').html(`Results for ${searchQuery}
${resultsElements}`)
395 | },
396 | error: function(error) {
397 | console.log('API error', error);
398 | }
399 | })
400 | }, 500))
401 |
402 | // switching tabs
403 | $('#lookup_tab_btn').click(function(e) {
404 | $('#save_tab_btn').removeClass('active')
405 | $('#lookup_tab_btn').addClass('active')
406 |
407 | $('#save_tab').addClass('hidden')
408 | $('#lookup_tab').removeClass('hidden')
409 | })
410 |
411 | $('#save_tab_btn').click(function(e) {
412 | $('#lookup_tab_btn').removeClass('active')
413 | $('#save_tab_btn').addClass('active')
414 |
415 | $('#lookup_tab').addClass('hidden')
416 | $('#save_tab').removeClass('hidden')
417 | })
418 |
419 |
420 | // custom selection for Slack
421 | $('#add_slack_selection').click(function() {
422 | sendMsgToContentScript({addSelection: true})
423 |
424 | $('#add_slack_selection').addClass('hidden')
425 | $('#cancel_slack_selection').removeClass('hidden')
426 | })
427 |
428 | $('#cancel_slack_selection').click(function() {
429 | sendMsgToContentScript({ cancelSelection: true })
430 |
431 | $('#cancel_slack_selection').addClass('hidden')
432 | $('#add_slack_selection').removeClass('hidden')
433 | })
434 |
435 |
436 | // custom selection for Reddit
437 | $('#add_reddit_selection').click(function() {
438 | sendMsgToContentScript({addSelection: true})
439 |
440 | $('#add_reddit_selection').addClass('hidden')
441 | $('#cancel_reddit_selection').removeClass('hidden')
442 | })
443 |
444 | $('#cancel_reddit_selection').click(function() {
445 | sendMsgToContentScript({ cancelSelection: true })
446 |
447 | $('#cancel_reddit_selection').addClass('hidden')
448 | $('#add_reddit_selection').removeClass('hidden')
449 | })
450 |
451 |
452 |
453 | // $(document).ready(function () {
454 | // $("#add_to_field").tokenInput("http://127.0.0.1:8000/search", {
455 | // propertyToSearch: 'title'
456 | // });
457 | // });
458 |
459 | // requesting additional permission for storage
460 | $('#req_storage_permission').click(function(e) {
461 | chrome.permissions.request({
462 | permissions: ['storage']
463 | }, function(granted) {
464 | if (granted) {
465 | // console.log('storage permission granted')
466 | $('#permission_bar').html('Please re-open this extension')
467 | }
468 | else {
469 | console.log('storage permission not granted')
470 | }
471 | })
472 | })
473 | })
474 | })();
475 |
--------------------------------------------------------------------------------
/chrome/src/vendor/tesseract.min.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Tesseract=t():e.Tesseract=t()}(window,(function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=7)}([function(e,t){e.exports=function(e,t){return"".concat(e,"-").concat(t,"-").concat(Math.random().toString(16).slice(3,8))}},function(e,t){var r=this,n=!1;t.logging=n,t.setLogging=function(e){n=e},t.log=function(){for(var e=arguments.length,t=new Array(e),o=0;o1)for(var r=1;r=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}var s=r(11),f=r(14),l=r(4),p=r(1).log,d=r(0),h=r(15).defaultOEM,y=r(16),g=y.defaultOptions,b=y.spawnWorker,v=y.terminateWorker,m=y.onMessage,w=y.loadImage,O=y.send,j=0;e.exports=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=d("Worker",j),r=s(a({},g,{},e)),n=r.logger,i=r.errorHandler,c=u(r,["logger","errorHandler"]),y={},x={},k=b(c);j+=1;var P=function(e,t){y[e]=t},S=function(e,t){x[e]=t},E=function(e){var r=e.id,n=e.action,o=e.payload;return new Promise((function(e,i){p("[".concat(t,"]: Start ").concat(r,", action=").concat(n)),P(n,e),S(n,i),O(k,{workerId:t,jobId:r,action:n,payload:o})}))},L=function(e){return E(l({id:e,action:"load",payload:{options:c}}))},T=function(e,t,r){return E(l({id:r,action:"FS",payload:{method:"writeFile",args:[e,t]}}))},R=function(e,t){return E(l({id:t,action:"FS",payload:{method:"readFile",args:[e,{encoding:"utf8"}]}}))},_=function(e,t){return E(l({id:t,action:"FS",payload:{method:"unlink",args:[e]}}))},A=function(e,t,r){return E(l({id:r,action:"FS",payload:{method:e,args:t}}))},D=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"eng",t=arguments.length>1?arguments[1]:void 0;return E(l({id:t,action:"loadLanguage",payload:{langs:e,options:c}}))},N=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"eng",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:h,r=arguments.length>2?arguments[2]:void 0;return E(l({id:r,action:"initialize",payload:{langs:e,oem:t}}))},F=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1?arguments[1]:void 0;return E(l({id:t,action:"setParameters",payload:{params:e}}))},I=function(){var e=o(regeneratorRuntime.mark((function e(t){var r,n,o=arguments;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return r=o.length>1&&void 0!==o[1]?o[1]:{},n=o.length>2?o[2]:void 0,e.t0=E,e.t1=l,e.t2=n,e.next=7,w(t);case 7:return e.t3=e.sent,e.t4=r,e.t5={image:e.t3,options:e.t4},e.t6={id:e.t2,action:"recognize",payload:e.t5},e.t7=(0,e.t1)(e.t6),e.abrupt("return",(0,e.t0)(e.t7));case 13:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}(),M=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"Tesseract OCR Result",t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],r=arguments.length>2?arguments[2]:void 0;return E(l({id:r,action:"getPDF",payload:{title:e,textonly:t}}))},C=function(){var e=o(regeneratorRuntime.mark((function e(t,r){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.t0=E,e.t1=l,e.t2=r,e.next=5,w(t);case 5:return e.t3=e.sent,e.t4={image:e.t3},e.t5={id:e.t2,action:"detect",payload:e.t4},e.t6=(0,e.t1)(e.t5),e.abrupt("return",(0,e.t0)(e.t6));case 10:case"end":return e.stop()}}),e)})));return function(t,r){return e.apply(this,arguments)}}(),z=function(){var e=o(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return null!==k&&(v(k),k=null),e.abrupt("return",Promise.resolve());case 2:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}();return m(k,(function(e){var t=e.workerId,r=e.jobId,o=e.status,c=e.action,u=e.data;if("resolve"===o){p("[".concat(t,"]: Complete ").concat(r));var s=u;"recognize"===c?s=f(u):"getPDF"===c&&(s=Array.from(a({},u,{length:Object.keys(u).length}))),y[c]({jobId:r,data:s})}else if("reject"===o){if(x[c](u),!i)throw Error(u);i(u)}else"progress"===o&&n(u)})),{id:t,worker:k,setResolve:P,setReject:S,load:L,writeText:T,readText:R,removeFile:_,FS:A,loadLanguage:D,initialize:N,setParameters:F,recognize:I,getPDF:M,detect:C,terminate:z}}},function(e,t){e.exports={TESSERACT_ONLY:0,LSTM_ONLY:1,TESSERACT_LSTM_COMBINED:2,DEFAULT:3}},function(e,t,r){function n(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function o(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}r(8);var i=r(10),a=r(5),c=r(25),u=r(6),s=r(26),f=r(1).setLogging;e.exports=function(e){for(var t=1;t=0;--i){var a=this.tryEntries[i],c=a.completion;if("root"===a.tryLoc)return n("end");if(a.tryLoc<=this.prev){var u=o.call(a,"catchLoc"),s=o.call(a,"finallyLoc");if(u&&s){if(this.prev=0;--r){var n=this.tryEntries[r];if(n.tryLoc<=this.prev&&o.call(n,"finallyLoc")&&this.prev=0;--t){var r=this.tryEntries[t];if(r.finallyLoc===e)return this.complete(r.completion,r.afterLoc),E(r),y}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var r=this.tryEntries[t];if(r.tryLoc===e){var n=r.completion;if("throw"===n.type){var o=n.arg;E(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,n){return this.delegate={iterator:T(e),resultName:t,nextLoc:n},"next"===this.method&&(this.arg=r),y}},e}("object"===t(e)?e.exports:{});try{regeneratorRuntime=r}catch(e){Function("r","regeneratorRuntime = r")(r)}}).call(this,r(9)(e))},function(e,t){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}},function(e,t,r){var n=this;function o(e){return function(e){if(Array.isArray(e)){for(var t=0,r=new Array(e.length);t1?n-1:0),i=1;i=0)}}).call(this,r(2))},function(e,t){function r(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function n(e){for(var t=1;t!(e.length<2||e.match(/^\d/))).map(e=>e.toLowerCase()),this._termFrequencies=null}_calculateTermFrequencies(){this._termFrequencies=new Map,this._words.forEach(e=>{this._termFrequencies.has(e)?this._termFrequencies.set(e,this._termFrequencies.get(e)+1):this._termFrequencies.set(e,1)})}getTermFrequency(e){return this._termFrequencies||this._calculateTermFrequencies(),this._termFrequencies.has(e)?this._termFrequencies.get(e):null}getText(){return this._text}getLength(){return this._words.length}getUniqueTerms(){return this._termFrequencies||this._calculateTermFrequencies(),Array.from(this._termFrequencies.keys())}}const s=["me","my","myself","we","our","ours","ourselves","you","your","yours","yourself","yourselves","he","him","his","himself","she","her","hers","herself","it","its","itself","they","them","their","theirs","themselves","what","which","who","whom","this","that","these","those","am","is","are","was","were","be","been","being","have","has","had","having","do","does","did","doing","an","the","and","but","if","or","because","as","until","while","of","at","by","for","with","about","against","between","into","through","during","before","after","above","below","to","from","up","down","in","out","on","off","over","under","again","further","then","once","here","there","when","where","why","how","all","any","both","each","few","more","most","other","some","such","no","nor","not","only","own","same","so","than","too","very","can","will","just","don","could","should","would","now","ll","re","ve","aren","couldn","didn","doesn","hadn","hasn","haven","isn","mustn","needn","shouldn","wasn","weren","won","wouldn"];class o{constructor(e=!0,t=[]){const r=e?t.concat(s):t;this._stopwords=new Map(r.map(e=>[e,!0]))}includes(e){return this._stopwords.has(e)}getStopwordList(){return Array.from(this._stopwords.keys())}}class i{constructor(e,t,r=!0,s=[],i=2,c=.75){this._stopwords=new o(r,s),this._K1=i,this._b=c,this._documents=new Map;for(let r=0;r!this._stopwords.includes(e)).forEach(e=>{if(this._collectionFrequencies.has(e)){const t=this._collectionFrequencies.get(e);this._collectionFrequencies.set(e,t+1)}else this._collectionFrequencies.set(e,1)})}getTerms(){return this._collectionFrequencies||this._calculateCollectionFrequencies(),Array.from(this._collectionFrequencies.keys())}getCollectionFrequency(e){return this._collectionFrequencies||this._calculateCollectionFrequencies(),this._collectionFrequencies.has(e)?this._collectionFrequencies.get(e):null}getDocument(e){return this._documents.get(e)}getDocumentIdentifiers(){return Array.from(this._documents.keys())}getCommonTerms(e,t,r=10){const n=this.getDocumentVector(e),s=this.getDocumentVector(t);return Array.from(n.entries()).map(([e,t])=>[e,t*s.get(e)]).filter(e=>e[1]>0).sort((e,t)=>t[1]-e[1]).slice(0,r)}_calculateCollectionFrequencyWeights(){this._collectionFrequencies||this._calculateCollectionFrequencies(),this._collectionFrequencyWeights=new Map;const e=this._documents.size;for(const[t,r]of this._collectionFrequencies.entries())this._collectionFrequencyWeights.set(t,Math.log(e+1)-Math.log(r))}getCollectionFrequencyWeight(e){return this._collectionFrequencyWeights||this._calculateCollectionFrequencyWeights(),this._collectionFrequencyWeights.has(e)?this._collectionFrequencyWeights.get(e):null}_calculateDocumentVectors(){this._collectionFrequencyWeights||this._calculateCollectionFrequencyWeights(),this._documentVectors=new Map;const e=this._K1,t=this._b,r=Array.from(this._documents.values()).map(e=>e.getLength()).reduce((e,t)=>e+t,0)/this._documents.size;for(const[n,s]of this._documents){const o=new Map,i=s.getLength()/r;for(const[r,n]of this._collectionFrequencyWeights.entries()){const c=s.getTermFrequency(r),u=c?n*c*(e+1)/(e*(1-t+t*i)+c):0;o.set(r,u)}this._documentVectors.set(n,o)}}getDocumentVector(e){return this._documentVectors||this._calculateDocumentVectors(),this._documentVectors.get(e)}getTopTermsForDocument(e,t=30){const r=this.getDocumentVector(e);return r?Array.from(r.entries()).filter(e=>e[1]>0).sort((e,t)=>t[1]-e[1]).slice(0,t):[]}getResultsForQuery(e){if(!e||"string"!=typeof e||0===e.length)return[];const t=new n(e).getUniqueTerms();return this.getDocumentIdentifiers().map(e=>{const r=this.getDocumentVector(e);let n=0;return t.forEach(e=>{const t=r.get(e);t&&(n+=t)}),[e,n]}).filter(e=>e[1]>0).sort((e,t)=>t[1]-e[1])}getStopwords(){return this._stopwords}}class c{constructor(e){this._corpus=e,this._distanceMatrix=null}static cosineSimilarity(e,t){const r=Array.from(e.values()),n=Array.from(t.values());let s=0,o=0,i=0;const c=Math.min(r.length,n.length);for(let e=0;ethis._corpus.getDocumentVector(e)),r=new Array(t.length).fill(null).map(()=>new Array(t.length));for(let e=0;e {
13 | // })
14 |
15 | // chrome.permissions.contains({
16 | // permissions: ['storage']
17 | // }, function(result) {
18 | // if (result) {
19 | // // notify content script
20 | // // sendMsgToContentScript({hasStoragePermission: true})
21 | // }
22 | // })
23 |
24 |
25 | chrome.runtime.onMessage.addListener(function(message, sender, sendResponse){
26 | if (message.popupOpen) {
27 | // console.log('popup is open');
28 |
29 | sendMsgToContentScript({"message": "clicked_browser_action"})
30 | }
31 |
32 | if (message.takeScreenshot) {
33 | // console.log('TODO - take a screenshot')
34 |
35 | chrome.tabs.captureVisibleTab(null, {
36 | format : "png"
37 | }, function(data) {
38 | // screenshot.data = data;
39 | // console.log('screenshot data', data)
40 |
41 | chrome.runtime.sendMessage({screenshotCaptured: true, screenshotData: data});
42 | });
43 | }
44 | });
45 |
46 |
--------------------------------------------------------------------------------
/firefox/src/content.js:
--------------------------------------------------------------------------------
1 | const MOUSE_VISITED_CLASSNAME = 'crx_mouse_visited';
2 | let prevDOM = null
3 | let selectingDOM = false
4 | let selectedElements = []
5 | let hasStoragePermission = false
6 |
7 | // function isElementVisible(el) {
8 | // const o = new IntersectionObserver(([entry]) => {
9 | // return entry.intersectionRatio === 1;
10 | // });
11 | // o.observe(el)
12 | // }
13 |
14 | const getTopTermsInDoc = () => {
15 | // const corpus = new TinyTFIDF.Corpus(['doc1'], [$('body').text()])
16 | // return corpus.getTopTermsForDocument('doc1').slice(0, 15)
17 | }
18 |
19 | const initSelectedElements = () => {
20 | // get fetch existing selectedElements
21 | chrome.storage.local.get(['selectedElements'], function(result) {
22 | console.log('result.selectedElements', result.selectedElements)
23 |
24 | selectedElements = result.selectedElements || []
25 | })
26 | }
27 |
28 | const clearSelection = () => {
29 | unstyleSelectedElements()
30 |
31 | selectedElements = []
32 | $(document).unbind('mousemove')
33 | }
34 |
35 | const unstyleSelectedElements = () => {
36 | if (selectedElements.length > 0) {
37 | selectedElements.forEach(el => {
38 | // console.log('el in selectedElements', el)
39 | el.classList.remove(MOUSE_VISITED_CLASSNAME);
40 | })
41 | }
42 | }
43 |
44 | const styleSelectedElements = () => {
45 | if (selectedElements.length > 0) {
46 | selectedElements.forEach(el => {
47 | // console.log('el in selectedElements', el)
48 | el.classList.add(MOUSE_VISITED_CLASSNAME);
49 | })
50 | }
51 | }
52 |
53 | // Extract selected fields for Slack
54 | const slackFields = () => {
55 | const messages = selectedElements.map(el => {
56 | let selector = $(el)
57 | if (!el.classList.contains('c-virtual_list__item') && !el.classList.contains('c-message_kit__gutter')) {
58 | selector = $(el).closest('.c-virtual_list__item')
59 | }
60 |
61 | if (selector.length === 0) return null
62 |
63 | const sender = selector.find('.c-message__sender').text()
64 | const link = selector.find('a.c-link.c-timestamp')[0]['href']
65 | const text = selector.find('.p-rich_text_block').text()
66 |
67 | return({
68 | sender_name: sender,
69 | message_link: link,
70 | message_text: text
71 | })
72 | })
73 |
74 | return { messages: messages.filter((m) => m) }
75 | }
76 |
77 | const redditSelectedFields = () => {
78 | const comments = selectedElements.map(el => {
79 | let selector = $(el)
80 |
81 | let comment = {}
82 |
83 | const commentBody = $(el).find('div[data-test-id="comment"]')
84 | if (commentBody.length === 0) return null
85 | comment.body = commentBody.text()
86 |
87 | const timestamp = $(el).find('[id^=CommentTopMeta--Created--]')
88 | if (timestamp.length > 0) {
89 | comment.url = timestamp[0]['href']
90 | }
91 |
92 | return comment
93 | })
94 |
95 | return { comments: comments.filter(c => c) }
96 | }
97 |
98 | // parse '(hh):mm:ss' string into seconds
99 | const timeStringToSeconds = (str) => {
100 | return str.split(':').reverse().reduce((prev, curr, i) => prev + curr*Math.pow(60, i), 0)
101 | }
102 |
103 | const youtubeFields = () => {
104 | const currentTime = $('.ytp-time-current')[0].innerText
105 | const channelName = $('.ytd-channel-name yt-formatted-string')[0].innerText
106 | const channelUrl = $('.ytd-channel-name yt-formatted-string a')[0]['href']
107 | const publishedDate = $('#date yt-formatted-string.ytd-video-primary-info-renderer')[0].innerText.replace('Premiered ', '') // FIXME - this can break for other languages
108 |
109 | return ({
110 | current_time: currentTime,
111 | channel_name: channelName,
112 | channel_url: channelUrl,
113 | published_date: publishedDate
114 | })
115 | }
116 |
117 | const linkedinLearningFields = () => {
118 | const classTitle = $('.classroom-nav__details h1').text()
119 | const currentTime = $('.vjs-current-time').text()
120 | const teacherName = $('.authors-entity__name-text').text()
121 | const teacherUrl = $('a.course-author-entity__lockup').attr('href')
122 | const sessionTitle = $('.classroom-toc-item--selected').text()
123 | const sessionTranscript = $('.transcripts-component__sections').text().trim()
124 |
125 | return({
126 | class_title: classTitle.trim(),
127 | current_time: currentTime,
128 | teacher_name: teacherName.trim(),
129 | teacher_url: teacherUrl,
130 | session_title: sessionTitle.trim(),
131 | session_transcript: sessionTranscript
132 | })
133 | }
134 |
135 | const skillShareFields = () => {
136 | const classTitle = $('.class-details-header-name').text()
137 | const currentTime = $('.vjs-current-time-display').text()
138 | const teacherName = $('.class-details-header-teacher-link').text()
139 | const teacherUrl = $('.class-details-header-teacher-link').attr('href')
140 | const sessionTitle = $('.session-item.active .session-item-title').text()
141 |
142 | return({
143 | class_title: classTitle.trim(),
144 | current_time: currentTime,
145 | teacher_name: teacherName.trim(),
146 | teacher_url: teacherUrl,
147 | session_title: sessionTitle.trim()
148 | })
149 | }
150 |
151 | const netflixFields = () => {
152 | const videoTitle = $('.video-title h4').text()
153 | const episodeTitle = $('.video-title span').text()
154 | const currentTime = $('.scrubber-head').attr('aria-valuetext')
155 |
156 | return({
157 | video_title: videoTitle,
158 | episode_title: episodeTitle,
159 | current_time: currentTime
160 | })
161 | }
162 |
163 | const edxLectureFields = () => {
164 | const provider = $('.course-header .provider').text()
165 | const courseCode = $('.course-header .course-number').text()
166 | const courseName = $('.course-header .course-name').text()
167 |
168 | const videoUrl = $('.video-sources').length > 0 ? $('.video-sources').get(0)['href'] : null
169 | const slidesUrl = $('a[href$=pdf]').length > 0 ? $('a[href$=pdf]').get(0)['href'] : null
170 |
171 | const vidTime = $('.vidtime').length > 0 ? $('.vidtime').text().split('/')[0].trim() : null
172 |
173 | return({
174 | course_provider: provider,
175 | course_code: courseCode,
176 | course_name: courseName,
177 | video_url: videoUrl,
178 | slides_url: slidesUrl,
179 | current_time: vidTime
180 | })
181 | }
182 |
183 | const isLinkedinLearningPage = () => {
184 | return location.href.startsWith('https://www.linkedin.com/learning/') && $('.classroom-layout__content').length > 0
185 | }
186 |
187 | const isSkillshareVideoPage = () => {
188 | return location.href.startsWith('https://www.skillshare.com/classes/')
189 | }
190 |
191 | const isNetflixVideoPage = () => {
192 | return location.href.startsWith('https://www.netflix.com/watch/')
193 | }
194 |
195 | const isYoutubeVideoPage = () => {
196 | return location.href.startsWith('https://www.youtube.com/watch?v=')
197 | }
198 |
199 | const isKindleCloudReaderPage = () => {
200 | return location.href.startsWith('https://read.amazon.com') && !location.href.includes('notebook')
201 | }
202 |
203 | const isSlackPage = () => {
204 | return location.href.startsWith('https://app.slack.com/client/')
205 | }
206 |
207 | const isRedditPage = () => {
208 | return location.href.includes('reddit.com/r/') && location.href.includes('comments')
209 | }
210 |
211 | const isEdxLecturePage = () => {
212 | return location.href.startsWith('https://courses.edx.org/courses/') && location.href.includes('courseware')
213 | }
214 |
215 | const parseSelectedElements = () => {
216 | if (isSlackPage()) {
217 | return slackFields()
218 | }
219 |
220 | if (isRedditPage()) {
221 | return redditSelectedFields()
222 | }
223 | }
224 |
225 | chrome.runtime.onMessage.addListener(
226 | function(request, sender, sendResponse) {
227 | // console.log(request.message);
228 |
229 | // if (request.disconnect === true) {
230 | // if (hasStoragePermission) {
231 | // chrome.storage.local.clear()
232 | // chrome.storage.local.remove('selectedElements')
233 | // }
234 | // }
235 |
236 | if (request.hasStoragePermission === true) {
237 | hasStoragePermission = true
238 | sendResponse({ack: true})
239 | }
240 |
241 | if (request.clearSelection === true) {
242 | clearSelection()
243 | sendResponse({ack: true})
244 | }
245 |
246 | if (request.message === "clicked_browser_action") {
247 | const sel = window.getSelection()
248 | const selectionText = sel.toString()
249 |
250 | // console.log('selectionText', selectionText)
251 |
252 | let titleOverride = null
253 | let urlOverride = null
254 | let customFields = {}
255 | let closestId = ''
256 |
257 | if (sel && sel.rangeCount > 0) {
258 | const selectionEl = sel.getRangeAt(0).startContainer.parentNode
259 |
260 | if (selectionEl.id) {
261 | closestId = selectionEl.id
262 | }
263 | else {
264 | const prevSibling = $(selectionEl).prev('[id]')
265 | const prevParent = $(selectionEl).closest('[id]')
266 |
267 | if (prevSibling.length > 0) {
268 | closestId = prevSibling[0].id
269 | }
270 | else if (prevParent.length > 0) {
271 | closestId = prevParent[0].id
272 | }
273 | }
274 |
275 | if (closestId) {
276 | urlOverride = `${location.href}#${closestId}`
277 | }
278 | }
279 |
280 | // Index the DOM
281 | getTopTermsInDoc()
282 |
283 |
284 | // Youtube video
285 | if (isYoutubeVideoPage()) {
286 | const fields = youtubeFields()
287 | Object.assign(customFields, fields)
288 |
289 | // TODO - replace an existing t parameter
290 | if (location.search.includes('t=')) {
291 | urlOverride = `${location.origin}${location.pathname}${location.search.replace(/t=[0-9]+s/, 't=' + timeStringToSeconds(fields.current_time) + 's')}`
292 | }
293 | else {
294 | urlOverride = `${location.href}&t=${timeStringToSeconds(fields.current_time)}`
295 | }
296 | }
297 |
298 | // Netflix Video
299 | if (isNetflixVideoPage()) {
300 | const fields = netflixFields()
301 | Object.assign(customFields, fields)
302 | }
303 |
304 | // Skillshare video
305 | if (isSkillshareVideoPage()) {
306 | const fields = skillShareFields()
307 | Object.assign(customFields, fields)
308 | }
309 |
310 | // Linkedin Learning
311 | if (isLinkedinLearningPage()) {
312 | const fields = linkedinLearningFields()
313 | Object.assign(customFields, fields)
314 | }
315 |
316 |
317 | // Kindle Cloud reader
318 | if (isKindleCloudReaderPage()) {
319 | // TODO
320 | }
321 |
322 | // Page title
323 | if ($('h1').length > 0) {
324 | customFields.page_title = $('h1')[0].textContent.trim()
325 | }
326 |
327 | // edX
328 | if (isEdxLecturePage()) {
329 | Object.assign(customFields, edxLectureFields())
330 | }
331 |
332 | // if (isMedium)
333 | // Kindle Notes and Highlights: https://read.amazon.com/notebook
334 | // Go to the first book
335 | // $('.kp-notebook-library-each-book a.a-link-normal')[0].click()
336 | // document in the first kindle iframe
337 | // $('#KindleReaderIFrame').get(0).contentDocument
338 |
339 |
340 | if (selectedElements.length > 0) {
341 |
342 | const selectedFields = parseSelectedElements()
343 |
344 | customFields = {
345 | ...customFields,
346 | ...selectedFields
347 | }
348 | }
349 |
350 | const pageContext = {
351 | pageContext: {
352 | urlOverride: urlOverride,
353 | titleOverride: titleOverride,
354 | selection: selectionText,
355 | // closestId: closestId,
356 | // page_dom: document.documentElement.outerHTML,
357 | customFields: customFields
358 | }
359 | };
360 |
361 | // console.log('sending pageContext', pageContext, window.getSelection().toString())
362 |
363 | chrome.runtime.sendMessage(pageContext, function(response) {
364 | });
365 | }
366 |
367 | if (request.addSelection) {
368 | selectingDOM = true
369 |
370 | console.log('in addSelection in content script')
371 | sendResponse({ack: true})
372 |
373 | $(document).mousemove(function(e) {
374 | var target = e.target;
375 |
376 | // console.log('target', target)
377 | const whiteListedNodes = ['DIV', 'IMG', 'A', 'P', 'SPAN', 'H1', 'H2', 'H3', 'H4', 'H5']
378 |
379 | // if (whiteListedClasses && target.class)
380 | // TODO - perhaps we should restrict what elements can be added?
381 | // do it by source
382 |
383 | if (whiteListedNodes.includes(target.nodeName)) {
384 | // For NPE checking, we check safely. We need to remove the class name
385 | // Since we will be styling the new one after.
386 | if (prevDOM != null && !selectedElements.includes(prevDOM)) {
387 | prevDOM.classList.remove(MOUSE_VISITED_CLASSNAME);
388 | }
389 | // Add a visited class name to the element. So we can style it.
390 | target.classList.add(MOUSE_VISITED_CLASSNAME);
391 | // The current element is now the previous. So we can remove the class
392 | // during the next iteration.
393 | prevDOM = target;
394 | }
395 | })
396 | }
397 |
398 | if (request.cancelSelection) {
399 | selectingDOM = false
400 | $(document).unbind('mousemove')
401 | }
402 | }
403 | );
404 |
405 |
406 | $(function() {
407 | $(document).click(function(e) {
408 | if (selectingDOM) {
409 | if (hasStoragePermission) {
410 | let element = e.target
411 |
412 | if (selectedElements && selectedElements.includes(element.outerHTML)) return
413 |
414 | selectedElements.push(element)
415 | }
416 | }
417 | })
418 | })
419 |
--------------------------------------------------------------------------------
/firefox/src/css/content.css:
--------------------------------------------------------------------------------
1 | .crx_mouse_visited {
2 | background-color: #cbe6ff !important;
3 | outline: 1px solid #5166bb !important;
4 | }
5 |
--------------------------------------------------------------------------------
/firefox/src/css/popup.css:
--------------------------------------------------------------------------------
1 | body {
2 | min-width: 250px;
3 | position: relative;
4 | }
5 |
6 | button {
7 | height: 30px;
8 | width: 30px;
9 | outline: none;
10 | }
11 |
12 | .flex {
13 | display: flex;
14 | }
15 |
16 |
17 | ::-webkit-scrollbar {
18 | width: 0px;
19 | }
20 |
21 | ::-webkit-scrollbar-corner {
22 | background: transparent;
23 | }
24 |
25 | ::-webkit-scrollbar-thumb {
26 | background-color: rgba(0,0,0,.2);
27 | background-clip: padding-box;
28 | border: solid transparent;
29 | border-width: 1px 1px 1px 6px;
30 | min-height: 28px;
31 | padding: 100px 0 0;
32 | }
33 |
34 | .hidden {
35 | display: none !important;
36 | }
37 |
38 | .section-heading {
39 | cursor: pointer;
40 | }
41 |
42 | .gray-text {
43 | color: #777;
44 | }
45 |
46 | .section-heading .text {
47 | font-weight: 600;
48 | font-size: 0.7em;
49 | text-transform: uppercase;
50 | margin-bottom: 8px;
51 | }
52 |
53 | .btn.btn-primary {
54 | background-color: #0f8bff;
55 | }
56 |
57 | .inline-block {
58 | display: inline-block;
59 | }
60 |
61 | i.small-icon {
62 | font-size: 0.8em;
63 | }
64 |
65 | .field-label {
66 | font-size: 0.85em;
67 | color: #777;
68 | margin-bottom: 4px;
69 | }
70 |
71 | #screenshot_img {
72 | width: 100%;
73 | }
74 |
75 | .capture-form-container {
76 | padding-bottom: 2em;
77 | overflow-y: scroll;
78 | overflow: visible;
79 | }
80 |
81 | /*.capture-form-container::-webkit-scrollbar {
82 | width: 12px;
83 | }*/
84 |
85 | .save-btn-container {
86 | position: fixed;
87 | width: 100%;
88 | left: 0;
89 | right: 0;
90 | bottom: 0;
91 | padding: 1em;
92 | background-color: white;
93 | }
94 |
95 | .permission-bar {
96 | font-size: 0.9em;
97 | text-align: center;
98 | background-color: #CBE6FF;
99 | padding: 0.5em 0;
100 | }
101 |
102 | .inline-block {
103 | display: inline-block;
104 | }
105 |
106 | .btn-icon {
107 | width: 14px;
108 | margin-top: -4px;
109 | }
110 |
111 | .tabs {
112 | white-space: nowrap;
113 | border-bottom: 1px solid #eee;
114 | }
115 |
116 | .tabs .tab {
117 | display: inline-block;
118 | width: 50%;
119 | text-align: center;
120 | box-sizing: border-box;
121 | padding: 0.5em 0;
122 | color: #777;
123 | cursor: pointer;
124 | }
125 |
126 | .tabs .tab:hover {
127 | background-color: #f0f0f0;
128 | }
129 |
130 | .tab.active {
131 | border-bottom: 2px solid #0f8bff;
132 | }
133 |
134 | .section-container,
135 | .capture-container {
136 | width: 350px;
137 | }
138 |
139 | .capture-container {
140 | padding: 1em;
141 | }
142 |
143 | .captured-selection {
144 | padding: 1em;
145 | font-size: 0.8em;
146 | background-color: #f9f9f9;
147 | margin-bottom: 1em;
148 |
149 | max-height: 200px;
150 | overflow-y: scroll;
151 | }
152 |
153 | .captured-selection::-webkit-scrollbar {
154 | width: 12px;
155 | }
156 |
157 | .capture-container textarea {
158 | resize: none;
159 | min-height: 5em;
160 | overflow: hidden;
161 | }
162 |
163 | textarea.note-field {
164 | min-height: 7em;
165 | }
166 |
167 | .search-box-container {
168 | padding: 1em;
169 | border-bottom: 1px solid #eee;
170 | }
171 |
172 | .search-box {
173 | width: 100%;
174 | border: none;
175 | border-bottom: 1px solid #eee;
176 | /*margin: 1em;*/
177 | }
178 |
179 | .search-box:focus {
180 | outline: none;
181 | }
182 |
183 | .search-results {
184 | min-height: 100px;
185 | background-color: #f0f0f0;
186 | padding: 1em;
187 | max-height: 550px;
188 | overflow-y: scroll;
189 | }
190 |
191 | .search-results .list-item {
192 | background-color: white;
193 | padding: 1em;
194 | border-radius: 12px;
195 | margin-bottom: 1em;
196 | -webkit-box-shadow: 0px 5px 4px 0px rgba(171,171,171,1);
197 | -moz-box-shadow: 0px 5px 4px 0px rgba(171,171,171,1);
198 | box-shadow: 0px 5px 4px 0px rgba(171,171,171,1);
199 | overflow-wrap: break-word;
200 | }
201 |
202 | .list-item .title {
203 | margin-bottom: 0.5em;
204 | }
205 |
206 | .list-item .title a {
207 | font-size: 1.15em;
208 | }
209 |
210 | .list-item .text-body {
211 | color: #666;
212 | font-size: 0.9em;
213 | }
214 |
215 | .search-results .heading {
216 | font-weight: 600;
217 | font-size: 0.7em;
218 | text-transform: uppercase;
219 | margin-bottom: 8px;
220 | }
221 |
222 | .list-item .timestamp {
223 | font-size: 0.8em;
224 | margin-bottom: 0.5em;
225 | color: #888;
226 | }
227 |
228 | #custom_fields {
229 | /*overflow-wrap: break-word;*/
230 | color: #666;
231 | max-height: 200px;
232 | overflow-y: scroll;
233 | }
234 |
235 | #custom_fields::-webkit-scrollbar {
236 | width: 14px;
237 | }
238 |
239 | #custom_fields .prop-value {
240 | overflow-wrap: break-word;
241 | white-space: pre-wrap;
242 | font-size: 0.9em
243 | }
244 |
245 | #custom_fields .prop-name {
246 | font-size: 0.9em;
247 | font-weight: 550;
248 | }
249 |
250 | #custom_fields .divider {
251 | height: 1px;
252 | background-color: #ddd;
253 | margin: 1em;
254 | }
255 |
256 | .collections-dropdown-container {
257 | position: relative;
258 | }
259 |
260 | .collections-dropdown {
261 | /*padding: 0.75em;*/
262 | position: absolute;
263 | width: 100%;
264 | border: 1px solid #ccc;
265 | border-radius: 8px;
266 | background-color: white;
267 | z-index: 2;
268 |
269 | border-top-left-radius: 0;
270 | border-top-right-radius: 0;
271 |
272 | -webkit-box-shadow: 0px 5px 4px 0px rgba(171,171,171,1);
273 | -moz-box-shadow: 0px 5px 4px 0px rgba(171,171,171,1);
274 | box-shadow: 0px 5px 4px 0px rgba(171,171,171,1);
275 | }
276 |
277 | .as-msg {
278 | padding: 0.5em 0.75em;
279 | font-size: 0.9em;
280 | }
281 |
282 | .as-result {
283 | padding: 0.5em 0.75em;
284 | cursor: pointer;
285 | color: #666;
286 | font-size: 0.9em;
287 | }
288 |
289 | .as-result:hover {
290 | background-color: #eff7ff;
291 | outline: none;
292 | }
293 |
294 | .collections-selected {
295 | display: flex;
296 | flex-shrink: 0;
297 | max-height: 240px;
298 |
299 | flex-wrap: wrap;
300 | align-items: flex-start;
301 |
302 | border: 1px solid #ced4da;
303 | border-radius: 0.25rem;
304 | padding: .375rem .75rem;
305 | }
306 |
307 | .collections-selected .token {
308 | display: flex;
309 | flex-shrink: 1;
310 | height: 18px;
311 | align-items: center;
312 | background-color: #eff7ff;
313 | padding: 2px 6px;
314 | margin-right: 6px;
315 | margin-bottom: 6px;
316 | }
317 |
318 | .token .remove-btn {
319 | cursor: pointer;
320 | display: flex;
321 | align-items: center;
322 | justify-content: center;
323 | flex-grow: 0;
324 | flex-shrink: 0;
325 | margin-left: 2px;
326 | margin-right: 2px;
327 | width: 18px;
328 | height: 18px;
329 | }
330 |
331 | .results {
332 | max-height: 200px;
333 | overflow-y: scroll;
334 | }
335 |
336 | .results::-webkit-scrollbar {
337 | width: 10px;
338 | }
339 |
340 | .collections-dropdown .collections-search-container {
341 | display: flex;
342 | align-items: center;
343 | min-width: 100px;
344 | flex: 1;
345 | }
346 |
347 | .collections-search {
348 | font-family: sans-serif;
349 | font-size: 1em;
350 | width: 100%;
351 | border: none;
352 | padding-bottom: 0.5em;
353 | }
354 |
355 | .collections-search:focus {
356 | outline: none;
357 | }
358 |
--------------------------------------------------------------------------------
/firefox/src/images/icon-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/firefox/src/images/icon-screenshot.png
--------------------------------------------------------------------------------
/firefox/src/images/icon-slack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/firefox/src/images/icon-slack.png
--------------------------------------------------------------------------------
/firefox/src/images/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/firefox/src/images/icon128.png
--------------------------------------------------------------------------------
/firefox/src/images/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/firefox/src/images/icon16.png
--------------------------------------------------------------------------------
/firefox/src/images/icon24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/firefox/src/images/icon24.png
--------------------------------------------------------------------------------
/firefox/src/images/icon32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/firefox/src/images/icon32.png
--------------------------------------------------------------------------------
/firefox/src/images/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LayBacc/rumin-web-clipper/ab1258784ebf16468394c67367392c32ba944569/firefox/src/images/spinner.gif
--------------------------------------------------------------------------------
/firefox/src/index.js:
--------------------------------------------------------------------------------
1 | // import App from "../components/App";
2 | import App from './App'
--------------------------------------------------------------------------------
/firefox/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Rumin",
3 | "version": "0.0.0.15",
4 | "description": "The fastest way to collect information from diverse sources on the web - Rumin helps you capture any content instantly.",
5 | "permissions": ["", "activeTab", "tabs", "storage"],
6 | "background": {
7 | "scripts": ["background.js"]
8 | },
9 | "content_scripts": [
10 | {
11 | "matches": [
12 | "*://*/*"
13 | ],
14 | "js": ["vendor/jquery-3.4.1.min.js", "content.js"],
15 | "css": ["css/content.css"]
16 | }
17 | ],
18 | "browser_action": {
19 | "default_popup": "popup.html",
20 | "default_icon": {
21 | "16": "images/icon16.png",
22 | "24": "images/icon24.png",
23 | "32": "images/icon32.png"
24 | }
25 | },
26 | "icons": {
27 | "16": "images/icon16.png",
28 | "24": "images/icon24.png",
29 | "32": "images/icon32.png",
30 | "128": "images/icon128.png"
31 | },
32 | "manifest_version": 2
33 | }
34 |
--------------------------------------------------------------------------------
/firefox/src/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Using local storage helps us serve you better
12 |
13 |
Allow Why?
14 |
15 |
16 |
17 |
18 | Save
19 |
20 |
21 | Lookup
22 |
23 |
24 |
25 |
26 |
110 |
111 |
114 |
115 |
116 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/firefox/src/popup.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | let favIconUrl;
3 | let title;
4 | let pageUrl;
5 | let selection;
6 | let note;
7 | let page_dom;
8 | let hasStoragePermission = false;
9 | let searchQuery = '';
10 | let customFields = {};
11 |
12 | function delay(fn, ms) {
13 | let timer = 0
14 | return function(...args) {
15 | clearTimeout(timer)
16 | timer = setTimeout(fn.bind(this, ...args), ms || 0)
17 | }
18 | }
19 |
20 | function escapeHtml(unsafe) {
21 | return unsafe
22 | .replace(/&/g, "&")
23 | .replace(//g, ">")
25 | .replace(/"/g, """)
26 | .replace(/'/g, "'");
27 | }
28 |
29 | const serializeHTML = (node) => {
30 | if (typeof node.text === 'string') {
31 | return escapeHtml(node.text)
32 | }
33 |
34 | const children = node.children.map(n => serializeHTML(n)).join('')
35 |
36 | let url
37 | if (node.url) {
38 | if (node.url.startsWith('/spaces/') || node.url.startsWith('/activities/')) {
39 | url = `https://getrumin.com${node.url}`
40 | }
41 | else {
42 | url = node.url
43 | }
44 | }
45 |
46 | switch (node.type) {
47 | case 'quote':
48 | return `${children}
`
49 | case 'paragraph':
50 | return `${children}
`
51 | case 'link':
52 | return `${children} `
53 | case 'list-item':
54 | return `${children} `
55 | case 'heading-one':
56 | return `${children} `
57 | case 'heading-two':
58 | return `${children} `
59 | case 'heading-three':
60 | return `${children} `
61 | default:
62 | return children
63 | }
64 | }
65 |
66 | function activityData() {
67 | title = $('#captured_title_field').val();
68 | note = $('#captured_note_field').val();
69 |
70 | let params = {
71 | title: title,
72 | url: pageUrl,
73 | selection: selection,
74 | favicon_url: favIconUrl,
75 | note: note || '',
76 | // page_dom: page_dom,
77 | screenshot: $('#screenshot_img').attr('src'),
78 | custom_fields: customFields
79 | }
80 |
81 | if (window.selectedSpaces) {
82 | params.in_collections = window.selectedSpaces
83 | }
84 |
85 | return params
86 | }
87 |
88 | function buildActivity(activity) {
89 | let youtubeString = ''
90 | if (activity.url && activity.url.startsWith('https://www.youtube.com') && activity.custom_fields && activity.custom_fields.current_time) {
91 | youtubeString = `Video at ${activity.custom_fields.current_time}
`
92 | }
93 |
94 | return(`
95 |
96 |
99 |
100 | created ${ new Date(activity.created_at).toISOString().slice(0,10) } last updated ${ new Date(activity.updated_at).toISOString().slice(0,10) }
101 |
102 | ${ youtubeString }
103 |
104 |
105 |
106 | ${ activity.selection }
107 |
108 |
109 |
110 |
111 |
${ serializeHTML({ children: activity.json_body }) }
112 |
113 |
114 | `)
115 | }
116 |
117 | function buildSpace(space) {
118 | return(`
119 |
120 |
123 |
124 | created ${ new Date(space.created_at).toISOString().slice(0,10) } last updated ${ new Date(space.created_at).toISOString().slice(0,10) }
125 |
126 |
127 |
${ space.text_body.length > 500 ? space.text_body.slice(0, 500) + '...' : space.text_body }
128 |
129 |
130 | `)
131 | }
132 |
133 | function buildCustomField(name, value) {
134 | if (typeof value === 'object') {
135 | value = JSON.stringify(value, null, 2)
136 | }
137 |
138 | return(`
139 |
140 |
${ name }:
141 |
${ value }
142 |
143 | `)
144 | }
145 |
146 | function fetchMatchingPages() {
147 | $.ajax({
148 | url: `https://getrumin.com/api/v1/search/?url=${encodeURIComponent(encodeURIComponent(pageUrl))}`,
149 | method: 'GET',
150 | contentType: 'application/json',
151 | success: function(data) {
152 | if (data.length === 0) {
153 | $('#search_results').html('No matching results on this page
')
154 | return
155 | }
156 |
157 | const resultElements = data.map(obj => {
158 | // console.log('obj', obj.content_type, obj.title, obj)
159 |
160 | if (obj.content_type === 'Activity') {
161 | return buildActivity(obj)
162 | }
163 | else {
164 | return buildSpace(obj)
165 | }
166 | }).join('')
167 |
168 |
169 | $('#search_results').html(`Related to this page
${resultElements}`)
170 | },
171 | error: function(error) {
172 | console.log('API error', error);
173 | }
174 | });
175 | }
176 |
177 | function sendMsgToContentScript(msg) {
178 | chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
179 | chrome.tabs.sendMessage(tabs[0].id, msg, function(response) {})
180 | });
181 | }
182 |
183 | chrome.runtime.sendMessage({popupOpen: true});
184 |
185 | chrome.runtime.onMessage.addListener(function(req, sender, sendResponse) {
186 |
187 | if (hasStoragePermission) {
188 | chrome.storage.local.get(['noteValue', 'titleValue'], function(result) {
189 | if (result.titleValue) {
190 | title = result.titleValue
191 | $('#captured_title_field').val(result.titleValue)
192 | }
193 |
194 | if (result.noteValue) {
195 | $('#captured_note_field').val(result.noteValue)
196 | }
197 | })
198 | }
199 |
200 | if (req.pageContext) {
201 |
202 | favIconUrl = sender.tab.favIconUrl;
203 |
204 | // no saved title from local storage
205 | if (!title || (title && title.trim() === '')) {
206 | title = req.pageContext.titleOverride ? req.pageContext.titleOverride : sender.tab.title;
207 | title = title.slice(0, 300)
208 | }
209 |
210 | pageUrl = req.pageContext.urlOverride ? req.pageContext.urlOverride : sender.tab.url;
211 | selection = req.pageContext.selection;
212 | // page_dom = req.pageContext.page_dom;
213 | customFields = req.pageContext.customFields;
214 |
215 | // get it for the Lookup tab
216 | fetchMatchingPages()
217 |
218 | if (title.trim() !== '') {
219 | $('#captured_title_field').val(title.trim());
220 | }
221 | else {
222 | $('#captured_title_field').val(pageUrl);
223 | }
224 |
225 | if (selection.trim()) {
226 | $('#captured_selection').text(selection.slice(0, 300));
227 | }
228 | else {
229 | $('.captured-selection').hide()
230 | }
231 |
232 | if (Object.keys(customFields).length > 0) {
233 | $('#custom_fields_section').removeClass('hidden')
234 |
235 | const divider = '
'
236 |
237 | const customFieldElements = Object.keys(customFields).map(fieldName => {
238 |
239 | return buildCustomField(fieldName, customFields[fieldName])
240 | }).join(divider)
241 |
242 | $('#custom_fields').html(`
243 |
244 | ${ customFieldElements }
245 |
246 | `)
247 | }
248 |
249 | // Site-specific selection and actions
250 | if (pageUrl.startsWith('https://app.slack.com/client/')) {
251 | $('#add_slack_selection').removeClass('hidden')
252 | }
253 |
254 | if (pageUrl.includes('reddit.com/r/') && pageUrl.includes('comments')) {
255 | // console.log('showing reddit btn')
256 | $('#add_reddit_selection').removeClass('hidden')
257 | }
258 | }
259 |
260 | if (req.screenshotCaptured) {
261 | $('#screenshot_img').removeClass('hidden')
262 | $('#screenshot_img').attr('src', req.screenshotData)
263 |
264 | // Tesseract.recognize(
265 | // req.screenshotData,
266 | // 'eng',
267 | // { logger: m => console.log(m) }
268 | // ).then(({ data: { text } }) => {
269 | // console.log('recognized text from screenshot', text);
270 | // })
271 |
272 | // TODO - convert OCR here
273 | }
274 | });
275 |
276 | $(function() {
277 | var noteSelectionStart;
278 | var noteSelectionEnd;
279 |
280 | chrome.permissions.contains({
281 | permissions: ['storage']
282 | }, function(result) {
283 | if (result) {
284 | $('#permission_bar').hide()
285 | hasStoragePermission = true
286 |
287 | // notify content script
288 | sendMsgToContentScript({hasStoragePermission: true})
289 | }
290 | })
291 |
292 | $('#save_btn').click(function() {
293 | // disable button
294 | $('#save_btn').html('Saving...Do not close this
')
295 | $('#save_btn').removeClass()
296 |
297 |
298 | if (hasStoragePermission) {
299 | // chrome.storage.local.clear()
300 | chrome.storage.local.remove(['titleValue', 'noteValue'])
301 | sendMsgToContentScript({ cancelSelection: true })
302 | sendMsgToContentScript({clearSelection: true})
303 | }
304 |
305 | $.ajax({
306 | url: 'https://getrumin.com/api/v1/activities/', //'http://127.0.0.1:8000/api/v1/activities/',//'https://getrumin.com/api/v1/activities/',
307 | method: 'POST',
308 | contentType: 'application/json',
309 | data: JSON.stringify(activityData()),
310 | success: function(data) {
311 | $('.capture-container').html(`
312 | The content is successfully saved.
313 |
316 | `)
317 | },
318 | error: function(error) {
319 | console.log('API error', error);
320 | }
321 | });
322 | });
323 |
324 | $('#custom_fields_heading').click(function() {
325 | $('.custom-fields-content').toggleClass('hidden')
326 | $('#custom_fields_display_icon').toggleClass('hidden')
327 | })
328 |
329 | $('#screenshot_btn').click(function() {
330 | chrome.runtime.sendMessage({takeScreenshot: true});
331 | })
332 |
333 | // TODO - finish implementing, so that the user doesn't remove the selection
334 | // $('#captured_note_field').keydown(function(e) {
335 | // var cursorStart = e.target.selectionStart
336 | // var cursorEnd = e.target.selectionEnd
337 |
338 | // if (cursorEnd > cursorStart && e.key === '{') {
339 | // noteSelectionStart = cursorStart
340 | // noteSelectionEnd = cursorEnd
341 | // }
342 | // })
343 |
344 |
345 | $('#captured_title_field').change(function(e) {
346 | if (hasStoragePermission) {
347 | chrome.storage.local.set({titleValue: e.target.value}, function() { console.log('setting noteValue in storage') })
348 | }
349 | })
350 |
351 | $('#captured_note_field').change(function(e) {
352 | if (hasStoragePermission) {
353 | chrome.storage.local.set({noteValue: e.target.value}, function() {})
354 | }
355 | })
356 |
357 |
358 | $('#captured_note_field').keyup(function(e) {
359 | // console.log('selection start', e.target.selectionStart, 'selection end', e.target.selectionEnd)
360 |
361 | if (e.key === '{') {
362 | // this is wrong
363 | // console.log(noteSelectionStart, noteSelectionEnd)
364 |
365 | var cursorStart = e.target.selectionStart
366 | var cursorEnd = e.target.selectionEnd
367 | var value = e.target.value
368 |
369 |
370 | e.target.value = value.slice(0, cursorStart) + '}' + value.slice(cursorStart,)
371 | e.target.selectionEnd = cursorStart
372 | }
373 | })
374 |
375 | // fetch search results
376 | $('#search_box').keyup(delay(function(e) {
377 | searchQuery = e.target.value
378 | $('#search_results').html('fetching...
')
379 |
380 | $.ajax({
381 | url: `https://getrumin.com/api/v1/search?q=${searchQuery}&is_as=true/`,
382 | method: 'GET',
383 | contentType: 'application/json',
384 | success: function(data) {
385 | const resultsElements = data.results.map(obj => {
386 | if (obj.content_type === 'Activity') {
387 | return buildActivity(obj)
388 | }
389 | else {
390 | return buildSpace(obj)
391 | }
392 | }).join('')
393 |
394 | $('#search_results').html(`Results for ${searchQuery}
${resultsElements}`)
395 | },
396 | error: function(error) {
397 | console.log('API error', error);
398 | }
399 | })
400 | }, 500))
401 |
402 | // switching tabs
403 | $('#lookup_tab_btn').click(function(e) {
404 | $('#save_tab_btn').removeClass('active')
405 | $('#lookup_tab_btn').addClass('active')
406 |
407 | $('#save_tab').addClass('hidden')
408 | $('#lookup_tab').removeClass('hidden')
409 | })
410 |
411 | $('#save_tab_btn').click(function(e) {
412 | $('#lookup_tab_btn').removeClass('active')
413 | $('#save_tab_btn').addClass('active')
414 |
415 | $('#lookup_tab').addClass('hidden')
416 | $('#save_tab').removeClass('hidden')
417 | })
418 |
419 |
420 | // custom selection for Slack
421 | $('#add_slack_selection').click(function() {
422 | sendMsgToContentScript({addSelection: true})
423 |
424 | $('#add_slack_selection').addClass('hidden')
425 | $('#cancel_slack_selection').removeClass('hidden')
426 | })
427 |
428 | $('#cancel_slack_selection').click(function() {
429 | sendMsgToContentScript({ cancelSelection: true })
430 |
431 | $('#cancel_slack_selection').addClass('hidden')
432 | $('#add_slack_selection').removeClass('hidden')
433 | })
434 |
435 |
436 | // custom selection for Reddit
437 | $('#add_reddit_selection').click(function() {
438 | sendMsgToContentScript({addSelection: true})
439 |
440 | $('#add_reddit_selection').addClass('hidden')
441 | $('#cancel_reddit_selection').removeClass('hidden')
442 | })
443 |
444 | $('#cancel_reddit_selection').click(function() {
445 | sendMsgToContentScript({ cancelSelection: true })
446 |
447 | $('#cancel_reddit_selection').addClass('hidden')
448 | $('#add_reddit_selection').removeClass('hidden')
449 | })
450 |
451 |
452 |
453 | // $(document).ready(function () {
454 | // $("#add_to_field").tokenInput("http://127.0.0.1:8000/search", {
455 | // propertyToSearch: 'title'
456 | // });
457 | // });
458 |
459 | // requesting additional permission for storage
460 | $('#req_storage_permission').click(function(e) {
461 | chrome.permissions.request({
462 | permissions: ['storage']
463 | }, function(granted) {
464 | if (granted) {
465 | // console.log('storage permission granted')
466 | $('#permission_bar').html('Please re-open this extension')
467 | }
468 | else {
469 | console.log('storage permission not granted')
470 | }
471 | })
472 | })
473 | })
474 | })();
475 |
--------------------------------------------------------------------------------
/firefox/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | module: {
3 | rules: [
4 | {
5 | test: /\.js$/,
6 | exclude: /node_modules/,
7 | // use: {
8 | // loader: "babel-loader"
9 | // }
10 | loader: 'babel-loader',
11 | options: {
12 | presets: ['@babel/preset-env',
13 | '@babel/react',{
14 | 'plugins': ['@babel/plugin-proposal-class-properties']}]
15 | }
16 | },
17 | {
18 | test: /\.css$/,
19 | use: [
20 | // 'style-loader',
21 | 'css-loader'
22 | ]
23 | }
24 | ],
25 | },
26 | watch: true,
27 | watchOptions: {
28 | poll: true,
29 | ignored: /node_modules/
30 | }
31 | };
32 |
--------------------------------------------------------------------------------