diff --git a/packages/webextension-v3/src/action/action.html b/packages/webextension-v3/src/action/action.html index fc7342d86..f91b51334 100644 --- a/packages/webextension-v3/src/action/action.html +++ b/packages/webextension-v3/src/action/action.html @@ -71,6 +71,21 @@ display: none; } + #default-server-desc { + color: rgba(0, 0, 0, 0.8); + font-size: 12px; + } + + #default-server-desc input { + width: initial; + padding: initial; + margin-left: 0; + } + + #custom-server-input { + display: none; + } + #message { display: inline-block; padding-top: 10px; @@ -122,9 +137,29 @@

Login

Password
-

+
+ + +
+ +
+
+ +
+
+ +
  - Create an account diff --git a/packages/webextension-v3/src/action/action.js b/packages/webextension-v3/src/action/action.js index c12b15e25..fa0516bda 100644 --- a/packages/webextension-v3/src/action/action.js +++ b/packages/webextension-v3/src/action/action.js @@ -1,9 +1,17 @@ -const API_BASE = "https://api.recipesage.com/"; +let api_url; +let base_url; + +const getServerUrls = () => { + chrome.storage.local.get(["api_url", "base_url"], (result) => { + api_url = result.api_url || "https://api.recipesage.com/"; + base_url = result.base_url || "https://recipesage.com/"; + }); +}; + +getServerUrls(); chrome.runtime.onMessage.addListener((request) => { const clipData = request; - console.log(clipData); - saveClip(clipData); }); @@ -11,7 +19,7 @@ let token; const login = async () => { try { - const loginResponse = await fetch(API_BASE + "users/login", { + const loginResponse = await fetch(api_url + "users/login", { method: "POST", mode: "cors", cache: "no-cache", @@ -41,8 +49,11 @@ const login = async () => { const data = await loginResponse.json(); const { token } = data; + // Assumes API URL is Base URL with "api." prepended. User can override in + // extension settings. + base_url = api_url.replace("api.", ""); - chrome.storage.local.set({ token }, () => { + chrome.storage.local.set({ token, api_url, base_url }, () => { chrome.storage.local.get(["seenTutorial"], (result) => { if (result.seenTutorial) { document.getElementById("message").innerText = @@ -95,11 +106,21 @@ const showLogin = () => { document.getElementById("start").style.display = "none"; }; +const showServerInput = () => { + document.getElementById("custom-server-input").style.display = "block"; + document.getElementById("register-link").style.display = "none"; +}; + +const hideServerInput = () => { + document.getElementById("custom-server-input").style.display = "none"; + document.getElementById("register-link").style.display = "initial"; +}; + const createImageFromBlob = async (imageBlob) => { const formData = new FormData(); formData.append("image", imageBlob); - const imageCreateResponse = await fetch(`${API_BASE}images?token=${token}`, { + const imageCreateResponse = await fetch(`${api_url}images?token=${token}`, { method: "POST", body: formData, }); @@ -112,6 +133,7 @@ const createImageFromBlob = async (imageBlob) => { }; const interactiveClip = async () => { + getServerUrls(); const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); await chrome.scripting.executeScript({ @@ -123,6 +145,7 @@ const interactiveClip = async () => { }; const autoClip = async () => { + getServerUrls(); showLoading(); try { @@ -138,7 +161,6 @@ const autoClip = async () => { const clipWithInject = async () => { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); - await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ["/inject/clip.js"], @@ -153,7 +175,7 @@ const clipWithAPI = async () => { func: () => document.documentElement.innerHTML, }); - const clipResponse = await fetch(`${API_BASE}clip?token=${token}`, { + const clipResponse = await fetch(`${api_url}clip?token=${token}`, { method: "POST", mode: "cors", cache: "no-cache", @@ -188,31 +210,31 @@ const saveClip = async (clipData) => { } } - const recipeCreateResponse = await fetch( - `${API_BASE}recipes?token=${token}`, - { - method: "POST", - mode: "cors", - cache: "no-cache", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - ...clipData, - imageIds: imageId ? [imageId] : [], - }), + const recipeCreateResponse = await fetch(`${api_url}recipes?token=${token}`, { + method: "POST", + mode: "cors", + cache: "no-cache", + headers: { + "Content-Type": "application/json", }, - ); + body: JSON.stringify({ + ...clipData, + imageIds: imageId ? [imageId] : [], + }), + }); if (!recipeCreateResponse.ok) { switch (recipeCreateResponse.status) { case 401: - chrome.storage.local.set({ token: null }, () => { - window.alert( - "Please Login. It looks like you're logged out. Please click the\ + chrome.storage.local.set( + { token: null, api_url: null, base_url: null }, + () => { + window.alert( + "Please Login. It looks like you're logged out. Please click the\ RecipeSage icon to login again.", - ); - }); + ); + }, + ); break; default: window.alert( @@ -225,8 +247,7 @@ const saveClip = async (clipData) => { } const recipeData = await recipeCreateResponse.json(); - - const url = `https://recipesage.com/#/recipe/${recipeData.id}`; + const url = `${base_url}#/recipe/${recipeData.id}`; chrome.tabs.create({ url, active: true, @@ -237,14 +258,53 @@ const saveClip = async (clipData) => { }, 500); }; +const userDetailsValid = () => { + const username = document.getElementById("email").value; + const password = document.getElementById("password").value; + const server = document.getElementById("server").value; + + if (!username || !password) { + document.getElementById("message").innerText = + "Please enter a username and password"; + return false; + } + + if (!server) { + document.getElementById("message").innerText = + "Please enter a server address"; + return false; + } + + if (!server.startsWith("https://")) { + document.getElementById("message").innerText = + "Please enter a valid https:// URL"; + return false; + } + + return true; +}; + document.addEventListener("DOMContentLoaded", () => { + document.getElementById("default-server-checkbox").onchange = (event) => { + if (event.target.checked) { + hideServerInput(); + } else { + showServerInput(); + } + }; + document.getElementById("server").onchange = (event) => { + // replace ensures that the api_url ends with a forward slash + api_url = event.target.value.replace(/\/?$/, "/"); + }; [...document.getElementsByClassName("logo")].forEach( (logo) => (logo.src = chrome.runtime.getURL( "./images/recipesage-black-trimmed.png", )), ); - document.getElementById("login-submit").onclick = login; + document.getElementById("login-submit").onclick = () => { + if (userDetailsValid()) login(); + }; document.getElementById("password").onkeydown = (event) => { if (event.key === "Enter") login(); }; diff --git a/packages/webextension-v3/src/inject/clip.js b/packages/webextension-v3/src/inject/clip.js index 2c637a6bb..fdf8b4e4f 100644 --- a/packages/webextension-v3/src/inject/clip.js +++ b/packages/webextension-v3/src/inject/clip.js @@ -1,9 +1,7 @@ import { clipRecipe } from "@julianpoy/recipe-clipper/dist/recipe-clipper.mjs"; -chrome.storage.local.get(["token"], async (result) => { - window.RC_ML_CLASSIFY_ENDPOINT = - "https://api.recipesage.com/proxy/ingredient-instruction-classifier?token=" + - result.token; +chrome.storage.local.get(["token", "api_url"], async (result) => { + window.RC_ML_CLASSIFY_ENDPOINT = `${result.api_url}proxy/ingredient-instruction-classifier?token=${result.token}`; const clip = await clipRecipe().catch(() => { alert("Error while attempting to automatically clip recipe from page"); diff --git a/packages/webextension-v3/src/inject/inject.js b/packages/webextension-v3/src/inject/inject.js index e29ceb1b6..aa70e8380 100644 --- a/packages/webextension-v3/src/inject/inject.js +++ b/packages/webextension-v3/src/inject/inject.js @@ -15,10 +15,10 @@ if (window[extensionContainerId]) { console.log("Loading RecipeSage Browser Extension"); - const fetchToken = () => { + const fetchTokenAndUrls = () => { return new Promise((resolve) => { - chrome.storage.local.get(["token"], (result) => { - resolve(result.token); + chrome.storage.local.get(["token", "api_url", "base_url"], (result) => { + resolve(result); }); }); }; @@ -55,10 +55,8 @@ if (window[extensionContainerId]) { autoSnipPending.innerText = "Grabbing Recipe Content..."; autoSnipPendingContainer.appendChild(autoSnipPending); - autoSnipPromise = fetchToken().then((token) => { - window.RC_ML_CLASSIFY_ENDPOINT = - "https://api.recipesage.com/proxy/ingredient-instruction-classifier?token=" + - token; + autoSnipPromise = fetchTokenAndUrls().then((result) => { + window.RC_ML_CLASSIFY_ENDPOINT = `${result.api_url}proxy/ingredient-instruction-classifier?token=${result.token}`; return clipRecipe().catch(() => { alert( @@ -424,7 +422,7 @@ if (window[extensionContainerId]) { let submit = async () => { try { - const token = await fetchToken(); + const result = await fetchTokenAndUrls(); let imageId; try { @@ -435,7 +433,7 @@ if (window[extensionContainerId]) { formData.append("image", imageBlob); const imageCreateResponse = await fetch( - `https://api.recipesage.com/images?token=${token}`, + `${result.api_url}images?token=${result.token}`, { method: "POST", body: formData, @@ -452,7 +450,7 @@ if (window[extensionContainerId]) { } const recipeCreateResponse = await fetch( - `https://api.recipesage.com/recipes?token=${token}`, + `${result.api_url}recipes?token=${result.token}`, { method: "POST", headers: { @@ -472,19 +470,22 @@ if (window[extensionContainerId]) { `Recipe Saved!`, `Click to open`, 4000, - `https://recipesage.com/#/recipe/${data.id}`, + `${result.base_url}#/recipe/${data.id}`, ); }); } else { switch (recipeCreateResponse.status) { case 401: - chrome.storage.local.set({ token: null }, () => { - displayAlert( - "Please Login", - `It looks like you're logged out. Please click the RecipeSage icon to login again.`, - 4000, - ); - }); + chrome.storage.local.set( + { token: null, api_url: null, base_url: null }, + () => { + displayAlert( + "Please Login", + `It looks like you're logged out. Please click the RecipeSage icon to login again.`, + 4000, + ); + }, + ); break; case 412: displayAlert( diff --git a/packages/webextension-v3/src/settings/settings.html b/packages/webextension-v3/src/settings/settings.html index 4597e0620..60942bbb3 100644 --- a/packages/webextension-v3/src/settings/settings.html +++ b/packages/webextension-v3/src/settings/settings.html @@ -12,13 +12,38 @@ text-align: center; font-size: 14px; + font-family: Arial, Helvetica, sans-serif; } - #loggedInMessage, - #loggedOutMessage { + input, + textarea { + width: calc(100% - 16px); + background: none; + border: none; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + padding: 10px; + transition: 0.3s; + background: transparent; + color: initial; + } + + input:disabled { + background-color: lightgray; + } + + #loggedInView, + #loggedOutView { display: none; } + #serverDetails { + text-align: left; + } + + label p { + font-size: 12px; + } + button { height: 32px; padding: 0 11px; @@ -44,13 +69,38 @@

RecipeSage Browser Extension Settings


-
- You're currently logged in as loading... +
+ You're currently logged in as + loading... on + loading....

+
+

Server Details

+ +
+
+   +
+ +
-
+
You're not logged in. Click the RecipeSage icon next to the address bar to log in.

Note: If you don't see the RecipeSage icon next to the address bar, you diff --git a/packages/webextension-v3/src/settings/settings.js b/packages/webextension-v3/src/settings/settings.js index 65fa92cf5..87c8f1ffd 100644 --- a/packages/webextension-v3/src/settings/settings.js +++ b/packages/webextension-v3/src/settings/settings.js @@ -1,35 +1,53 @@ -const loggedInMessage = document.getElementById("loggedInMessage"); -const loggedOutMessage = document.getElementById("loggedOutMessage"); +const loggedInView = document.getElementById("loggedInView"); +const loggedOutView = document.getElementById("loggedOutView"); -chrome.storage.local.get(["token"], async (result) => { +chrome.storage.local.get(["token", "api_url", "base_url"], async (result) => { if (result.token) { try { const response = await fetch( - `https://api.recipesage.com/users?token=${result.token}`, + `${result.api_url}users?token=${result.token}`, ); const data = await response.json(); document.getElementById("loggedInEmail").innerText = data.email; - loggedInMessage.style.display = "block"; + document.getElementById("loggedInServer").innerText = result.api_url; + document.getElementById("baseUrl").value = result.base_url; + document.getElementById("apiUrl").value = result.api_url; + loggedInView.style.display = "block"; } catch (e) { switch (e.status) { case 401: alert("There was an error while fetching your account data."); break; default: - loggedOutMessage.style.display = "block"; + loggedOutView.style.display = "block"; break; } } } else { - loggedOutMessage.style.display = "block"; + loggedOutView.style.display = "block"; } }); const logout = () => { - chrome.storage.local.set({ token: null }, () => { - loggedOutMessage.style.display = "block"; - loggedInMessage.style.display = "none"; + chrome.storage.local.set( + { token: null, api_url: null, base_url: null }, + () => { + loggedOutView.style.display = "block"; + loggedInView.style.display = "none"; + }, + ); +}; + +const updateServerDetails = () => { + const base_url = document.getElementById("baseUrl").value; + chrome.storage.local.set({ base_url }, () => { + document.getElementById("message").innerText = + "Your server details have been updated."; + setTimeout(() => { + document.getElementById("message").innerText = ""; + }, 3000); }); }; document.getElementById("logoutButton").onclick = logout; +document.getElementById("serverDetailsUpdate").onclick = updateServerDetails;