Skip to content
This repository was archived by the owner on Apr 28, 2022. It is now read-only.

Commit 93da985

Browse files
committed
Use webpack on main app
Bundle updated list of groups Inject files in consistent way Add option to linkify hashtags in post Add option to show matching keywords in posts Move complete release to dist folder
1 parent 2120073 commit 93da985

28 files changed

+801
-147
lines changed

background.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ chrome.runtime.onInstalled.addListener(function() {
1616

1717
chrome.pageAction.onClicked.addListener((tab) => {
1818
chrome.tabs.executeScript(tab.id,
19-
{code: `document.body.style.background = '#e9ebee';`},
19+
{code: `document.mark.style.background = '#ecf0f7';`},
2020
function() {
2121
chrome.tabs.executeScript(tab.id, { file: 'contentscript.js' });
2222
});

contentscript.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,17 @@ function getInterestKeywords(callback) {
6666
chrome.storage.sync.get('keywords', (res) => callback(null, res.keywords || []));
6767
}
6868

69+
function getHighlightMatches(callback) {
70+
chrome.storage.sync.get('highlightMatches', (res) => callback(null, typeof res.highlightMatches === 'undefined' ? true : res.highlightMatches));
71+
}
72+
6973
// inject scripts to page: https://stackoverflow.com/a/9517879
7074
function injectScriptFile(fname, callback) {
7175
const s = document.createElement('script');
7276
s.src = chrome.extension.getURL(fname);
7377
s.onload = function() {
7478
this.remove();
75-
if (typeof callback === 'function') callback();
79+
callback();
7680
};
7781
(document.head||document.documentElement).appendChild(s);
7882
}
@@ -88,6 +92,7 @@ asyncParallel({
8892
getAccessToken,
8993
getBlacklistedGroups,
9094
getInterestKeywords,
95+
getHighlightMatches,
9196
}, (err, results) => {
9297
if (err) return alert(err.message || err);
9398

@@ -96,9 +101,22 @@ asyncParallel({
96101
_apiKey: '${results.getAccessToken}',
97102
_blacklist: [${results.getBlacklistedGroups}],
98103
_keywords: new Set([${keywords.map(v => `'${v}'`)}]),
104+
_highlightMatches: ${results.getHighlightMatches},
99105
}`);
100106

101-
injectScriptFile('utils.js', function(){
102-
injectScriptFile('fb-dev-merge.js');
107+
asyncParallel({
108+
utils: cb => injectScriptFile('utils.js', cb),
109+
groups: cb => injectScriptFile('groups.js', cb),
110+
inject: cb => injectScriptFile('inject.js', cb),
111+
}, (err, res) => {
112+
injectScriptText(`
113+
console.log('<<< fbDevInterest >>>');
114+
fbDevInterest.parent.innerHTML = '';
115+
fbDevInterest.getFeed();
116+
`);
103117
});
118+
119+
// injectScriptFile('utils.js', function(){
120+
// injectScriptFile('inject.js');
121+
// });
104122
});

dist/background.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
chrome.runtime.onInstalled.addListener(function() {
2+
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
3+
chrome.declarativeContent.onPageChanged.addRules([{
4+
conditions: [
5+
new chrome.declarativeContent.PageStateMatcher({
6+
pageUrl: { urlContains: 'localhost' },
7+
}),
8+
new chrome.declarativeContent.PageStateMatcher({
9+
pageUrl: { urlContains: 'https://www.facebook.com/groups/' },
10+
})
11+
],
12+
actions: [new chrome.declarativeContent.ShowPageAction()]
13+
}]);
14+
});
15+
});
16+
17+
chrome.pageAction.onClicked.addListener((tab) => {
18+
chrome.tabs.executeScript(tab.id,
19+
{code: `document.mark.style.background = '#ecf0f7';`},
20+
function() {
21+
chrome.tabs.executeScript(tab.id, { file: 'contentscript.js' });
22+
});
23+
});

dist/contentscript.js

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
function asyncParallel(tasks, cb) {
2+
var results, pending, keys
3+
var isSync = true
4+
5+
if (Array.isArray(tasks)) {
6+
results = []
7+
pending = tasks.length
8+
} else {
9+
keys = Object.keys(tasks)
10+
results = {}
11+
pending = keys.length
12+
}
13+
14+
function done(err) {
15+
function end() {
16+
if (cb) cb(err, results)
17+
cb = null
18+
}
19+
if (isSync) process.nextTick(end)
20+
else end()
21+
}
22+
23+
function each(i, err, result) {
24+
results[i] = result
25+
if (--pending === 0 || err) {
26+
done(err)
27+
}
28+
}
29+
30+
if (!pending) {
31+
// empty
32+
done(null)
33+
} else if (keys) {
34+
// object
35+
keys.forEach(function(key) {
36+
tasks[key](function(err, result) { each(key, err, result) })
37+
})
38+
} else {
39+
// array
40+
tasks.forEach(function(task, i) {
41+
task(function(err, result) { each(i, err, result) })
42+
})
43+
}
44+
45+
isSync = false
46+
}
47+
48+
// get access token from saved extension settings
49+
function getAccessToken(callback) {
50+
chrome.storage.sync.get('apikey', (res) => {
51+
if (!res.apikey) {
52+
alert('API key not set.');
53+
return callback('API key not set.')
54+
};
55+
callback(null, res.apikey);
56+
});
57+
}
58+
59+
function getBlacklistedGroups(callback) {
60+
chrome.storage.sync.get('groups', (res) => {
61+
callback(null, res.groups || []);
62+
});
63+
}
64+
65+
function getInterestKeywords(callback) {
66+
chrome.storage.sync.get('keywords', (res) => callback(null, res.keywords || []));
67+
}
68+
69+
function getHighlightMatches(callback) {
70+
chrome.storage.sync.get('highlightMatches', (res) => callback(null, typeof res.highlightMatches === 'undefined' ? true : res.highlightMatches));
71+
}
72+
73+
// inject scripts to page: https://stackoverflow.com/a/9517879
74+
function injectScriptFile(fname, callback) {
75+
const s = document.createElement('script');
76+
s.src = chrome.extension.getURL(fname);
77+
s.onload = function() {
78+
this.remove();
79+
callback();
80+
};
81+
(document.head||document.documentElement).appendChild(s);
82+
}
83+
84+
function injectScriptText(str) {
85+
var script = document.createElement('script');
86+
script.textContent = str;
87+
(document.head||document.documentElement).appendChild(script);
88+
script.remove();
89+
}
90+
91+
asyncParallel({
92+
getAccessToken,
93+
getBlacklistedGroups,
94+
getInterestKeywords,
95+
getHighlightMatches,
96+
}, (err, results) => {
97+
if (err) return alert(err.message || err);
98+
99+
const keywords = [...results.getInterestKeywords].map(k => k.name);
100+
injectScriptText(`var fbDevInterest = {
101+
_apiKey: '${results.getAccessToken}',
102+
_blacklist: [${results.getBlacklistedGroups}],
103+
_keywords: new Set([${keywords.map(v => `'${v}'`)}]),
104+
_highlightMatches: ${results.getHighlightMatches},
105+
}`);
106+
107+
asyncParallel({
108+
utils: cb => injectScriptFile('utils.js', cb),
109+
groups: cb => injectScriptFile('groups.js', cb),
110+
inject: cb => injectScriptFile('inject.js', cb),
111+
}, (err, res) => {
112+
injectScriptText(`
113+
console.log('<<< fbDevInterest >>>');
114+
fbDevInterest.parent.innerHTML = '';
115+
fbDevInterest.getFeed();
116+
`);
117+
});
118+
119+
// injectScriptFile('utils.js', function(){
120+
// injectScriptFile('inject.js');
121+
// });
122+
});

dist/groups.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fb-dev-merge.js renamed to dist/inject.js

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
var ALL_GROUPS = [249598592040574,1378294582253698,2224932161064321,1924443867832338,1922538421363451,1920036621597031,1903916609822504,1841081392797911,1806620552895262,1780072415645281,1741843536047014,1724152667880378,1607133026028061,1494181493938081,1443394385967980,1258355007573190,1152576018114322,1148469218498930,1075017422642967,1074858042611323,1071045349642536,1041205739348709,893652180764182,886251554842166,885490321621308,854314664699156,826341790867138,813879575430133,811281355669013,793016410839401,786453984830109,638854212931776,485698195138488,476463749198108,428973767504677,402137910152010,362906487478469,348458995586076,332006040559709,313087542449350,309450039518404,304477986647756,293458267749614,265793323822652,223094988221674,199036970608482,187217085094857,186924858495604,160941794378470,152127978670639,132580147377707,125327974795168,111858152705945]; // all public groups
2-
31
// remove blacklisted groups
4-
for (let groupId of fbDevInterest._blacklist) {
5-
let idx = ALL_GROUPS.indexOf(parseInt(groupId, 10));
6-
ALL_GROUPS.splice(idx, 1);
2+
fbDevInterest.clearBlacklist = function() {
3+
for (let groupId of this._blacklist) {
4+
let idx = this.ALL_GROUPS.indexOf(parseInt(groupId, 10));
5+
this.ALL_GROUPS.splice(idx, 1);
6+
}
77
}
88

99
fbDevInterest.createPlaceholder = function() {
@@ -18,35 +18,85 @@ fbDevInterest.BASE_API_URL = `https://graph.facebook.com/v2.11/GROUPID/?&access_
1818

1919
// gets a group id from ALL_GROUPS list
2020
fbDevInterest.getGroupId = {};
21-
fbDevInterest.getGroupId = (function *() {
22-
let i = -1;
21+
fbDevInterest.getGroupId = function () {
22+
const self = this;
23+
let i = -1;
24+
return (function *() {
2325
while (true) {
24-
yield this.ALL_GROUPS[++i % ALL_GROUPS.length];
26+
yield self.ALL_GROUPS[++i % self.ALL_GROUPS.length];
2527
};
2628
})();
29+
}
2730

2831
fbDevInterest.parent = document.querySelector('#pagelet_group_mall'); // feed parent
2932
fbDevInterest.state = {}; // stores next fetch urls for group and group names
3033

31-
32-
fbDevInterest.satisfiesFilters = function(content) {
33-
if (this._keywords.size === 0) return true;
34+
// find matching keywords in post body
35+
fbDevInterest.findMatchedKeywords = function(content) {
36+
if (this._keywords.size === 0) return [[0, 0]];
3437
const $content = content.toLowerCase();
38+
const matches = [];
3539
for (const keyword of this._keywords) {
36-
if ($content.includes(keyword)) return true;
40+
const re = new RegExp(`(?:^|\\b)(${keyword.replace(/\s\s+/g, '\\s*')})(?=\\b|$)`);
41+
const match = $content.match(re);
42+
if (match) matches.push([match.index, match[0].length])
3743
}
38-
return false;
44+
function compare(a,b) {
45+
if (a[0] < b[0]) return -1;
46+
if (a[0] > b[0]) return 1;
47+
if (a[1] < b[1]) return 1; // to have larger keyword before
48+
if (a[1] > b[1]) return -1;
49+
return 0;
50+
}
51+
return matches.sort(compare);
52+
}
53+
54+
// highlight matched keywords in post body
55+
fbDevInterest.highlightMatches = function(str, matches) {
56+
let $str = ''
57+
let pos;
58+
let flag = 0;
59+
const alreadyIncludedStart = new Set();
60+
for (let i = 0; i < matches.length; ++i) {
61+
pos = (i === 0) ? 0 : matches[i-1][0] + matches[i-1][1];
62+
const [ $start, $length ] = matches[i];
63+
if (alreadyIncludedStart.has($start)) {
64+
flag = matches[i-1][0] + matches[i-1][1];
65+
continue; // to prevent highlight again if a smaller keyword in a larger keyword
66+
}
67+
alreadyIncludedStart.add($start);
68+
$str += `${str.substring(flag !== 0 ? flag : pos, $start)}<mark>${str.substring($start, $start + $length)}</mark>`;
69+
flag = 0;
70+
}
71+
const lastMatch = matches[matches.length - 1];
72+
$str += str.substring(lastMatch[0] + lastMatch[1]);
73+
return $str;
74+
}
75+
76+
// split post body if too long
77+
fbDevInterest.splitPostBody = function(content) {
78+
const splitted = content.split('</p>');
79+
if (splitted.length < 6) return content;
80+
const visible = splitted.slice(0, 6).join('</p>');
81+
const hidden = splitted.slice(6).join('</p>');
82+
83+
const id = Math.random().toString().replace('.', '');
84+
const postBody = `<div id="id_${id}" class="text_exposed_root">${visible}<span class="text_exposed_hide">...</span><div class="text_exposed_show">${hidden}</div><span class="text_exposed_hide"> <span class="text_exposed_link"><a class="see_more_link" onclick="var func = function(e) { e.preventDefault(); }; var parent = Parent.byClass(this, 'text_exposed_root'); if (parent && parent.getAttribute('id') == 'id_${id}') { CSS.addClass(parent, 'text_exposed'); Arbiter.inform('reflow'); }; func(event); "><span class="see_more_link_inner">See more</span></a></span></span></div>`;
85+
return postBody;
3986
}
4087

4188
// appends a post in feed
4289
fbDevInterest.showPost = function(entry, group) {
4390
if (!entry.message) return;
44-
if (!this.satisfiesFilters(entry.message)) return;
91+
const matchedKeywords = this.findMatchedKeywords(entry.message);
92+
if (matchedKeywords.length === 0) return;
93+
if (this._highlightMatches) entry.message = this.highlightMatches(entry.message, matchedKeywords);
4594
entry.message = entry.message.replace(/\n/g, "</p><p>").linkify();
95+
4696
if (entry.link && !entry.full_picture) {
47-
entry.message = `${entry.message} </p><p>[<a href="${entry.link}" target="_blank">${entry.link}</a>]`;
97+
entry.message = `${entry.message} </p><p>[<b>LINK:</b> <a href="${entry.link}" target="_blank">${entry.link}</a>]`;
4898
}
49-
99+
entry.message = this.splitPostBody(entry.message);
50100
const created_time = new Date(entry.created_time);
51101

52102
const image = (entry.full_picture && !entry.full_picture.includes('//external.'))
@@ -58,7 +108,7 @@ fbDevInterest.showPost = function(entry, group) {
58108
<div></div>
59109
<div class="_5pcr userContentWrapper">
60110
<div class="_1dwg _1w_m _q7o">
61-
<h5>
111+
<h5 class="_5pbw fwb">
62112
<a href="https://www.facebook.com/${entry.from.id}">${entry.from.name}</a>
63113
‎ ▶
64114
<a href="https://www.facebook.com/${group.id}">${group.name}</a>
@@ -95,8 +145,6 @@ fbDevInterest.getGroupFeed = function(options) {
95145
fetchUrl = state.nextPageUrl;
96146
}
97147

98-
console.log(fetchUrl)
99-
100148
fetch(fetchUrl)
101149
.then((res) => res.json())
102150
.then(json => {
@@ -139,6 +187,8 @@ fbDevInterest.clearPosts = function() {
139187
};
140188

141189
fbDevInterest.getFeed = function() {
190+
this.clearBlacklist();
191+
this.getGroupId = this.getGroupId();
142192
this.clearPosts();
143193
for (let i = 0; i < 5; ++i) this.parent.appendChild(this.createPlaceholder());
144194
scrollToItem(this.parent);
@@ -160,7 +210,3 @@ fbDevInterest.showPostsOnScroll = function() {
160210
};
161211
window.addEventListener('scroll', onVisible, true)
162212
}
163-
164-
console.log('<<< fbDevInterest >>>');
165-
fbDevInterest.parent.innerHTML = '';
166-
fbDevInterest.getFeed();

dist/manifest.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"manifest_version": 2,
3+
"name": "Facebook Dev Circles Merge",
4+
"description": "Shows results from all the Facebook Dev Circle groups based on your interests",
5+
"version": "1.0",
6+
"page_action":
7+
{
8+
"default_title": "Facebook Dev Interests"
9+
},
10+
"background":
11+
{
12+
"scripts": ["background.js"],
13+
"persistent": false
14+
},
15+
"options_page": "options/index.html",
16+
"web_accessible_resources": [
17+
"utils.js",
18+
"groups.js",
19+
"inject.js"
20+
],
21+
"permissions": [
22+
"declarativeContent",
23+
"activeTab",
24+
"storage"
25+
],
26+
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
27+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
<head>
44
<meta charset="UTF-8">
55
<title>Facebook Developer Interests | Options</title>
6+
<meta name="viewport" content="width=device-width">
67
</head>
78
<body>
89
<div id="root"></div>
9-
<script type="text/javascript" src="vendor.bundle.js"></script><script type="text/javascript" src="app.js"></script></body>
10+
<script type="text/javascript" src="vendor.bundle.js"></script><script type="text/javascript" src="options.js"></script></body>
1011
</html>

dist/options/options.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)