mirror of
https://github.com/tubearchivist/browser-extension.git
synced 2024-11-05 03:30:12 +00:00
commit
9fadbd5c15
21
.eslintrc.js
Normal file
21
.eslintrc.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
'use strict';
|
||||||
|
module.exports = {
|
||||||
|
extends: ['eslint:recommended', 'eslint-config-prettier'],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
browser: 'readonly',
|
||||||
|
chrome: 'readonly',
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
strict: ['error', 'global'],
|
||||||
|
'no-unused-vars': ['error', { vars: 'local' }],
|
||||||
|
eqeqeq: ['error', 'always', { null: 'ignore' }],
|
||||||
|
curly: ['error', 'multi-line'],
|
||||||
|
'no-var': 'error',
|
||||||
|
},
|
||||||
|
};
|
16
.github/workflows/lint_js.yml
vendored
Normal file
16
.github/workflows/lint_js.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
name: lint_js
|
||||||
|
|
||||||
|
on: [pull_request, push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: lint_js
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm run lint
|
||||||
|
- run: npm run format -- --check
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,3 +3,6 @@ extension/manifest.json
|
|||||||
|
|
||||||
# release builds
|
# release builds
|
||||||
release/*
|
release/*
|
||||||
|
|
||||||
|
# JavaScript stuff
|
||||||
|
node_modules
|
||||||
|
@ -78,3 +78,6 @@ Join us on [Discord](https://www.tubearchivist.com/discord) and help us improve
|
|||||||
- [ ] Implement download button for videos on playlist
|
- [ ] Implement download button for videos on playlist
|
||||||
- [ ] Error handling for connection errors
|
- [ ] Error handling for connection errors
|
||||||
- [X] Dynamically inject buttons with mutation observer
|
- [X] Dynamically inject buttons with mutation observer
|
||||||
|
|
||||||
|
## Making changes to the JavaScript
|
||||||
|
The JavaScript does not require any build step; you just edit the files directly. However, there is config for eslint and prettier (a linter and formatter respectively); their use is recommended but not required. To use them, install `node`, run `npm i` from the root directory of this repository to install dependencies, then run `npm run lint` and `npm run format` to run eslint and prettier respectively.
|
||||||
|
@ -2,51 +2,48 @@
|
|||||||
extension background script listening for events
|
extension background script listening for events
|
||||||
*/
|
*/
|
||||||
|
|
||||||
console.log("running background.js");
|
'use strict';
|
||||||
|
|
||||||
|
console.log('running background.js');
|
||||||
|
|
||||||
let browserType = getBrowser();
|
let browserType = getBrowser();
|
||||||
|
|
||||||
|
|
||||||
// boilerplate to dedect browser type api
|
// boilerplate to dedect browser type api
|
||||||
function getBrowser() {
|
function getBrowser() {
|
||||||
if (typeof chrome !== "undefined") {
|
if (typeof chrome !== 'undefined') {
|
||||||
if (typeof browser !== "undefined") {
|
if (typeof browser !== 'undefined') {
|
||||||
return browser;
|
return browser;
|
||||||
} else {
|
} else {
|
||||||
return chrome;
|
return chrome;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("failed to detect browser");
|
console.log('failed to detect browser');
|
||||||
throw "browser detection error"
|
throw 'browser detection error';
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// send get request to API backend
|
// send get request to API backend
|
||||||
async function sendGet(path) {
|
async function sendGet(path) {
|
||||||
|
|
||||||
let access = await getAccess();
|
let access = await getAccess();
|
||||||
const url = `${access.url}:${access.port}/${path}`;
|
const url = `${access.url}:${access.port}/${path}`;
|
||||||
console.log("GET: " + url);
|
console.log('GET: ' + url);
|
||||||
|
|
||||||
const rawResponse = await fetch(url, {
|
const rawResponse = await fetch(url, {
|
||||||
method: "GET",
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
"Accept": "application/json",
|
Accept: 'application/json',
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
"Authorization": "Token " + access.apiKey,
|
Authorization: 'Token ' + access.apiKey,
|
||||||
"mode": "no-cors"
|
mode: 'no-cors',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const content = await rawResponse.json();
|
const content = await rawResponse.json();
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// send post/put request to API backend
|
// send post/put request to API backend
|
||||||
async function sendData(path, payload, method) {
|
async function sendData(path, payload, method) {
|
||||||
|
|
||||||
let access = await getAccess();
|
let access = await getAccess();
|
||||||
const url = `${access.url}:${access.port}/${path}`;
|
const url = `${access.url}:${access.port}/${path}`;
|
||||||
console.log(`${method}: ${url}`);
|
console.log(`${method}: ${url}`);
|
||||||
@ -56,129 +53,113 @@ async function sendData(path, payload, method) {
|
|||||||
const rawResponse = await fetch(url, {
|
const rawResponse = await fetch(url, {
|
||||||
method: method,
|
method: method,
|
||||||
headers: {
|
headers: {
|
||||||
"Accept": "application/json",
|
Accept: 'application/json',
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
"Authorization": "Token " + access.apiKey,
|
Authorization: 'Token ' + access.apiKey,
|
||||||
"mode": "no-cors"
|
mode: 'no-cors',
|
||||||
},
|
},
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
const content = await rawResponse.json();
|
const content = await rawResponse.json();
|
||||||
return content;
|
return content;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// read access details from storage.local
|
// read access details from storage.local
|
||||||
async function getAccess() {
|
async function getAccess() {
|
||||||
|
let storage = await browserType.storage.local.get('access');
|
||||||
|
|
||||||
var storage = await browserType.storage.local.get("access");
|
return storage.access;
|
||||||
|
|
||||||
return storage.access
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// check if cookie is valid
|
// check if cookie is valid
|
||||||
async function getCookieState() {
|
async function getCookieState() {
|
||||||
|
const path = 'api/cookie/';
|
||||||
|
let response = await sendGet(path);
|
||||||
|
console.log('cookie state: ' + JSON.stringify(response));
|
||||||
|
|
||||||
const path = "api/cookie/";
|
return response;
|
||||||
let response = await sendGet(path)
|
|
||||||
console.log("cookie state: " + JSON.stringify(response));
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// send ping to server, return response
|
// send ping to server, return response
|
||||||
async function verifyConnection() {
|
async function verifyConnection() {
|
||||||
|
const path = 'api/ping/';
|
||||||
|
let response = await sendGet(path);
|
||||||
|
console.log('verify connection: ' + JSON.stringify(response));
|
||||||
|
|
||||||
const path = "api/ping/";
|
return response;
|
||||||
let response = await sendGet(path)
|
|
||||||
console.log("verify connection: " + JSON.stringify(response));
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// send youtube link from injected buttons
|
// send youtube link from injected buttons
|
||||||
async function youtubeLink(youtubeMessage) {
|
async function youtubeLink(youtubeMessage) {
|
||||||
|
|
||||||
let path;
|
let path;
|
||||||
let payload;
|
let payload;
|
||||||
|
|
||||||
if (youtubeMessage.action === "download") {
|
if (youtubeMessage.action === 'download') {
|
||||||
path = "api/download/";
|
path = 'api/download/';
|
||||||
payload = {
|
payload = {
|
||||||
"data": [
|
data: [
|
||||||
{
|
{
|
||||||
"youtube_id": youtubeMessage.url,
|
youtube_id: youtubeMessage.url,
|
||||||
"status": "pending",
|
status: 'pending',
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
};
|
||||||
} else if (youtubeMessage.action === "subscribe") {
|
} else if (youtubeMessage.action === 'subscribe') {
|
||||||
path = "api/channel/";
|
path = 'api/channel/';
|
||||||
payload = {
|
payload = {
|
||||||
"data": [
|
data: [
|
||||||
{
|
{
|
||||||
"channel_id": youtubeMessage.url,
|
channel_id: youtubeMessage.url,
|
||||||
"channel_subscribed": true,
|
channel_subscribed: true,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = await sendData(path, payload, "POST");
|
let response = await sendData(path, payload, 'POST');
|
||||||
return response
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function cookieStr(cookieLines) {
|
async function cookieStr(cookieLines) {
|
||||||
|
const path = 'api/cookie/';
|
||||||
const path = "api/cookie/";
|
|
||||||
let payload = {
|
let payload = {
|
||||||
"cookie": cookieLines.join("\n")
|
cookie: cookieLines.join('\n'),
|
||||||
}
|
};
|
||||||
let response = await sendData(path, payload, "PUT");
|
let response = await sendData(path, payload, 'PUT');
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function buildCookieLine(cookie) {
|
function buildCookieLine(cookie) {
|
||||||
return [
|
return [
|
||||||
cookie.domain,
|
cookie.domain,
|
||||||
"TRUE",
|
'TRUE',
|
||||||
cookie.path,
|
cookie.path,
|
||||||
cookie.httpOnly.toString().toUpperCase(),
|
cookie.httpOnly.toString().toUpperCase(),
|
||||||
Math.trunc(cookie.expirationDate) || 0,
|
Math.trunc(cookie.expirationDate) || 0,
|
||||||
cookie.name,
|
cookie.name,
|
||||||
cookie.value,
|
cookie.value,
|
||||||
].join("\t");
|
].join('\t');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function sendCookies() {
|
async function sendCookies() {
|
||||||
console.log("function sendCookies");
|
console.log('function sendCookies');
|
||||||
|
|
||||||
let cookieStores = await browserType.cookies.getAllCookieStores();
|
let cookieStores = await browserType.cookies.getAllCookieStores();
|
||||||
var cookieLines = [
|
let cookieLines = [
|
||||||
"# Netscape HTTP Cookie File",
|
'# Netscape HTTP Cookie File',
|
||||||
"# https://curl.haxx.se/rfc/cookie_spec.html",
|
'# https://curl.haxx.se/rfc/cookie_spec.html',
|
||||||
"# This is a generated file! Do not edit.\n"
|
'# This is a generated file! Do not edit.\n',
|
||||||
];
|
];
|
||||||
for (let i = 0; i < cookieStores.length; i++) {
|
for (let i = 0; i < cookieStores.length; i++) {
|
||||||
const cookieStore = cookieStores[i];
|
const cookieStore = cookieStores[i];
|
||||||
var allCookiesStore = await browserType.cookies.getAll({
|
let allCookiesStore = await browserType.cookies.getAll({
|
||||||
domain: ".youtube.com",
|
domain: '.youtube.com',
|
||||||
storeId: cookieStore["id"]
|
storeId: cookieStore['id'],
|
||||||
});
|
});
|
||||||
for (let j = 0; j < allCookiesStore.length; j++) {
|
for (let j = 0; j < allCookiesStore.length; j++) {
|
||||||
const cookie = allCookiesStore[j];
|
const cookie = allCookiesStore[j];
|
||||||
@ -188,37 +169,34 @@ async function sendCookies() {
|
|||||||
|
|
||||||
let response = cookieStr(cookieLines);
|
let response = cookieStr(cookieLines);
|
||||||
|
|
||||||
return response
|
return response;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// process and return message if needed
|
// process and return message if needed
|
||||||
function handleMessage(request, sender, sendResponse) {
|
function handleMessage(request, sender, sendResponse) {
|
||||||
console.log("message background.js listener: " + JSON.stringify(request));
|
console.log('message background.js listener: ' + JSON.stringify(request));
|
||||||
|
|
||||||
if (request.verify === true) {
|
if (request.verify === true) {
|
||||||
let response = verifyConnection();
|
let response = verifyConnection();
|
||||||
response.then(message => {
|
response.then(message => {
|
||||||
sendResponse(message);
|
sendResponse(message);
|
||||||
})
|
});
|
||||||
} else if (request.youtube) {
|
} else if (request.youtube) {
|
||||||
let response = youtubeLink(request.youtube);
|
let response = youtubeLink(request.youtube);
|
||||||
response.then(message => {
|
response.then(message => {
|
||||||
sendResponse(message)
|
sendResponse(message);
|
||||||
})
|
});
|
||||||
} else if (request.cookieState) {
|
} else if (request.cookieState) {
|
||||||
let response = getCookieState();
|
let response = getCookieState();
|
||||||
response.then(message => {
|
response.then(message => {
|
||||||
sendResponse(message)
|
sendResponse(message);
|
||||||
})
|
});
|
||||||
} else if (request.sendCookie) {
|
} else if (request.sendCookie) {
|
||||||
console.log("backgound: " + JSON.stringify(request));
|
console.log('backgound: ' + JSON.stringify(request));
|
||||||
let response = sendCookies();
|
let response = sendCookies();
|
||||||
response.then(message => {
|
response.then(message => {
|
||||||
sendResponse(message)
|
sendResponse(message);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -2,93 +2,89 @@
|
|||||||
Loaded into popup index.html
|
Loaded into popup index.html
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
let browserType = getBrowser();
|
let browserType = getBrowser();
|
||||||
|
|
||||||
// boilerplate to dedect browser type api
|
// boilerplate to dedect browser type api
|
||||||
function getBrowser() {
|
function getBrowser() {
|
||||||
if (typeof chrome !== "undefined") {
|
if (typeof chrome !== 'undefined') {
|
||||||
if (typeof browser !== "undefined") {
|
if (typeof browser !== 'undefined') {
|
||||||
return browser;
|
return browser;
|
||||||
} else {
|
} else {
|
||||||
return chrome;
|
return chrome;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("failed to dedect browser");
|
console.log('failed to dedect browser');
|
||||||
throw "browser detection error"
|
throw 'browser detection error';
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// store access details
|
// store access details
|
||||||
document.getElementById("save-login").addEventListener("click", function () {
|
document.getElementById('save-login').addEventListener('click', function () {
|
||||||
let url = document.getElementById("full-url").value;
|
let url = document.getElementById('full-url').value;
|
||||||
if (!url.includes('://')) {
|
if (!url.includes('://')) {
|
||||||
url = 'http://' + url;
|
url = 'http://' + url;
|
||||||
}
|
}
|
||||||
let parsed = new URL(url);
|
let parsed = new URL(url);
|
||||||
let toStore = {
|
let toStore = {
|
||||||
"access": {
|
access: {
|
||||||
"url": `${parsed.protocol}//${parsed.hostname}`,
|
url: `${parsed.protocol}//${parsed.hostname}`,
|
||||||
"port": parsed.port || (parsed.protocol === 'https:' ? '443' : '80'),
|
port: parsed.port || (parsed.protocol === 'https:' ? '443' : '80'),
|
||||||
"apiKey": document.getElementById("api-key").value
|
apiKey: document.getElementById('api-key').value,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
browserType.storage.local.set(toStore, function() {
|
browserType.storage.local.set(toStore, function () {
|
||||||
console.log("Stored connection details: " + JSON.stringify(toStore));
|
console.log('Stored connection details: ' + JSON.stringify(toStore));
|
||||||
pingBackend();
|
pingBackend();
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
// verify connection status
|
// verify connection status
|
||||||
document.getElementById("status-icon").addEventListener("click", function() {
|
document.getElementById('status-icon').addEventListener('click', function () {
|
||||||
pingBackend();
|
pingBackend();
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
// send cookie
|
// send cookie
|
||||||
document.getElementById("sendCookies").addEventListener("click", function() {
|
document.getElementById('sendCookies').addEventListener('click', function () {
|
||||||
sendCookie();
|
sendCookie();
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
function sendCookie() {
|
function sendCookie() {
|
||||||
console.log("popup send cookie");
|
console.log('popup send cookie');
|
||||||
|
|
||||||
function handleResponse(message) {
|
function handleResponse(message) {
|
||||||
console.log("handle cookie response: " + JSON.stringify(message));
|
console.log('handle cookie response: ' + JSON.stringify(message));
|
||||||
let cookie_validated = message.cookie_validated;
|
let cookie_validated = message.cookie_validated;
|
||||||
document.getElementById("sendCookiesStatus").innerText = "validated: " + cookie_validated
|
document.getElementById('sendCookiesStatus').innerText = 'validated: ' + cookie_validated;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleError(error) {
|
function handleError(error) {
|
||||||
console.log(`Error: ${error}`);
|
console.log(`Error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let checked = document.getElementById("sendCookies").checked;
|
let checked = document.getElementById('sendCookies').checked;
|
||||||
let toStore = {
|
let toStore = {
|
||||||
"sendCookies": {
|
sendCookies: {
|
||||||
"checked": checked
|
checked: checked,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
browserType.storage.local.set(toStore, function() {
|
browserType.storage.local.set(toStore, function () {
|
||||||
console.log("stored option: " + JSON.stringify(toStore));
|
console.log('stored option: ' + JSON.stringify(toStore));
|
||||||
})
|
});
|
||||||
if (checked === false) {
|
if (checked === false) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
let sending = browserType.runtime.sendMessage({"sendCookie": true});
|
let sending = browserType.runtime.sendMessage({ sendCookie: true });
|
||||||
sending.then(handleResponse, handleError);
|
sending.then(handleResponse, handleError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// send ping message to TA backend
|
// send ping message to TA backend
|
||||||
function pingBackend() {
|
function pingBackend() {
|
||||||
|
|
||||||
function handleResponse(message) {
|
function handleResponse(message) {
|
||||||
if (message.response === "pong") {
|
if (message.response === 'pong') {
|
||||||
setStatusIcon(true);
|
setStatusIcon(true);
|
||||||
console.log("connection validated")
|
console.log('connection validated');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,26 +93,23 @@ function pingBackend() {
|
|||||||
setStatusIcon(false);
|
setStatusIcon(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("ping TA server")
|
console.log('ping TA server');
|
||||||
let sending = browserType.runtime.sendMessage({"verify": true});
|
let sending = browserType.runtime.sendMessage({ verify: true });
|
||||||
sending.then(handleResponse, handleError);
|
sending.then(handleResponse, handleError);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add url to image
|
// add url to image
|
||||||
function addUrl(access) {
|
function addUrl(access) {
|
||||||
const url = `${access.url}:${access.port}`;
|
const url = `${access.url}:${access.port}`;
|
||||||
document.getElementById("ta-url").setAttribute("href", url);
|
document.getElementById('ta-url').setAttribute('href', url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function setCookieState() {
|
function setCookieState() {
|
||||||
|
|
||||||
function handleResponse(message) {
|
function handleResponse(message) {
|
||||||
console.log(message);
|
console.log(message);
|
||||||
document.getElementById("sendCookies").checked = message.cookie_enabled;
|
document.getElementById('sendCookies').checked = message.cookie_enabled;
|
||||||
if (message.validated_str) {
|
if (message.validated_str) {
|
||||||
document.getElementById("sendCookiesStatus").innerText = message.validated_str;
|
document.getElementById('sendCookiesStatus').innerText = message.validated_str;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,68 +117,57 @@ function setCookieState() {
|
|||||||
console.log(`Error: ${error}`);
|
console.log(`Error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("set cookie state");
|
console.log('set cookie state');
|
||||||
let sending = browserType.runtime.sendMessage({"cookieState": true});
|
let sending = browserType.runtime.sendMessage({ cookieState: true });
|
||||||
sending.then(handleResponse, handleError)
|
sending.then(handleResponse, handleError);
|
||||||
document.getElementById("sendCookies").checked = true;
|
document.getElementById('sendCookies').checked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// change status icon based on connection status
|
// change status icon based on connection status
|
||||||
function setStatusIcon(connected) {
|
function setStatusIcon(connected) {
|
||||||
|
let statusIcon = document.getElementById('status-icon');
|
||||||
let statusIcon = document.getElementById("status-icon")
|
if (connected) {
|
||||||
if (connected == true) {
|
statusIcon.innerHTML = '☑';
|
||||||
statusIcon.innerHTML = "☑";
|
statusIcon.style.color = 'green';
|
||||||
statusIcon.style.color = "green";
|
|
||||||
} else {
|
} else {
|
||||||
statusIcon.innerHTML = "☒";
|
statusIcon.innerHTML = '☒';
|
||||||
statusIcon.style.color = "red";
|
statusIcon.style.color = 'red';
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// fill in form
|
// fill in form
|
||||||
document.addEventListener("DOMContentLoaded", async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
|
||||||
function onGot(item) {
|
function onGot(item) {
|
||||||
if (!item.access) {
|
if (!item.access) {
|
||||||
console.log("no access details found");
|
console.log('no access details found');
|
||||||
setStatusIcon(false);
|
setStatusIcon(false);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
let { url, port } = item.access;
|
let { url, port } = item.access;
|
||||||
let fullUrl = url;
|
let fullUrl = url;
|
||||||
if (!(url.startsWith('http://') && port === '80')) {
|
if (!(url.startsWith('http://') && port === '80')) {
|
||||||
fullUrl += `:${port}`;
|
fullUrl += `:${port}`;
|
||||||
}
|
}
|
||||||
document.getElementById("full-url").value = fullUrl;
|
document.getElementById('full-url').value = fullUrl;
|
||||||
document.getElementById("api-key").value = item.access.apiKey;
|
document.getElementById('api-key').value = item.access.apiKey;
|
||||||
pingBackend();
|
pingBackend();
|
||||||
addUrl(item.access);
|
addUrl(item.access);
|
||||||
};
|
}
|
||||||
|
|
||||||
function setCookiesOptions(result) {
|
function setCookiesOptions(result) {
|
||||||
if (!result.sendCookies || result.sendCookies.checked === false) {
|
if (!result.sendCookies || result.sendCookies.checked === false) {
|
||||||
console.log("sync cookies not set");
|
console.log('sync cookies not set');
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
console.log("set options: " + JSON.stringify(result));
|
console.log('set options: ' + JSON.stringify(result));
|
||||||
setCookieState();
|
setCookieState();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onError(error) {
|
browserType.storage.local.get('access', function (result) {
|
||||||
console.log(`Error: ${error}`);
|
onGot(result);
|
||||||
};
|
|
||||||
|
|
||||||
browserType.storage.local.get("access", function(result) {
|
|
||||||
onGot(result)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
browserType.storage.local.get("sendCookies", function(result) {
|
browserType.storage.local.get('sendCookies', function (result) {
|
||||||
setCookiesOptions(result)
|
setCookiesOptions(result);
|
||||||
})
|
});
|
||||||
|
});
|
||||||
})
|
|
||||||
|
@ -2,23 +2,24 @@
|
|||||||
content script running on youtube.com
|
content script running on youtube.com
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let browserType = getBrowser();
|
'use strict';
|
||||||
|
|
||||||
|
let browserType = getBrowser();
|
||||||
|
|
||||||
// boilerplate to dedect browser type api
|
// boilerplate to dedect browser type api
|
||||||
function getBrowser() {
|
function getBrowser() {
|
||||||
if (typeof chrome !== "undefined") {
|
if (typeof chrome !== 'undefined') {
|
||||||
if (typeof browser !== "undefined") {
|
if (typeof browser !== 'undefined') {
|
||||||
console.log("detected firefox");
|
console.log('detected firefox');
|
||||||
return browser;
|
return browser;
|
||||||
} else {
|
} else {
|
||||||
console.log("detected chrome");
|
console.log('detected chrome');
|
||||||
return chrome;
|
return chrome;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("failed to dedect browser");
|
console.log('failed to dedect browser');
|
||||||
throw "browser detection error"
|
throw 'browser detection error';
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadIcon = `<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
const downloadIcon = `<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
@ -100,187 +101,181 @@ viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="pres
|
|||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>`
|
</svg>`;
|
||||||
|
|
||||||
|
|
||||||
function buildButtonDiv() {
|
function buildButtonDiv() {
|
||||||
var buttonDiv = document.createElement("div");
|
let buttonDiv = document.createElement('div');
|
||||||
buttonDiv.setAttribute("id", "ta-channel-button");
|
buttonDiv.setAttribute('id', 'ta-channel-button');
|
||||||
|
|
||||||
Object.assign(buttonDiv.style, {
|
Object.assign(buttonDiv.style, {
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
backgroundColor: "#00202f",
|
backgroundColor: '#00202f',
|
||||||
color: "#fff",
|
color: '#fff',
|
||||||
fontSize: "14px",
|
fontSize: '14px',
|
||||||
padding: "5px",
|
padding: '5px',
|
||||||
margin: "0 5px",
|
margin: '0 5px',
|
||||||
borderRadius: "8px",
|
borderRadius: '8px',
|
||||||
});
|
});
|
||||||
return buttonDiv
|
return buttonDiv;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSubLink(channelContainer) {
|
function buildSubLink(channelContainer) {
|
||||||
var subLink = document.createElement("span");
|
let subLink = document.createElement('span');
|
||||||
subLink.innerText = "Subscribe";
|
subLink.innerText = 'Subscribe';
|
||||||
subLink.addEventListener('click', e => {
|
subLink.addEventListener('click', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var currentLocation = window.location.href;
|
let currentLocation = window.location.href;
|
||||||
console.log("subscribe to: " + currentLocation);
|
console.log('subscribe to: ' + currentLocation);
|
||||||
sendUrl(currentLocation, "subscribe", subLink);
|
sendUrl(currentLocation, 'subscribe', subLink);
|
||||||
});
|
});
|
||||||
subLink.addEventListener("mouseover", e => {
|
subLink.addEventListener('mouseover', e => {
|
||||||
let subText
|
let subText;
|
||||||
if (window.location.pathname == "/watch") {
|
if (window.location.pathname === '/watch') {
|
||||||
var currentLocation = window.location.href;
|
let currentLocation = window.location.href;
|
||||||
subText = currentLocation;
|
subText = currentLocation;
|
||||||
} else {
|
} else {
|
||||||
subText = channelContainer.querySelector("#text").textContent;
|
subText = channelContainer.querySelector('#text').textContent;
|
||||||
};
|
}
|
||||||
|
|
||||||
e.target.title = "TA Subscribe: " + subText;
|
e.target.title = 'TA Subscribe: ' + subText;
|
||||||
});
|
});
|
||||||
Object.assign(subLink.style, {
|
Object.assign(subLink.style, {
|
||||||
padding: "5px",
|
padding: '5px',
|
||||||
cursor: "pointer",
|
cursor: 'pointer',
|
||||||
});
|
});
|
||||||
|
|
||||||
return subLink
|
return subLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSpacer() {
|
function buildSpacer() {
|
||||||
var spacer = document.createElement("span");
|
let spacer = document.createElement('span');
|
||||||
spacer.innerText = "|";
|
spacer.innerText = '|';
|
||||||
|
|
||||||
return spacer
|
return spacer;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildDlLink(channelContainer) {
|
function buildDlLink(channelContainer) {
|
||||||
var dlLink = document.createElement("span");
|
let dlLink = document.createElement('span');
|
||||||
dlLink.innerHTML = downloadIcon;
|
dlLink.innerHTML = downloadIcon;
|
||||||
|
|
||||||
dlLink.addEventListener('click', e => {
|
dlLink.addEventListener('click', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var currentLocation = window.location.href;
|
let currentLocation = window.location.href;
|
||||||
console.log("download: " + currentLocation)
|
console.log('download: ' + currentLocation);
|
||||||
sendUrl(currentLocation, "download", dlLink);
|
sendUrl(currentLocation, 'download', dlLink);
|
||||||
});
|
});
|
||||||
dlLink.addEventListener("mouseover", e => {
|
dlLink.addEventListener('mouseover', e => {
|
||||||
let subText
|
let subText;
|
||||||
if (window.location.pathname == "/watch") {
|
if (window.location.pathname === '/watch') {
|
||||||
var currentLocation = window.location.href;
|
let currentLocation = window.location.href;
|
||||||
subText = currentLocation;
|
subText = currentLocation;
|
||||||
} else {
|
} else {
|
||||||
subText = channelContainer.querySelector("#text").textContent;
|
subText = channelContainer.querySelector('#text').textContent;
|
||||||
};
|
}
|
||||||
|
|
||||||
e.target.title = "TA Download: " + subText;
|
e.target.title = 'TA Download: ' + subText;
|
||||||
});
|
});
|
||||||
Object.assign(dlLink.style, {
|
Object.assign(dlLink.style, {
|
||||||
filter: "invert()",
|
filter: 'invert()',
|
||||||
width: "20px",
|
width: '20px',
|
||||||
padding: "0 5px",
|
padding: '0 5px',
|
||||||
cursor: "pointer",
|
cursor: 'pointer',
|
||||||
});
|
});
|
||||||
|
|
||||||
return dlLink
|
return dlLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function buildChannelButton(channelContainer) {
|
function buildChannelButton(channelContainer) {
|
||||||
|
let buttonDiv = buildButtonDiv();
|
||||||
|
|
||||||
var buttonDiv = buildButtonDiv()
|
let subLink = buildSubLink(channelContainer);
|
||||||
|
buttonDiv.appendChild(subLink);
|
||||||
|
|
||||||
var subLink = buildSubLink(channelContainer);
|
let spacer = buildSpacer();
|
||||||
buttonDiv.appendChild(subLink)
|
|
||||||
|
|
||||||
var spacer = buildSpacer()
|
|
||||||
buttonDiv.appendChild(spacer);
|
buttonDiv.appendChild(spacer);
|
||||||
|
|
||||||
var dlLink = buildDlLink(channelContainer)
|
let dlLink = buildDlLink(channelContainer);
|
||||||
buttonDiv.appendChild(dlLink);
|
buttonDiv.appendChild(dlLink);
|
||||||
|
|
||||||
return buttonDiv
|
return buttonDiv;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getChannelContainers() {
|
function getChannelContainers() {
|
||||||
var nodes = document.querySelectorAll("#inner-header-container, #owner");
|
let nodes = document.querySelectorAll('#inner-header-container, #owner');
|
||||||
return nodes
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getThubnailContainers() {
|
function getThubnailContainers() {
|
||||||
var nodes = document.querySelectorAll('#thumbnail');
|
let nodes = document.querySelectorAll('#thumbnail');
|
||||||
return nodes
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildVideoButton(thumbContainer) {
|
function buildVideoButton(thumbContainer) {
|
||||||
var thumbLink = thumbContainer?.href;
|
let thumbLink = thumbContainer?.href;
|
||||||
if (!thumbLink) return;
|
if (!thumbLink) return;
|
||||||
if (thumbLink.includes('list=') || thumbLink.includes('/shorts/')) return;
|
if (thumbLink.includes('list=') || thumbLink.includes('/shorts/')) return;
|
||||||
|
|
||||||
var dlButton = document.createElement("a");
|
let dlButton = document.createElement('a');
|
||||||
dlButton.setAttribute("id", "ta-video-button");
|
dlButton.setAttribute('id', 'ta-video-button');
|
||||||
dlButton.href = '#'
|
dlButton.href = '#';
|
||||||
|
|
||||||
dlButton.addEventListener('click', e => {
|
dlButton.addEventListener('click', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let videoLink = thumbContainer.href;
|
let videoLink = thumbContainer.href;
|
||||||
console.log("download: " + videoLink);
|
console.log('download: ' + videoLink);
|
||||||
sendUrl(videoLink, "download", dlButton)
|
sendUrl(videoLink, 'download', dlButton);
|
||||||
});
|
});
|
||||||
dlButton.addEventListener('mouseover', e => {
|
dlButton.addEventListener('mouseover', e => {
|
||||||
Object.assign(dlButton.style, {
|
Object.assign(dlButton.style, {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
});
|
});
|
||||||
let videoTitle = thumbContainer.href;
|
let videoTitle = thumbContainer.href;
|
||||||
e.target.title = "TA download: " + videoTitle;
|
e.target.title = 'TA download: ' + videoTitle;
|
||||||
})
|
});
|
||||||
dlButton.addEventListener('mouseout', e => {
|
dlButton.addEventListener('mouseout', () => {
|
||||||
Object.assign(dlButton.style, {
|
Object.assign(dlButton.style, {
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
});
|
});
|
||||||
})
|
|
||||||
|
|
||||||
Object.assign(dlButton.style, {
|
|
||||||
display: "flex",
|
|
||||||
position: "absolute",
|
|
||||||
top: "5px",
|
|
||||||
left: "5px",
|
|
||||||
alignItems: "center",
|
|
||||||
backgroundColor: "#00202f",
|
|
||||||
color: "#fff",
|
|
||||||
fontSize: "1.4rem",
|
|
||||||
textDecoration: "none",
|
|
||||||
borderRadius: "8px",
|
|
||||||
cursor: "pointer",
|
|
||||||
opacity: 0,
|
|
||||||
transition: "all 0.3s ease 0.3s",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var dlIcon = document.createElement("span");
|
Object.assign(dlButton.style, {
|
||||||
|
display: 'flex',
|
||||||
|
position: 'absolute',
|
||||||
|
top: '5px',
|
||||||
|
left: '5px',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#00202f',
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: '1.4rem',
|
||||||
|
textDecoration: 'none',
|
||||||
|
borderRadius: '8px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
opacity: 0,
|
||||||
|
transition: 'all 0.3s ease 0.3s',
|
||||||
|
});
|
||||||
|
|
||||||
|
let dlIcon = document.createElement('span');
|
||||||
dlIcon.innerHTML = downloadIcon;
|
dlIcon.innerHTML = downloadIcon;
|
||||||
Object.assign(dlIcon.style, {
|
Object.assign(dlIcon.style, {
|
||||||
filter: "invert()",
|
filter: 'invert()',
|
||||||
width: "20px",
|
width: '20px',
|
||||||
padding: "10px 13px",
|
padding: '10px 13px',
|
||||||
});
|
});
|
||||||
|
|
||||||
dlButton.appendChild(dlIcon);
|
dlButton.appendChild(dlIcon);
|
||||||
|
|
||||||
return dlButton
|
return dlButton;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// fix positioning of #owner div to fit new button
|
// fix positioning of #owner div to fit new button
|
||||||
function adjustOwner(channelContainer) {
|
function adjustOwner(channelContainer) {
|
||||||
let sponsorButton = channelContainer.querySelector('#sponsor-button');
|
let sponsorButton = channelContainer.querySelector('#sponsor-button');
|
||||||
if (sponsorButton === null) {
|
if (sponsorButton === null) {
|
||||||
return channelContainer
|
return channelContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
let variableMinWidth
|
let variableMinWidth;
|
||||||
if (sponsorButton.hasChildNodes()) {
|
if (sponsorButton.hasChildNodes()) {
|
||||||
variableMinWidth = '140px';
|
variableMinWidth = '140px';
|
||||||
} else {
|
} else {
|
||||||
@ -288,65 +283,62 @@ function adjustOwner(channelContainer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(channelContainer.firstElementChild.style, {
|
Object.assign(channelContainer.firstElementChild.style, {
|
||||||
minWidth: variableMinWidth
|
minWidth: variableMinWidth,
|
||||||
})
|
});
|
||||||
Object.assign(channelContainer.style, {
|
Object.assign(channelContainer.style, {
|
||||||
minWidth: 'calc(40% + 50px)'
|
minWidth: 'calc(40% + 50px)',
|
||||||
})
|
});
|
||||||
return channelContainer
|
return channelContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function ensureTALinks() {
|
function ensureTALinks() {
|
||||||
|
let channelContainerNodes = getChannelContainers();
|
||||||
|
|
||||||
var channelContainerNodes = getChannelContainers()
|
for (let channelContainer of channelContainerNodes) {
|
||||||
|
|
||||||
for (var channelContainer of channelContainerNodes) {
|
|
||||||
channelContainer = adjustOwner(channelContainer);
|
channelContainer = adjustOwner(channelContainer);
|
||||||
if (channelContainer.hasTA) continue;
|
if (channelContainer.hasTA) continue;
|
||||||
var channelButton = buildChannelButton(channelContainer);
|
let channelButton = buildChannelButton(channelContainer);
|
||||||
channelContainer.appendChild(channelButton);
|
channelContainer.appendChild(channelButton);
|
||||||
channelContainer.hasTA = true;
|
channelContainer.hasTA = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var thumbContainerNodes = getThubnailContainers();
|
let thumbContainerNodes = getThubnailContainers();
|
||||||
|
|
||||||
for (var thumbContainer of thumbContainerNodes) {
|
for (let thumbContainer of thumbContainerNodes) {
|
||||||
if (thumbContainer.hasTA) continue;
|
if (thumbContainer.hasTA) continue;
|
||||||
var videoButton = buildVideoButton(thumbContainer);
|
let videoButton = buildVideoButton(thumbContainer);
|
||||||
if (videoButton == null) continue;
|
if (videoButton == null) continue;
|
||||||
thumbContainer.parentElement.appendChild(videoButton);
|
thumbContainer.parentElement.appendChild(videoButton);
|
||||||
thumbContainer.hasTA = true;
|
thumbContainer.hasTA = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function buttonError(button) {
|
function buttonError(button) {
|
||||||
let buttonSpan = button.querySelector("span");
|
let buttonSpan = button.querySelector('span');
|
||||||
if (buttonSpan === null) {
|
|
||||||
buttonSpan = button
|
|
||||||
}
|
|
||||||
buttonSpan.style.filter = "invert(19%) sepia(93%) saturate(7472%) hue-rotate(359deg) brightness(105%) contrast(113%)";
|
|
||||||
buttonSpan.style.color = "red";
|
|
||||||
|
|
||||||
button.style.opacity = 1;
|
|
||||||
button.addEventListener('mouseout', e => {
|
|
||||||
Object.assign(button.style, {
|
|
||||||
opacity: 1,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function buttonSuccess(button) {
|
|
||||||
let buttonSpan = button.querySelector("span");
|
|
||||||
if (buttonSpan === null) {
|
if (buttonSpan === null) {
|
||||||
buttonSpan = button;
|
buttonSpan = button;
|
||||||
}
|
}
|
||||||
if (buttonSpan.innerHTML === "Subscribe") {
|
buttonSpan.style.filter =
|
||||||
buttonSpan.innerHTML = "Success";
|
'invert(19%) sepia(93%) saturate(7472%) hue-rotate(359deg) brightness(105%) contrast(113%)';
|
||||||
|
buttonSpan.style.color = 'red';
|
||||||
|
|
||||||
|
button.style.opacity = 1;
|
||||||
|
button.addEventListener('mouseout', () => {
|
||||||
|
Object.assign(button.style, {
|
||||||
|
opacity: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buttonSuccess(button) {
|
||||||
|
let buttonSpan = button.querySelector('span');
|
||||||
|
if (buttonSpan === null) {
|
||||||
|
buttonSpan = button;
|
||||||
|
}
|
||||||
|
if (buttonSpan.innerHTML === 'Subscribe') {
|
||||||
|
buttonSpan.innerHTML = 'Success';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
buttonSpan.innerHTML = "Subscribe";
|
buttonSpan.innerHTML = 'Subscribe';
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} else {
|
} else {
|
||||||
buttonSpan.innerHTML = checkmarkIcon;
|
buttonSpan.innerHTML = checkmarkIcon;
|
||||||
@ -356,12 +348,10 @@ function buttonSuccess(button) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function sendUrl(url, action, button) {
|
function sendUrl(url, action, button) {
|
||||||
|
|
||||||
function handleResponse(message) {
|
function handleResponse(message) {
|
||||||
console.log("sendUrl response: " + JSON.stringify(message));
|
console.log('sendUrl response: ' + JSON.stringify(message));
|
||||||
if (message === null || message.detail === "Invalid token.") {
|
if (message === null || message.detail === 'Invalid token.') {
|
||||||
buttonError(button);
|
buttonError(button);
|
||||||
} else {
|
} else {
|
||||||
buttonSuccess(button);
|
buttonSuccess(button);
|
||||||
@ -369,24 +359,22 @@ function sendUrl(url, action, button) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleError(error) {
|
function handleError(error) {
|
||||||
console.log("error");
|
console.log('error');
|
||||||
console.log(JSON.stringify(error));
|
console.log(JSON.stringify(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
let payload = {
|
let payload = {
|
||||||
"youtube": {
|
youtube: {
|
||||||
"url": url,
|
url: url,
|
||||||
"action": action,
|
action: action,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
console.log("youtube link: " + JSON.stringify(payload));
|
console.log('youtube link: ' + JSON.stringify(payload));
|
||||||
|
|
||||||
let sending = browserType.runtime.sendMessage(payload);
|
let sending = browserType.runtime.sendMessage(payload);
|
||||||
sending.then(handleResponse, handleError);
|
sending.then(handleResponse, handleError);
|
||||||
|
}
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
let throttleBlock;
|
let throttleBlock;
|
||||||
const throttle = (callback, time) => {
|
const throttle = (callback, time) => {
|
||||||
|
1960
package-lock.json
generated
Normal file
1960
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
package.json
Normal file
18
package.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "tubearchivist-browser-extension",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint 'extension/**/*.js'",
|
||||||
|
"format": "prettier --write 'extension/**/*.js'"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^8.26.0",
|
||||||
|
"prettier": "^2.7.1",
|
||||||
|
"eslint-config-prettier": "^8.5.0"
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"singleQuote": true,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user