47 |
48 | **Safari:** No immediate plans. While I can port the extension to Safari, I can't sign and publish it without a recent MacOS device.
49 |
50 | **Other browsers:** No immediate plans. For most other browsers, you should be able to download from either the Chrome or Firefox stores.
51 |
52 | If you have thoughts or want to contribute, you can join the discussion here: https://lemmy.ca/c/instance_assistant
53 |
54 |
55 |
56 |
57 |
58 |
59 | # Features
60 |
61 |
62 |
63 | **Support** for Lemmy and Kbin instances + custom frontends such as Alexandrite and Photon
64 |
65 |
66 |
67 | **Post to Lemmy/Kbin:**
68 | * Instance Assistant can generate a draft post with autofilled title/link/body
69 | * For most news sites, videos, and other webpages, you can use the popup menu
70 | * For images, you can right-click on the image itself
71 |
72 |
73 |
74 | **Search for posts:**
75 | * Find and open all posts that have a link to the site you are on. Use it to find posts about news articles, videos, and more
76 |
77 |
78 |
79 | **Search for communities and content without leaving the page:**
80 | * You can use the popup menu or sidebar to search for communities (powered by [lemmyverse.net](https://lemmyverse.net/communities)), and for content (powered by [search-lemmy.com](https://search-lemmy.com))
81 |
82 |
83 |
84 | **Redirect to your home instance:**
85 | * When you are on a foreign instance, buttons will be available in the sidebar to open the **community**, **post**, or **user (NEW)** in your home instance, allowing you to participate immediately
86 |
87 |
88 |
89 | **Open links in home instance:**
90 | * If you come across a Lemmy/Kbin link anywhere on the web, you can right click to open it in your home instance
91 |
92 |
93 |
94 | **Upgraded Pages:**
95 | * 'Community not found' pages now have better information and buttons to trigger a fetch, open the community in the source instance, and more. This also supports alternative front-ends. [See it in action here](https://lemmy.ca/c/fakecommunity@example.com)
96 | * `/communities` pages will have the non-functional 'subscribe' buttons replaced, to make it easier when browsing other instances.
97 |
98 |
99 |
100 | **Customizable:**
101 | * Customizable list of instances in popup/sidebar to let you quickly switch home instances. This is great for if you have multiple accounts on different instances
102 | * You can change the behaviour of the extension and customize the actual Lemmy/Kbin site interface, making your browsing experience your own.
103 |
104 |
105 |
106 |
107 | ### I'm new to Lemmy/Kbin, what is this?
108 |
109 | Lemmy and Kbin are a part of the Fediverse, a network of interconnected social media platforms that work similar to Reddit. This extension is designed to make it easier to use these platforms and make the most of decentralized social media while mimizing the difficulties that may come with it.
110 |
111 | Say, for example, you may make an account on `lemmy.ca`. You may google a community or topic and come across a page on a different instance (ex. `lemmy.ml/c/technology`). If you want to subscribe to the community, you will need to copy the code (`!technology@lemmy.ml`), open your home instance, and then paste it into the search page. If you want to comment on a post, you would then try to track it down by scrolling through the community.
112 |
113 | This extension will let you jump to the version on your home instance, allowing you to subscribe and participate immediately.
114 |
115 | There are many other features as well, and hopefully many more to come! :)
116 |
117 |
118 |
119 | # Contributing
120 |
121 | You can contribute to this project in a number of ways:
122 |
123 | 🐛 **Report bugs & suggest features:** If you find a bug or want a new feature, you can discuss in the [Support Community](https://lemmy.ca/c/instance_assistant) or open an issue on GitHub.
124 |
125 | 💻 **Contribute code:** You can find guidance [on the wiki](https://github.com/cynber/lemmy-instance-assistant/wiki/Development-Process)
126 |
127 | 🌐 **Translate:** If you want to help translate the extension, you will soon be able to do so. I will update this section once it is ready.
128 |
129 | 💛 **Donate:** If you want to support the project financially, you can do so by clicking the sponsor button on this page, or through [ko-fi.com/cynber](https://ko-fi.com/cynber)
130 |
131 |
132 |
133 |
134 |
135 | # Other Links
136 |
137 | * [Recent Updates](https://github.com/cynber/lemmy-instance-assistant/wiki#recent-updates)
138 | * [Open Issues](https://github.com/cynber/lemmy-instance-assistant/issues)
139 | * [What is being worked on?](https://github.com/users/cynber/projects/1)
140 | * [Credits](https://github.com/cynber/lemmy-instance-assistant/wiki#credits)
141 |
142 |
143 |
144 | # Screenshots
145 |
146 | This is not a complete list of features, but it should give you an idea of what the extension can do. I will update these screenshots every now and then, so the UI may have changed since these were taken.
147 |
148 |
149 |
150 |
Popup Menu with redirect, community search, and more
151 |
Improved 'community not found' page, with button to triger a fetch and more
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
Redirect button in sidebar of foreign Lemmy instance
162 |
Redirect button in sidebar of foreign Kbin instance
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
Redirect button for Photon Frontend
173 |
Redirect button for Alexandrite Frontend
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
Improved '/communities' page on foreign instances
184 |
Persistent sidebar version of popup menu
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
Right click context menu to open any links in your home instance
195 |
Change display settings, such as hiding the default sidebar
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function init_build() {
4 | # GET VERSION AND MANIFEST VERSION ==========================================
5 | version=$(grep -o '"version": "[^"]*' src/manifest_$1.json | cut -d'"' -f4)
6 | manifest=$(grep -o '"manifest_version": [0-9]*' src/manifest_$1.json | cut -d' ' -f2)
7 |
8 | # CREATE BUILD DIRECTORY ====================================================
9 | echo "Building $1 v-$version$suffix (Manifest $manifest)..."
10 | directory="build/$1/instance-assistant-$version$suffix"
11 | if [ -d "$directory" ]; then
12 | rm -rf $directory
13 | fi
14 | mkdir -p $directory
15 |
16 | # COPY FILES ================================================================
17 | cp src/manifest_$1.json $directory/manifest.json
18 | cp LICENSE $directory/LICENSE
19 | cp src/styles.css $directory/styles.css
20 | cp src/utils.js $directory/utils.js
21 | cp src/content-sidebar.js $directory/content-sidebar.js
22 | cp src/content-communityNotFound.js $directory/content-communityNotFound.js
23 | cp src/content-general.js $directory/content-general.js
24 | cp -r node_modules $directory/node_modules
25 | cp -r src/img $directory/img
26 | # cp -r src/_locales $directory/_locales # TODO: Fix translations
27 | cp -r src/page-options $directory/page-options
28 | cp -r src/page-popup $directory/page-popup
29 | cp -r src/page-settings $directory/page-settings
30 | cp -r src/page-sidebar $directory/page-sidebar
31 | cp -r src/page-search $directory/page-search
32 |
33 | # COPY SCRIPT FILES BASED ON MANIFEST VERSION ================================
34 | if [ "$manifest" = 2 ]; then
35 | cp src/background.js $directory/background.js
36 | else
37 | cp src/m3-background.js $directory/background.js
38 | fi
39 |
40 | # REPLACE DEV IMAGES WITH PRODUCTION IMAGES ==================================
41 | if [ "$isDev" = false ]; then
42 | sed -i 's/_dev.png/.png/' $directory/manifest.json
43 | fi
44 |
45 | # CHECK IF ZIP FILE ALREADY EXISTS AND CONFIRM OVERWRITE (PRODUCTION ONLY) ===
46 | if [ -f "build/$1/instance-assistant-$1-$version$suffix.zip" ] && [ "$isDev" = false ]; then
47 | read -p "Zip file already exists. Overwrite? (y/n): " confirm
48 | if [[ $confirm == "y" ]]; then
49 | rm "build/$1/instance-assistant-$1-$version$suffix.zip"
50 | else
51 | echo "Build process canceled."
52 | exit 1
53 | fi
54 | fi
55 |
56 | # ZIP FILES, REMOVE BUILD DIRECTORY, AND PRINT SUCCESS MESSAGE ===============
57 | cd build/$1/instance-assistant-$version$suffix
58 | zip -r ../instance-assistant-$1-$version$suffix.zip * >/dev/null 2>&1
59 | cd ../../..
60 | # rm -rf $directory
61 | echo -e "\e[32mDone building $1 v-$version$suffix\e[0m"
62 | }
63 |
64 | # =================================================================================
65 | # BUILD SCRIPT
66 | # =================================================================================
67 |
68 | suffix=""
69 | isDev=false
70 |
71 | if [ "$1" == "-dev" ]; then
72 | suffix="-DEV"
73 | isDev=true
74 | fi
75 |
76 | init_build "chrome"
77 | init_build "firefox"
78 | init_build "edge"
79 | init_build "opera"
80 | #init_build "safari"
--------------------------------------------------------------------------------
/dev-testing/info.txt:
--------------------------------------------------------------------------------
1 | This folder has code that's still being tested
--------------------------------------------------------------------------------
/node_modules/.package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lemmy-instance-assistant",
3 | "lockfileVersion": 3,
4 | "requires": true,
5 | "packages": {
6 | "node_modules/webextension-polyfill": {
7 | "version": "0.10.0",
8 | "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz",
9 | "integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==",
10 | "dev": true
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/node_modules/webextension-polyfill/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
374 |
--------------------------------------------------------------------------------
/node_modules/webextension-polyfill/README.md:
--------------------------------------------------------------------------------
1 | # WebExtension `browser` API Polyfill
2 |
3 | This library allows extensions that use the Promise-based WebExtension/BrowserExt API being standardized by the
4 | [W3 Browser Extensions][w3-browserext] group to run on Google Chrome with minimal or no changes.
5 |
6 | [](https://circleci.com/gh/mozilla/webextension-polyfill)
7 | [](https://codecov.io/gh/mozilla/webextension-polyfill)
8 | [](https://david-dm.org/mozilla/webextension-polyfill#info=devDependencies)
9 | [](https://badge.fury.io/js/webextension-polyfill)
10 |
11 | > This library doesn't (and it is not going to) polyfill API methods or options that are missing on Chrome but natively provided
12 | > on Firefox, and so the extension has to do its own "runtime feature detection" in those cases (and then eventually polyfill the
13 | > missing feature on its own or enable/disable some of the features accordingly).
14 |
15 | [w3-browserext]: https://www.w3.org/community/browserext/
16 |
17 | Table of contents
18 | =================
19 |
20 | * [Supported Browsers](#supported-browsers)
21 | * [Installation](#installation)
22 | * [Basic Setup](#basic-setup)
23 | * [Basic Setup with ES6 module loader](#basic-setup-with-es6-module-loader)
24 | * [Basic Setup with module bundlers](#basic-setup-with-module-bundlers)
25 | * [Usage with webpack without bundling](#usage-with-webpack-without-bundling)
26 | * [Using the Promise-based APIs](#using-the-promise-based-apis)
27 | * [Examples](#examples)
28 | * [Usage with TypeScript](#usage-with-typescript)
29 | * [Known Limitations and Incompatibilities](#known-limitations-and-incompatibilities)
30 | * [Contributing to this project](#contributing-to-this-project)
31 |
32 | Supported Browsers
33 | ==================
34 |
35 | | Browser | Support Level |
36 | | ------------------------- | -------------------------------------------------------------------------------------------------- |
37 | | Chrome | *Officially Supported* (with automated tests) |
38 | | Firefox | *Officially Supported as a NO-OP* (with automated tests for comparison with the behaviors on Chrome) |
39 | | Opera / Edge (>=79.0.309) | *Unofficially Supported* as a Chrome-compatible target (but not explicitly tested in automation) |
40 |
41 | The polyfill is being tested explicitly (with automated tests that run on every pull request) on **officially supported**
42 | browsers (that are currently the last stable versions of Chrome and Firefox).
43 |
44 | On Firefox, this library is actually acting as a NO-OP: it detects that the `browser` API object is already defined
45 | and it does not create any custom wrappers.
46 | Firefox is still included in the automated tests, to ensure that no wrappers are being created when running on Firefox,
47 | and for comparison with the behaviors implemented by the library on Chrome.
48 |
49 | ## Installation
50 |
51 | A new version of the library is built from this repository and released as an npm package.
52 |
53 | The npm package is named after this repo: [webextension-polyfill](https://www.npmjs.com/package/webextension-polyfill).
54 |
55 | For the extension that already include a package.json file, the last released version of this library can be quickly installed using:
56 |
57 | ```
58 | npm install --save-dev webextension-polyfill
59 | ```
60 |
61 | Inside the `dist/` directory of the npm package, there are both the minified and non-minified builds (and their related source map files):
62 |
63 | - node_modules/webextension-polyfill/dist/browser-polyfill.js
64 | - node_modules/webextension-polyfill/dist/browser-polyfill.min.js
65 |
66 | For extensions that do not include a package.json file and/or prefer to download and add the library directly into their own code repository, all the versions released on npm are also available for direct download from unpkg.com:
67 |
68 | - https://unpkg.com/webextension-polyfill/dist/
69 |
70 | and linked to the Github releases:
71 |
72 | - https://github.com/mozilla/webextension-polyfill/releases
73 |
74 | ## Basic Setup
75 |
76 | In order to use the polyfill, it must be loaded into any context where `browser` APIs are accessed. The most common cases
77 | are background and content scripts, which can be specified in `manifest.json` (make sure to include the `browser-polyfill.js` script before any other scripts that use it):
78 |
79 | ```javascript
80 | {
81 | // ...
82 |
83 | "background": {
84 | "scripts": [
85 | "browser-polyfill.js",
86 | "background.js"
87 | ]
88 | },
89 |
90 | "content_scripts": [{
91 | // ...
92 | "js": [
93 | "browser-polyfill.js",
94 | "content.js"
95 | ]
96 | }]
97 | }
98 | ```
99 |
100 | For HTML documents, such as `browserAction` popups, or tab pages, it must be
101 | included more explicitly:
102 |
103 | ```html
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | ```
113 |
114 | And for dynamically-injected content scripts loaded by `tabs.executeScript`,
115 | it must be injected by a separate `executeScript` call, unless it has
116 | already been loaded via a `content_scripts` declaration in
117 | `manifest.json`:
118 |
119 | ```javascript
120 | browser.tabs.executeScript({file: "browser-polyfill.js"});
121 | browser.tabs.executeScript({file: "content.js"}).then(result => {
122 | // ...
123 | });
124 | ```
125 |
126 | ### Basic Setup with ES6 module loader
127 |
128 | The polyfill can also be loaded using the native ES6 module loader available in
129 | the recent browsers versions.
130 |
131 | Be aware that the polyfill module does not export the `browser` API object,
132 | but defines the `browser` object in the global namespace (i.e. `window`).
133 |
134 | ```html
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 | ```
144 |
145 | ```javascript
146 | // In background.js (loaded after browser-polyfill.js) the `browser`
147 | // API object is already defined and provides the promise-based APIs.
148 | browser.runtime.onMessage.addListener(...);
149 | ```
150 |
151 | ### Basic Setup with module bundlers
152 |
153 | This library is built as a **UMD module** (Universal Module Definition), and so it can also be used with module bundlers (and explicitly tested on both **webpack** and **browserify**) or AMD module loaders.
154 |
155 | **src/background.js**:
156 | ```javascript
157 | var browser = require("webextension-polyfill");
158 |
159 | browser.runtime.onMessage.addListener(async (msg, sender) => {
160 | console.log("BG page received message", msg, "from", sender);
161 | console.log("Stored data", await browser.storage.local.get());
162 | });
163 |
164 | browser.browserAction.onClicked.addListener(() => {
165 | browser.tabs.executeScript({file: "content.js"});
166 | });
167 | ```
168 |
169 | **src/content.js**:
170 | ```javascript
171 | var browser = require("webextension-polyfill");
172 |
173 | browser.storage.local.set({
174 | [window.location.hostname]: document.title,
175 | }).then(() => {
176 | browser.runtime.sendMessage(`Saved document title for ${window.location.hostname}`);
177 | });
178 | ```
179 |
180 | By using `require("webextension-polyfill")`, the module bundler will use the non-minified version of this library, and the extension is supposed to minify the entire generated bundles as part of its own build steps.
181 |
182 | If the extension doesn't minify its own sources, it is still possible to explicitly ask the module bundler to use the minified version of this library, e.g.:
183 |
184 | ```javascript
185 | var browser = require("webextension-polyfill/dist/browser-polyfill.min");
186 |
187 | ...
188 | ```
189 |
190 | ### Usage with webpack without bundling
191 |
192 | The previous section explains how to bundle `webextension-polyfill` in each script. An alternative method is to include a single copy of the library in your extension, and load the library as shown in [Basic Setup](#basic-setup). You will need to install [copy-webpack-plugin](https://www.npmjs.com/package/copy-webpack-plugin):
193 |
194 | ```sh
195 | npm install --save-dev copy-webpack-plugin
196 | ```
197 |
198 | **In `webpack.config.js`,** import the plugin and configure it this way. It will copy the minified file into your _output_ folder, wherever your other webpack files are generated.
199 |
200 | ```js
201 | const CopyWebpackPlugin = require('copy-webpack-plugin');
202 |
203 | module.exports = {
204 | /* Your regular webpack config, probably including something like this:
205 | output: {
206 | path: path.join(__dirname, 'distribution'),
207 | filename: '[name].js'
208 | },
209 | */
210 | plugins: [
211 | new CopyWebpackPlugin({
212 | patterns: [{
213 | from: 'node_modules/webextension-polyfill/dist/browser-polyfill.js',
214 | }],
215 | })
216 | ]
217 | }
218 | ```
219 |
220 | And then include the file in each context, using the `manifest.json` just like in [Basic Setup](#basic-setup).
221 |
222 | ## Using the Promise-based APIs
223 |
224 | The Promise-based APIs in the `browser` namespace work, for the most part,
225 | very similarly to the callback-based APIs in Chrome's `chrome` namespace.
226 | The major differences are:
227 |
228 | * Rather than receiving a callback argument, every async function returns a
229 | `Promise` object, which resolves or rejects when the operation completes.
230 |
231 | * Rather than checking the `chrome.runtime.lastError` property from every
232 | callback, code which needs to explicitly deal with errors registers a
233 | separate Promise rejection handler.
234 |
235 | * Rather than receiving a `sendResponse` callback to send a response,
236 | `onMessage` listeners simply return a Promise whose resolution value is
237 | used as a reply.
238 |
239 | * Rather than nesting callbacks when a sequence of operations depend on each
240 | other, Promise chaining is generally used instead.
241 |
242 | * The resulting Promises can be also used with `async` and `await`, rather
243 | than dealt with directly.
244 |
245 | ## Examples
246 |
247 | The following code will retrieve a list of URLs patterns from the `storage`
248 | API, retrieve a list of tabs which match any of them, reload each of those
249 | tabs, and notify the user that is has been done:
250 |
251 | ```javascript
252 | browser.storage.local.get("urls").then(({urls}) => {
253 | return browser.tabs.query({url: urls});
254 | }).then(tabs => {
255 | return Promise.all(
256 | Array.from(tabs, tab => browser.tabs.reload(tab.id))
257 | );
258 | }).then(() => {
259 | return browser.notifications.create({
260 | type: "basic",
261 | iconUrl: "icon.png",
262 | title: "Tabs reloaded",
263 | message: "Your tabs have been reloaded",
264 | });
265 | }).catch(error => {
266 | console.error(`An error occurred while reloading tabs: ${error.message}`);
267 | });
268 | ```
269 |
270 | Or, using an async function:
271 |
272 | ```javascript
273 | async function reloadTabs() {
274 | try {
275 | let {urls} = await browser.storage.local.get("urls");
276 |
277 | let tabs = await browser.tabs.query({url: urls});
278 |
279 | await Promise.all(
280 | Array.from(tabs, tab => browser.tabs.reload(tab.id))
281 | );
282 |
283 | await browser.notifications.create({
284 | type: "basic",
285 | iconUrl: "icon.png",
286 | title: "Tabs reloaded",
287 | message: "Your tabs have been reloaded",
288 | });
289 | } catch (error) {
290 | console.error(`An error occurred while reloading tabs: ${error.message}`);
291 | }
292 | }
293 | ```
294 |
295 | It's also possible to use Promises effectively using two-way messaging.
296 | Communication between a background page and a tab content script, for example,
297 | looks something like this from the background page side:
298 |
299 | ```javascript
300 | browser.tabs.sendMessage(tabId, "get-ids").then(results => {
301 | processResults(results);
302 | });
303 | ```
304 |
305 | And like this from the content script:
306 |
307 | ```javascript
308 | browser.runtime.onMessage.addListener(msg => {
309 | if (msg == "get-ids") {
310 | return browser.storage.local.get("idPattern").then(({idPattern}) => {
311 | return Array.from(document.querySelectorAll(idPattern),
312 | elem => elem.textContent);
313 | });
314 | }
315 | });
316 | ```
317 |
318 | or:
319 |
320 | ```javascript
321 | browser.runtime.onMessage.addListener(async function(msg) {
322 | if (msg == "get-ids") {
323 | let {idPattern} = await browser.storage.local.get("idPattern");
324 |
325 | return Array.from(document.querySelectorAll(idPattern),
326 | elem => elem.textContent);
327 | }
328 | });
329 | ```
330 |
331 | Or vice versa.
332 |
333 | ## Usage with TypeScript
334 |
335 | There are multiple projects that add TypeScript support to your web-extension project:
336 |
337 | | Project | Description |
338 | | ------------- | ------------- |
339 | | [@types/webextension-polyfill](https://www.npmjs.com/package/@types/webextension-polyfill) | Types and JS-Doc are automatically generated from the mozilla schema files, so it is always up-to-date with the latest APIs. Formerly known as [webextension-polyfill-ts](https://github.com/Lusito/webextension-polyfill-ts). |
340 | | [web-ext-types](https://github.com/kelseasy/web-ext-types) | Manually maintained types based on MDN's documentation. No JS-Doc included. |
341 | | [@types/chrome](https://www.npmjs.com/package/@types/chrome) | Manually maintained types and JS-Doc. Only contains types for chrome extensions though! |
342 |
343 | ## Known Limitations and Incompatibilities
344 |
345 | This library tries to minimize the amount of "special handling" that a cross-browser extension has to do to be able to run on the supported browsers from a single codebase, but there are still cases when polyfillling the missing or incompatible behaviors or features is not possible or out of the scope of this polyfill.
346 |
347 | This section aims to keep track of the most common issues that an extension may have.
348 |
349 | ### No callback supported by the Promise-based APIs on Chrome
350 |
351 | While some of the asynchronous API methods in Firefox (the ones that return a promise) also support the callback parameter (mostly as a side effect of the backward compatibility with the callback-based APIs available on Chrome), the Promise-based APIs provided by this library do not support the callback parameter (See ["#102 Cannot call browser.storage.local.get with callback"][I-102]).
352 |
353 | ### No promise returned on Chrome for some API methods
354 |
355 | This library takes its knowledge of the APIs to wrap and their signatures from a metadata JSON file:
356 | [api-metadata.json](api-metadata.json).
357 |
358 | If an API method is not yet included in this "API metadata" file, it will not be recognized.
359 | Promises are not supported for unrecognized APIs, and callbacks have to be used for them.
360 |
361 | Chrome-only APIs have no promise version, because extensions that use such APIs
362 | would not be compatible with Firefox.
363 |
364 | File an issue in this repository for API methods that support callbacks in Chrome *and*
365 | Firefox but are currently missing from the "API metadata" file.
366 |
367 | ### Issues that happen only when running on Firefox
368 |
369 | When an extension that uses this library doesn't behave as expected on Firefox, it is almost never an issue in this polyfill, but an issue with the native implementation in Firefox.
370 |
371 | "Firefox only" issues should be reported upstream on Bugzilla:
372 | - https://bugzilla.mozilla.org/enter_bug.cgi?product=WebExtensions&component=Untriaged
373 |
374 | ### API methods or options that are only available when running in Firefox
375 |
376 | This library does not provide any polyfill for API methods and options that are only available on Firefox, and they are actually considered out of the scope of this library.
377 |
378 | ### tabs.executeScript
379 |
380 | On Firefox `browser.tabs.executeScript` returns a promise which resolves to the result of the content script code that has been executed, which can be an immediate value or a Promise.
381 |
382 | On Chrome, the `browser.tabs.executeScript` API method as polyfilled by this library also returns a promise which resolves to the result of the content script code, but only immediate values are supported.
383 | If the content script code result is a Promise, the promise returned by `browser.tabs.executeScript` will be resolved to `undefined`.
384 |
385 | ### MSEdge support
386 |
387 | MSEdge versions >= 79.0.309 are unofficially supported as a Chrome-compatible target (as for Opera or other Chrome-based browsers that also support extensions).
388 |
389 | MSEdge versions older than 79.0.309 are **unsupported**, for extension developers that still have to work on extensions for older MSEdge versions, the MSEdge `--ms-preload` manifest key and the [Microsoft Edge Extension Toolkit](https://docs.microsoft.com/en-us/microsoft-edge/extensions/guides/porting-chrome-extensions)'s Chrome API bridge can be used to be able to load the webextension-polyfill without any MSEdge specific changes.
390 |
391 | The following Github repository provides some additional detail about this strategy and a minimal test extension that shows how to put it together:
392 |
393 | - https://github.com/rpl/example-msedge-extension-with-webextension-polyfill
394 |
395 | ## Contributing to this project
396 |
397 | Read the [contributing section](CONTRIBUTING.md) for additional information about how to build the library from this repository and how to contribute and test changes.
398 |
399 | [PR-114]: https://github.com/mozilla/webextension-polyfill/pull/114
400 | [I-102]: https://github.com/mozilla/webextension-polyfill/issues/102#issuecomment-379365343
401 |
--------------------------------------------------------------------------------
/node_modules/webextension-polyfill/dist/browser-polyfill.min.js:
--------------------------------------------------------------------------------
1 | (function(a,b){if("function"==typeof define&&define.amd)define("webextension-polyfill",["module"],b);else if("undefined"!=typeof exports)b(module);else{var c={exports:{}};b(c),a.browser=c.exports}})("undefined"==typeof globalThis?"undefined"==typeof self?this:self:globalThis,function(a){"use strict";if(!globalThis.chrome?.runtime?.id)throw new Error("This script should only be loaded in a browser extension.");if("undefined"==typeof globalThis.browser||Object.getPrototypeOf(globalThis.browser)!==Object.prototype){a.exports=(a=>{const b={alarms:{clear:{minArgs:0,maxArgs:1},clearAll:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getAll:{minArgs:0,maxArgs:0}},bookmarks:{create:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},getChildren:{minArgs:1,maxArgs:1},getRecent:{minArgs:1,maxArgs:1},getSubTree:{minArgs:1,maxArgs:1},getTree:{minArgs:0,maxArgs:0},move:{minArgs:2,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeTree:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}},browserAction:{disable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},enable:{minArgs:0,maxArgs:1,fallbackToNoCallback:!0},getBadgeBackgroundColor:{minArgs:1,maxArgs:1},getBadgeText:{minArgs:1,maxArgs:1},getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},openPopup:{minArgs:0,maxArgs:0},setBadgeBackgroundColor:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setBadgeText:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},browsingData:{remove:{minArgs:2,maxArgs:2},removeCache:{minArgs:1,maxArgs:1},removeCookies:{minArgs:1,maxArgs:1},removeDownloads:{minArgs:1,maxArgs:1},removeFormData:{minArgs:1,maxArgs:1},removeHistory:{minArgs:1,maxArgs:1},removeLocalStorage:{minArgs:1,maxArgs:1},removePasswords:{minArgs:1,maxArgs:1},removePluginData:{minArgs:1,maxArgs:1},settings:{minArgs:0,maxArgs:0}},commands:{getAll:{minArgs:0,maxArgs:0}},contextMenus:{remove:{minArgs:1,maxArgs:1},removeAll:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},cookies:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:1,maxArgs:1},getAllCookieStores:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},devtools:{inspectedWindow:{eval:{minArgs:1,maxArgs:2,singleCallbackArg:!1}},panels:{create:{minArgs:3,maxArgs:3,singleCallbackArg:!0},elements:{createSidebarPane:{minArgs:1,maxArgs:1}}}},downloads:{cancel:{minArgs:1,maxArgs:1},download:{minArgs:1,maxArgs:1},erase:{minArgs:1,maxArgs:1},getFileIcon:{minArgs:1,maxArgs:2},open:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},pause:{minArgs:1,maxArgs:1},removeFile:{minArgs:1,maxArgs:1},resume:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},extension:{isAllowedFileSchemeAccess:{minArgs:0,maxArgs:0},isAllowedIncognitoAccess:{minArgs:0,maxArgs:0}},history:{addUrl:{minArgs:1,maxArgs:1},deleteAll:{minArgs:0,maxArgs:0},deleteRange:{minArgs:1,maxArgs:1},deleteUrl:{minArgs:1,maxArgs:1},getVisits:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1}},i18n:{detectLanguage:{minArgs:1,maxArgs:1},getAcceptLanguages:{minArgs:0,maxArgs:0}},identity:{launchWebAuthFlow:{minArgs:1,maxArgs:1}},idle:{queryState:{minArgs:1,maxArgs:1}},management:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},getSelf:{minArgs:0,maxArgs:0},setEnabled:{minArgs:2,maxArgs:2},uninstallSelf:{minArgs:0,maxArgs:1}},notifications:{clear:{minArgs:1,maxArgs:1},create:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:0},getPermissionLevel:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},pageAction:{getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},hide:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setIcon:{minArgs:1,maxArgs:1},setPopup:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},setTitle:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0},show:{minArgs:1,maxArgs:1,fallbackToNoCallback:!0}},permissions:{contains:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},request:{minArgs:1,maxArgs:1}},runtime:{getBackgroundPage:{minArgs:0,maxArgs:0},getPlatformInfo:{minArgs:0,maxArgs:0},openOptionsPage:{minArgs:0,maxArgs:0},requestUpdateCheck:{minArgs:0,maxArgs:0},sendMessage:{minArgs:1,maxArgs:3},sendNativeMessage:{minArgs:2,maxArgs:2},setUninstallURL:{minArgs:1,maxArgs:1}},sessions:{getDevices:{minArgs:0,maxArgs:1},getRecentlyClosed:{minArgs:0,maxArgs:1},restore:{minArgs:0,maxArgs:1}},storage:{local:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},managed:{get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1}},sync:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}}},tabs:{captureVisibleTab:{minArgs:0,maxArgs:2},create:{minArgs:1,maxArgs:1},detectLanguage:{minArgs:0,maxArgs:1},discard:{minArgs:0,maxArgs:1},duplicate:{minArgs:1,maxArgs:1},executeScript:{minArgs:1,maxArgs:2},get:{minArgs:1,maxArgs:1},getCurrent:{minArgs:0,maxArgs:0},getZoom:{minArgs:0,maxArgs:1},getZoomSettings:{minArgs:0,maxArgs:1},goBack:{minArgs:0,maxArgs:1},goForward:{minArgs:0,maxArgs:1},highlight:{minArgs:1,maxArgs:1},insertCSS:{minArgs:1,maxArgs:2},move:{minArgs:2,maxArgs:2},query:{minArgs:1,maxArgs:1},reload:{minArgs:0,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeCSS:{minArgs:1,maxArgs:2},sendMessage:{minArgs:2,maxArgs:3},setZoom:{minArgs:1,maxArgs:2},setZoomSettings:{minArgs:1,maxArgs:2},update:{minArgs:1,maxArgs:2}},topSites:{get:{minArgs:0,maxArgs:0}},webNavigation:{getAllFrames:{minArgs:1,maxArgs:1},getFrame:{minArgs:1,maxArgs:1}},webRequest:{handlerBehaviorChanged:{minArgs:0,maxArgs:0}},windows:{create:{minArgs:0,maxArgs:1},get:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:1},getCurrent:{minArgs:0,maxArgs:1},getLastFocused:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}}};if(0===Object.keys(b).length)throw new Error("api-metadata.json has not been included in browser-polyfill");class c extends WeakMap{constructor(a,b=void 0){super(b),this.createItem=a}get(a){return this.has(a)||this.set(a,this.createItem(a)),super.get(a)}}const d=a=>a&&"object"==typeof a&&"function"==typeof a.then,e=(b,c)=>(...d)=>{a.runtime.lastError?b.reject(new Error(a.runtime.lastError.message)):c.singleCallbackArg||1>=d.length&&!1!==c.singleCallbackArg?b.resolve(d[0]):b.resolve(d)},f=a=>1==a?"argument":"arguments",g=(a,b)=>function(c,...d){if(d.lengthb.maxArgs)throw new Error(`Expected at most ${b.maxArgs} ${f(b.maxArgs)} for ${a}(), got ${d.length}`);return new Promise((f,g)=>{if(b.fallbackToNoCallback)try{c[a](...d,e({resolve:f,reject:g},b))}catch(e){console.warn(`${a} API method doesn't seem to support the callback parameter, `+"falling back to call it without a callback: ",e),c[a](...d),b.fallbackToNoCallback=!1,b.noCallback=!0,f()}else b.noCallback?(c[a](...d),f()):c[a](...d,e({resolve:f,reject:g},b))})},h=(a,b,c)=>new Proxy(b,{apply(b,d,e){return c.call(d,a,...e)}});let i=Function.call.bind(Object.prototype.hasOwnProperty);const j=(a,b={},c={})=>{let d=Object.create(null),e=Object.create(a);return new Proxy(e,{has(b,c){return c in a||c in d},get(e,f){if(f in d)return d[f];if(!(f in a))return;let k=a[f];if("function"==typeof k){if("function"==typeof b[f])k=h(a,a[f],b[f]);else if(i(c,f)){let b=g(f,c[f]);k=h(a,a[f],b)}else k=k.bind(a);}else if("object"==typeof k&&null!==k&&(i(b,f)||i(c,f)))k=j(k,b[f],c[f]);else if(i(c,"*"))k=j(k,b[f],c["*"]);else return Object.defineProperty(d,f,{configurable:!0,enumerable:!0,get(){return a[f]},set(b){a[f]=b}}),k;return d[f]=k,k},set(b,c,e){return c in d?d[c]=e:a[c]=e,!0},defineProperty(a,b,c){return Reflect.defineProperty(d,b,c)},deleteProperty(a,b){return Reflect.deleteProperty(d,b)}})},k=a=>({addListener(b,c,...d){b.addListener(a.get(c),...d)},hasListener(b,c){return b.hasListener(a.get(c))},removeListener(b,c){b.removeListener(a.get(c))}}),l=new c(a=>"function"==typeof a?function(b){const c=j(b,{},{getContent:{minArgs:0,maxArgs:0}});a(c)}:a),m=new c(a=>"function"==typeof a?function(b,c,e){let f,g,h=!1,i=new Promise(a=>{f=function(b){h=!0,a(b)}});try{g=a(b,c,f)}catch(a){g=Promise.reject(a)}const j=!0!==g&&d(g);if(!0!==g&&!j&&!h)return!1;const k=a=>{a.then(a=>{e(a)},a=>{let b;b=a&&(a instanceof Error||"string"==typeof a.message)?a.message:"An unexpected error occurred",e({__mozWebExtensionPolyfillReject__:!0,message:b})}).catch(a=>{console.error("Failed to send onMessage rejected reply",a)})};return j?k(g):k(i),!0}:a),n=({reject:b,resolve:c},d)=>{a.runtime.lastError?a.runtime.lastError.message==="The message port closed before a response was received."?c():b(new Error(a.runtime.lastError.message)):d&&d.__mozWebExtensionPolyfillReject__?b(new Error(d.message)):c(d)},o=(a,b,c,...d)=>{if(d.lengthb.maxArgs)throw new Error(`Expected at most ${b.maxArgs} ${f(b.maxArgs)} for ${a}(), got ${d.length}`);return new Promise((a,b)=>{const e=n.bind(null,{resolve:a,reject:b});d.push(e),c.sendMessage(...d)})},p={devtools:{network:{onRequestFinished:k(l)}},runtime:{onMessage:k(m),onMessageExternal:k(m),sendMessage:o.bind(null,"sendMessage",{minArgs:1,maxArgs:3})},tabs:{sendMessage:o.bind(null,"sendMessage",{minArgs:2,maxArgs:3})}},q={clear:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}};return b.privacy={network:{"*":q},services:{"*":q},websites:{"*":q}},j(a,p,b)})(chrome)}else a.exports=globalThis.browser});
2 | //# sourceMappingURL=browser-polyfill.min.js.map
3 |
4 | // webextension-polyfill v.0.10.0 (https://github.com/mozilla/webextension-polyfill)
5 |
6 | /* This Source Code Form is subject to the terms of the Mozilla Public
7 | * License, v. 2.0. If a copy of the MPL was not distributed with this
8 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 |
--------------------------------------------------------------------------------
/node_modules/webextension-polyfill/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webextension-polyfill",
3 | "version": "0.10.0",
4 | "description": "A lightweight polyfill library for Promise-based WebExtension APIs in Chrome.",
5 | "main": "dist/browser-polyfill.js",
6 | "files": [
7 | "dist"
8 | ],
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/mozilla/webextension-polyfill.git"
12 | },
13 | "author": "Mozilla",
14 | "license": "MPL-2.0",
15 | "bugs": {
16 | "url": "https://github.com/mozilla/webextension-polyfill/issues"
17 | },
18 | "homepage": "https://github.com/mozilla/webextension-polyfill",
19 | "devDependencies": {
20 | "@babel/core": "7.18.10",
21 | "@babel/preset-env": "7.18.10",
22 | "@babel/register": "7.18.9",
23 | "@babel/eslint-parser": "7.18.9",
24 | "babel-preset-minify": "0.5.2",
25 | "browserify": "17.0.0",
26 | "chai": "4.3.6",
27 | "chromedriver": "104.0.0",
28 | "cross-env": "7.0.3",
29 | "eslint": "8.21.0",
30 | "finalhandler": "1.2.0",
31 | "geckodriver": "3.0.2",
32 | "global-replaceify": "1.0.0",
33 | "grunt": "1.5.3",
34 | "grunt-babel": "8.0.0",
35 | "grunt-contrib-concat": "2.1.0",
36 | "grunt-replace": "2.0.2",
37 | "istanbul-lib-instrument": "5.2.0",
38 | "jsdom": "20.0.0",
39 | "mocha": "10.0.0",
40 | "nyc": "15.1.0",
41 | "selenium-webdriver": "4.4.0",
42 | "serve-static": "1.15.0",
43 | "shelljs": "0.8.5",
44 | "sinon": "14.0.0",
45 | "tape": "5.5.3",
46 | "tape-async": "2.3.0",
47 | "tmp": "0.2.1"
48 | },
49 | "nyc": {
50 | "reporter": [
51 | "lcov",
52 | "text",
53 | "html"
54 | ],
55 | "instrument": false
56 | },
57 | "scripts": {
58 | "build": "npm run lint && grunt",
59 | "prepublish": "npm run build && npm run test",
60 | "test": "mocha",
61 | "lint": "eslint *.js src/*.js scripts/**/*.js test/**/*.js",
62 | "test-coverage": "cross-env COVERAGE=y nyc mocha",
63 | "test-minified": "cross-env TEST_MINIFIED_POLYFILL=1 mocha",
64 | "test-integration": "tape test/integration/test-*",
65 | "test-integration:chrome": "cross-env TEST_BROWSER_TYPE=chrome npm run test-integration",
66 | "test-integration:firefox": "cross-env TEST_BROWSER_TYPE=firefox npm run test-integration"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lemmy-instance-assistant",
3 | "lockfileVersion": 3,
4 | "requires": true,
5 | "packages": {
6 | "": {
7 | "devDependencies": {
8 | "webextension-polyfill": "^0.10.0"
9 | }
10 | },
11 | "node_modules/webextension-polyfill": {
12 | "version": "0.10.0",
13 | "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz",
14 | "integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==",
15 | "dev": true
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "webextension-polyfill": "^0.10.0"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/_locales/de/messages.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/src/_locales/en/messages.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/src/_locales/fr/messages.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/src/background.js:
--------------------------------------------------------------------------------
1 | // --------------------------------------
2 | // COPIED FROM utils.js
3 | function getStorageAPI() {
4 | let storageAPI;
5 | if (typeof browser !== 'undefined' && browser.storage && browser.storage.local) {
6 | storageAPI = browser.storage.local;
7 | } else if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
8 | storageAPI = chrome.storage.local;
9 | } else {
10 | throw new Error('Storage API is not supported in this browser.');
11 | }
12 |
13 | return storageAPI;
14 | }
15 | function getBrowserAPI() {
16 | let browserAPI;
17 | if (typeof browser !== 'undefined' && browser.storage && browser.storage.local) {
18 | browserAPI = browser;
19 | } else if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) {
20 | browserAPI = chrome;
21 | } else {
22 | throw new Error('Browser API is not supported in this browser.');
23 | }
24 | return browserAPI;
25 | }
26 | async function getAllSettings() {
27 | const storageAPI = getStorageAPI();
28 | const allSettings = await storageAPI.get('settings');
29 | if (!allSettings || !allSettings.settings) {
30 | await setAllSettings(defaultSettings);
31 | return defaultSettings;
32 | }
33 | return await storageAPI.get('settings');
34 | }
35 | async function getSetting(settingName) {
36 | const allSettings = await getAllSettings();
37 | return allSettings.settings[settingName];
38 | }
39 | async function hasSelectedInstance() {
40 | const selectedInstance = await getSetting('selectedInstance');
41 | return selectedInstance !== undefined && selectedInstance !== "";
42 | }
43 | async function hasSelectedType() {
44 | const selectedType = await getSetting('selectedType');
45 | return selectedType !== undefined && selectedType !== "";
46 | }
47 | async function loadStorage(key) {
48 | const { value } = await browser.storage.local.get(key);
49 | return value;
50 | }
51 | async function p2l_getPostData() {
52 | const storageAPI = getBrowserAPI();
53 | return new Promise((resolve, reject) => {
54 | storageAPI.tabs.query({ active: true, currentWindow: true }, function (tabs) {
55 | const activeTab = tabs[0];
56 | const postData = {
57 | title: activeTab.title,
58 | url: activeTab.url
59 | };
60 | resolve(postData);
61 | });
62 | });
63 | }
64 | // --------------------------------------
65 |
66 |
67 | // --------------------------------------
68 | // Handle redirects within a Lemmy site
69 | // - Sometimes when navigating within a Lemmy site, the content scripts won't run despite the URL matching the pattern. This is a workaround for that.
70 | // --------------------------------------
71 | browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
72 | if (changeInfo.status === "complete") {
73 | if (
74 | /^https?:\/\/.*\/c\//.test(tab.url) ||
75 | /^https?:\/\/.*\/communities\//.test(tab.url) ||
76 | /^https?:\/\/.*\/post\//.test(tab.url)
77 | ) {
78 | browser.tabs.executeScript(tabId, { file: "utils.js" })
79 | browser.tabs.executeScript(tabId, { file: "content-general.js" })
80 | browser.tabs.executeScript(tabId, { file: "content-sidebar.js" })
81 | }
82 | }
83 | });
84 |
85 |
86 | // --------------------------------------
87 | // Handle context menu clicks
88 | // --------------------------------------
89 | browser.contextMenus.onClicked.addListener(async (info, tab) => {
90 | if (info.menuItemId === "redirect" && info.linkUrl) {
91 |
92 | let sourceHost = new URL(info.linkUrl).hostname;
93 | let sourcePath = new URL(info.linkUrl).pathname;
94 |
95 | if (sourcePath.includes("/c/") || sourcePath.includes("/m/")) {
96 | const communityName = sourcePath.match(/\/[cm]\/([^/@]+)/)[1];
97 | const sourceInstance = sourcePath.includes("@") ?
98 | sourcePath.match(/\/[cm]\/[^/@]+@([^/]+)/)[1] : sourceHost;
99 |
100 | async function loadStorage() {
101 |
102 | const selectedInstance = await getSetting('selectedInstance');
103 |
104 | const { selectedType } = await browser.storage.local.get('selectedType');
105 | communityPrefix = selectedType ? (selectedType === "lemmy" ? "/c/" : "/m/") : "/c/";
106 | const redirectURL = selectedInstance + communityPrefix + communityName + '@' + sourceInstance;
107 | browser.tabs.update(tab.id, { url: redirectURL });
108 | }
109 | loadStorage();
110 | } else {
111 | browser.tabs.update(tab.id, { url: 'https://github.com/cynber/lemmy-instance-assistant/wiki/Sorry-that-didn\'t-work...' });
112 | // TODO: Add a popup to explain this
113 | }
114 | } else if (info.menuItemId === "post-image" && info.srcUrl) {
115 |
116 | if (await hasSelectedInstance() && await hasSelectedType()) {
117 |
118 | const type = await getSetting("selectedType");
119 | const instance = await getSetting("selectedInstance");
120 | const postData = await p2l_getPostData();
121 |
122 | if (type === "lemmy") {
123 | const url = instance + "/create_post";
124 | const createdTab = await browser.tabs.create({ url: url });
125 |
126 | // Listen for tab updates to check for loading completion
127 | browser.tabs.onUpdated.addListener(function listener(tabId, changeInfo) {
128 | if (tabId === createdTab.id && changeInfo.status === "complete") {
129 | browser.tabs.onUpdated.removeListener(listener); // Remove the listener
130 |
131 | // Fill in form after the tab is fully loaded
132 | browser.tabs.executeScript(createdTab.id, {
133 | code: `
134 | const EVENT_OPTIONS = {bubbles: true, cancelable: false, composed: true};
135 | const EVENTS = {
136 | BLUR: new Event("blur", EVENT_OPTIONS),
137 | CHANGE: new Event("change", EVENT_OPTIONS),
138 | INPUT: new Event("input", EVENT_OPTIONS),
139 | };
140 |
141 | const postTitleInput = document.querySelector("#post-title");
142 | const postURLInput = document.querySelector("#post-url");
143 | const postBodyInput = document.querySelector("textarea[id^='markdown-textarea-']");
144 |
145 | postTitleInput.select();
146 | postTitleInput.value = "${postData.title}";
147 | postTitleInput.dispatchEvent(EVENTS.INPUT);
148 |
149 | postURLInput.select();
150 | postURLInput.value = "${info.srcUrl}";
151 | postURLInput.dispatchEvent(EVENTS.INPUT);
152 |
153 | postBodyInput.select();
154 | postBodyInput.value = "Source: ${postData.url}";
155 | postBodyInput.dispatchEvent(EVENTS.INPUT);
156 | `
157 | });
158 |
159 | window.close(); // Close the popup
160 | }
161 | });
162 |
163 | } else if (type === "kbin") {
164 | const url = instance + "/new?url=" + info.srcUrl + "&title=" + postData.title + "&body=Source: " + postData.url;
165 | await browser.tabs.create({ url: url });
166 | }
167 |
168 | } else { alert("No valid instance has been set. Please select an instance in the popup using 'Change my home instance'."); }
169 | }
170 | });
171 |
172 | browser.contextMenus.create({
173 | id: "redirect",
174 | title: "Redirect to home instance",
175 | contexts: ["link"],
176 | targetUrlPatterns: ["http://*/c/*", "https://*/c/*", "http://*/p/*", "https://*/p/*", "http://*/m/*", "https://*/m/*"],
177 | }, () => void browser.runtime.lastError,
178 | );
179 |
180 | browser.contextMenus.create({
181 | id: "post-image",
182 | title: "Post this image",
183 | contexts: ["image"],
184 | targetUrlPatterns: ["http://*/*", "https://*/*"]
185 | }, () => void browser.runtime.lastError,
186 | );
187 |
188 |
189 | // --------------------------------------
190 | // Set default values on install/update
191 | // --------------------------------------
192 |
193 | browser.runtime.onInstalled.addListener(async ({ reason }) => {
194 | // Set default values on install/update
195 | // TODO: fix the utils import so we can just use initializeSettingsWithDefaults()
196 | if (reason === 'install' || reason === 'update') {
197 |
198 | async function backgroundInitializeSettings() {
199 |
200 | const defaultSettings = {
201 | hideSidebarLemmy: false,
202 | hideSidebarKbin: false,
203 | instanceList: [
204 | { name: "lemmy.world", url: "https://lemmy.world" },
205 | { name: "lemmy.ca", url: "https://lemmy.ca" },
206 | { name: "lemm.ee", url: "https://lemm.ee" },
207 | { name: "kbin.social", url: "https://kbin.social" },
208 | ],
209 | runOnCommunitySidebar: true,
210 | runOnCommunityNotFound: true,
211 | hideHelp: false,
212 | selectedInstance: '', // users are forced to set this
213 | selectedType: 'lemmy', // lemmy or kbin
214 | theme: 'dark', // **NOT IMPLEMENTED YET**
215 | toolSearchCommunity_openInLemmyverse: false,
216 | };
217 |
218 | let storageAPI = browser.storage.local;
219 | let allSettings = await storageAPI.get('settings');
220 |
221 | if (!allSettings || !allSettings.settings) {
222 | await storageAPI.set({ 'settings': defaultSettings });
223 | } else {
224 | for (const settingName of Object.keys(defaultSettings)) {
225 | if (!allSettings.settings.hasOwnProperty(settingName)) {
226 | allSettings.settings[settingName] = defaultSettings[settingName];
227 | }
228 | }
229 | await storageAPI.set({ 'settings': allSettings.settings });
230 | }
231 | }
232 | await backgroundInitializeSettings();
233 | }
234 |
235 | // Open settings page when extension is first installed
236 | if (reason === 'install') {
237 | browser.tabs.create({ url: 'page-settings/settings.html' });
238 | }
239 | });
--------------------------------------------------------------------------------
/src/content-communityNotFound.js:
--------------------------------------------------------------------------------
1 | // ============================================================= //
2 | // Injects buttons and links into the community not found page to help users find their way. //
3 | // ============================================================= //
4 |
5 | setTimeout(() => {
6 | const testURL = window.location.href;
7 |
8 | if ((isLemmyCommunityNotFound(testURL))) {
9 | async function loadSelectedInstance() {
10 |
11 | // ------ Set up general variables ------ //
12 |
13 | const CURRENT_HOST = new URL(window.location.href).hostname;
14 | const CURRENT_PATH = new URL(window.location.href).pathname;
15 | const targetCommunity = CURRENT_PATH.match(/\/c\/(.+?)@/)[1];
16 | const targetInstance = CURRENT_PATH.match(/@(.+)/)[1];
17 |
18 | const selectedInstance = await getSetting('selectedInstance');
19 | let TARGET_ELEMENT = document.querySelector('.error-page');
20 |
21 | // --------- Set up injectables --------- //
22 | let createButton = (text) => {
23 | const button = document.createElement('button');
24 | button.setAttribute('type', 'button');
25 | button.textContent = text;
26 | button.setAttribute('id', 'instance-assistant-sidebar');
27 | return button;
28 | };
29 |
30 | const createMessage = (text) => {
31 | const paragraph = document.createElement('p');
32 | paragraph.innerHTML = text;
33 | paragraph.setAttribute('id', 'instance-assistant-sidebar');
34 | return paragraph;
35 | };
36 |
37 | const createDropdown = (text, options) => {
38 | const container = document.createElement('div');
39 | const dropdownText = document.createElement('p');
40 | const dropdownList = document.createElement('ul');
41 | dropdownText.innerHTML = "▼ " + text + " ▼";
42 | dropdownText.style.cssText = `cursor: pointer; font-size: 0.8rem; color: #939496;; text-decoration: underline;`;
43 | dropdownList.style.cssText = `list-style: none; padding: 0; margin: 0; font-size: 0.8rem; color: #939496;`;
44 |
45 | dropdownList.style.display = 'none';
46 | dropdownText.addEventListener('click', () => {
47 | dropdownList.style.display = dropdownList.style.display === 'none' ? 'block' : 'none';
48 | dropdownText.innerHTML = dropdownList.style.display === 'none' ? "▼ " + text + " ▼" : "▲ " + text + " ▲";
49 | });
50 |
51 | options.forEach((option) => {
52 | const listItem = document.createElement('li');
53 | listItem.innerHTML = option;
54 | listItem.style.cssText = `padding: 0.5rem 0rem;`;
55 | dropdownList.appendChild(listItem);
56 | });
57 | container.appendChild(dropdownText);
58 | container.appendChild(dropdownList);
59 | return container;
60 | };
61 |
62 | const container = document.createElement('div');
63 | container.setAttribute('id', 'instance-assistant-sidebar');
64 | container.style.cssText = `
65 | background-color: #1f1f1f;
66 | padding: 10px;
67 | border-radius: 5px;
68 | border: 2px solid #2f2f2f;
69 | margin-bottom: 10px;
70 | `;
71 |
72 | const txtErrorPage = createMessage(`Did you arrive here from Instance Assistant?
The community ` + targetCommunity + ` does not exist on this instance (yet). This can happen if you are the first person to try and open it in this instance. Someone will need to prompt this instance to fetch the community from the original instance. This task can be trigerred by entering the community URL (ex. ` + targetInstance + `/c/` + targetCommunity + `) or identifier (ex. !` + targetCommunity + `@` + targetInstance + `) into the search page (reference).
You can do this by clicking on the button below, and then coming back after some time. Don't worry about the "No results" message, the fetch process would have started in the background. Alternatively, you can copy one of the codes above and do the search manually at https://` + CURRENT_HOST + `/search.\n\n You can also just view the community on the foreign instance.
`)
73 |
74 | let btnOpenSearchLemmy = createButton('Trigger a search');
75 | btnOpenSearchLemmy.style.cssText = `
76 | padding: .375rem .75rem;
77 | margin: 0rem 2rem 2rem 2rem;
78 | width: 50%;
79 | border: none;
80 | border-radius: 5px;
81 | font-weight: 400;
82 | text-align: center;
83 | color: white;
84 | `;
85 | btnOpenSearchLemmy.style.backgroundColor = '#175a4c';
86 |
87 | let btnHomeLemmy = createButton('Go to my home instance');
88 | btnHomeLemmy.style.cssText = `
89 | padding: .375rem .75rem;
90 | margin: 1rem 2rem .5rem 2rem;
91 | width: 50%;
92 | border: none;
93 | border-radius: 5px;
94 | font-weight: 400;
95 | text-align: center;
96 | color: white;
97 | `;
98 | btnHomeLemmy.style.backgroundColor = '#5f35ae';
99 |
100 | let btnCommunityLemmy = createButton('Open community on foreign instance');
101 | btnCommunityLemmy.style.cssText = `
102 | padding: .375rem .75rem;
103 | margin: 1rem 2rem .5rem 2rem;
104 | width: 50%;
105 | border: none;
106 | border-radius: 5px;
107 | font-weight: 400;
108 | text-align: center;
109 | color: white;
110 | `;
111 | btnCommunityLemmy.style.backgroundColor = '#363636';
112 |
113 | let txtHomeInstance = selectedInstance ? createMessage(`Your home instance is ${selectedInstance}.`) : createMessage(`You have not set a home instance yet.`);
114 |
115 | const changeInstanceInstructions = [
116 | '1) Click on the extension icon in the browser toolbar',
117 | '2) Press "Change my home instance" and type in your home instance URL',
118 | '3) Press "Toggle home instance type" to switch between "Lemmy" and "Kbin". (default is "Lemmy")',
119 | ];
120 |
121 | const txtChangeInstance = createDropdown('How to change home instance', changeInstanceInstructions);
122 |
123 | // --------- Add Event Listeners -------- //
124 | btnHomeLemmy.addEventListener('click', () => {
125 | if (selectedInstance) {
126 | window.location.href = selectedInstance;
127 | } else { alert('No valid instance has been set.') }
128 | });
129 |
130 | btnOpenSearchLemmy.addEventListener('click', () => {
131 | window.location.href = 'https://' + CURRENT_HOST + '/search?q=' + '!' + targetCommunity + '%40' + targetInstance + '&type=All&listingType=All&page=1&sort=TopAll';
132 | });
133 |
134 | btnCommunityLemmy.addEventListener('click', () => {
135 | window.location.href = 'https://' + targetInstance + '/c/' + targetCommunity;
136 | });
137 |
138 | // ---------- Append elements ----------- //
139 | if (!document.querySelector('#instance-assistant-sidebar') && (await getSetting('runOnCommunityNotFound'))) { // prevent duplicate elements
140 | const hideHelp = await getSetting('hideHelp');
141 | if (!hideHelp) {container.appendChild(txtErrorPage);}
142 | container.appendChild(btnOpenSearchLemmy)
143 | container.appendChild(btnCommunityLemmy);
144 | container.appendChild(btnHomeLemmy);
145 | container.appendChild(txtHomeInstance);
146 | if (!hideHelp) {container.appendChild(txtChangeInstance);}
147 | TARGET_ELEMENT.insertBefore(container, TARGET_ELEMENT.firstChild);
148 | }
149 |
150 | if (mayBeFrontend(targetInstance)) {
151 |
152 | console.log('may be frontend')
153 |
154 | let txtAlternateRedirect = createMessage('
IMPORTANT: Based on your URL ' + targetInstance + ', you may be using a custom frontend. You can try getting the community from the main domain:
Welcome! Please enter your instance information below.
34 |
35 |
Change Instance
36 |
37 |
Home Instance URL:
38 |
39 |
40 |
Please enter a valid URL (ex. 'https://lemmy.ca")
41 |
42 |
Home Instance Type:
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
Change Display
53 |
54 |
You can modify the list of instances in the popup menu by editing this field. This is helpful if you have
55 | accounts on more than one instance, or even if you like jumping between instances. Remove the instances that you
56 | don't need, and if you make a mistake then you can 'Reset to Default'.
57 |
Please list one instance per line, and use the format "button-text, url"