diff --git a/background.js b/background.js index f3eef0d..1949c23 100644 --- a/background.js +++ b/background.js @@ -1,20 +1,85 @@ +// Define your token count at the top of the script +let tokenCount = 0; + +// 4/25/23: Need to revisit this and dig deeper into seeing +// if we are actually removing suggestion to be able to keep the conversation going. +// at some point the conversation cannot continue if it exceeds 4097 tokens. +// seems like I need to make an option to save the conversation and or something... +// Define the token counting function +function countTokens(text) { + let wordCount = text.split(' ').length; + let punctuationCount = (text.match(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g) || []).length; + return wordCount + punctuationCount; +} + async function fetchApiKey() { const { apiKey } = await chrome.storage.sync.get("apiKey"); return apiKey; } -// This is the proper URL to use. -// https://api.openai.com/v1/engines/text-davinci-002/completions +// Initialize the conversation in storage +chrome.runtime.onInstalled.addListener(() => { + chrome.storage.sync.set({ conversation: [] }); +}); + +// Only use this to test the notification notice. +// The version should be one less that the version in the manifest file. +/*chrome.storage.sync.set({ lastNotifiedVersion: '0.0.2', notificationClicked: 'no' }, () => { + console.log('Version set in storage and notification option stored'); +});*/ +/*chrome.storage.sync.get("lastNotifiedVersion", function(data) { + console.log('Last notified version:', data.lastNotifiedVersion); +}); +chrome.storage.sync.get("notificationClicked", function(data) { + console.log('Was notification clicked:', data.notificationClicked); +});*/ + +// Update the last notified version in storage when the extension is updated +chrome.runtime.onInstalled.addListener(details => { + if (details.reason === "update") { + const currentVersion = chrome.runtime.getManifest().version; + chrome.storage.sync.set({ lastNotifiedVersion: currentVersion }); + } +}); + +// Update the conversation in the storage whenever it changes +function updateConversation(convo) { + chrome.storage.sync.set({ conversation: convo }); +} + +let conversation = []; +chrome.storage.sync.get("conversation", function(data) { + if (data.conversation !== undefined) { + conversation = data.conversation; + tokenCount = conversation.reduce((sum, message) => sum + message.tokens, 0); + } +}); + async function getSuggestionsFromApi(apiKey, prompt, maxTokens, n, stop, temperature, engine) { + if (!apiKey) { - throw new Error("API key not provided. Please enter your API key in the settings."); + throw new Error("API key not provided. To get started please click on the Menu option in the top right corner, click on Settings and then enter your API key."); } + // gpt-3.5-turbo + const model = engine === 'gpt3' ? 'gpt-3.5-turbo' : 'gpt-4'; + + const engineURL = 'https://api.openai.com/v1/chat/completions'; - // gpt3: curie - // gpt4: text-davinci-002 - const engineURL = engine === 'gpt3' ? 'https://api.openai.com/v1/engines/curie/completions' : 'https://api.openai.com/v1/engines/text-davinci-002/completions'; + let promptTokens = countTokens(prompt); + conversation.push({role: "user", content: prompt, tokens: promptTokens}); + tokenCount += promptTokens; - console.log( engineURL ); + while (tokenCount > 4096 && conversation.length > 0) { + let removedMessage = conversation.shift(); + tokenCount -= removedMessage.tokens; // Subtract the tokens of the removed message + } + + if (promptTokens > 4096) { + throw new Error("Your message is too long! Please limit messages to 4096 tokens."); + } + + // copy the conversation array and remove tokens property + const conversationCopy = conversation.map(({ role, content }) => ({ role, content })); const response = await fetch(engineURL, { method: "POST", @@ -23,8 +88,9 @@ async function getSuggestionsFromApi(apiKey, prompt, maxTokens, n, stop, tempera "Authorization": `Bearer ${apiKey}` }, body: JSON.stringify({ - prompt: prompt, - max_tokens: parseInt(maxTokens) || 250, + model: model, + messages: conversationCopy, + max_tokens: parseInt(maxTokens) || 2000, n: parseInt(n) || 1, stop: stop || null, temperature: parseFloat(temperature) || 0.1 @@ -35,21 +101,37 @@ async function getSuggestionsFromApi(apiKey, prompt, maxTokens, n, stop, tempera const errorData = await response.json(); if (errorData.error && errorData.error.code === "invalid_api_key") { throw new Error("Invalid API key provided. Please check your API key."); - } else { + } + else if (errorData.error && errorData.error.message === "The model: `gpt-4` does not exist") { + //console.log( errorData.error ); + throw new Error("Please choose the GPT-3 Option from the Settings page to continue. The OpenAI API Key you entered does not have GPT-4 access yet.

You must have explicitly been granted a GPT-4 API Key from OpenAI for the GPT-4 option to work on our Settings page. You can apply for access here https://openai.com/waitlist/gpt-4-api"); + } + else{ throw new Error("Unexpected response from OpenAI API: " + JSON.stringify(errorData)); } } const data = await response.json(); if (data.choices) { - return data.choices.map((choice) => choice.text.trim()); + let suggestionResponses = data.choices.map((choice) => { + let messageContent = choice.message.content.trim(); + let tokens = countTokens(messageContent); + conversation.push({role: "assistant", content: messageContent, tokens: tokens}); + + // Notify loading-suggestions.js that a new suggestion has been added + chrome.runtime.sendMessage({ action: "newSuggestionAdded", tokens: tokens }); + + return messageContent; + }); + return suggestionResponses; } else { console.error("Unexpected response from OpenAI API:", JSON.stringify(data, null, 2)); return []; } } -chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { + +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.action === "getSuggestions") { if (request.text) { fetchApiKey().then(async function (apiKey) { @@ -58,12 +140,13 @@ chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { const suggestions = await getSuggestionsFromApi( apiKey, request.text, - settings.maxTokens || 50, + settings.maxTokens || 2000, settings.n || 1, settings.stop || null, settings.temperature || 0.1, - settings.engine || 'gpt4' + settings.engine || 'gpt3' ); + tokenCount += suggestions.reduce((sum, suggestion) => sum + countTokens(suggestion), 0); sendResponse({ suggestions: suggestions }); } catch (error) { console.error('Error:', error); @@ -77,5 +160,53 @@ chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { console.error("No text provided."); } } + + if (request.action === "clearConversationAndTokenCount") { + conversation = []; // Clear the conversation array + tokenCount = 0; // Clear the token count + updateConversation(conversation); // Save the cleared conversation + sendResponse({ message: "Conversation and token count cleared." }); + return true; + } + + + + if (request.action === "fetchChangelog") { + fetchChangelog() + .then(changelog => sendResponse({changelog: changelog})) + .catch(error => sendResponse({error: error.message})); + return true; // Indicate that we will send a response asynchronously + } }); +function fetchChangelog(attempt = 1) { + const maxAttempts = 3; + const repo = 'https://raw.githubusercontent.com/spencerslickremix/Slick-Personal-Assistant/main/'; + + return new Promise((resolve, reject) => { + fetch(repo + 'changelog.txt') + .then(response => { + if (response.status === 404) { + throw new Error('The changelog could not be found. Please check the file path.'); + } else if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.text(); + }) + .then(changelog => { + resolve(changelog); + }) + .catch(error => { + if (error.message.includes('changelog could not be found')) { + // If the changelog was not found, reject the promise immediately + reject(error); + } else if (attempt < maxAttempts) { + // If the fetch failed for a different reason and we haven't reached the max number of attempts, try again + fetchChangelog(attempt + 1).then(resolve).catch(reject); + } else { + // If we've reached the max number of attempts, reject the promise + reject(error); + } + }); + }); +} diff --git a/changelog.txt b/changelog.txt new file mode 100644 index 0000000..b68518a --- /dev/null +++ b/changelog.txt @@ -0,0 +1,28 @@ += Version 0.0.3 Monday, May 29th, 2023 = + * New: Settings: Custom font size option. + * New: Settings: Custom background and color options. + * New: Settings: Choose the width of the Popup. + * New: Suggestions: Added Token Count so users can see how many tokens are used per response. This text will also link you directly to the Settings page for quicker navigation. + * New: Header: The SlickRemix logo now links to the website, and the text "Your Personal Automated Assistant" links to the Suggestions tab for quicker navigation. + * New: Organized files in their respective folders. + * New: Namespaced and classify entire extension. + * Updated: Select a portion of text from a response to be copied automatically. + * Updated: Settings description for Max Tokens and Temperature to be more clear. + * Fixed: Added additional checks to Regex to parse URLs properly. We will probably end up adding a library in the future. + * Fixed: Added error messages to the suggestion list so users know when there is a problem. + * Fixed: Added GPT-4 specific error message to let users know if the API token they have entered will work with GPT-4 if they chose that model. + * Removed: content_scripts: matches": ["http://*/*", "https://*/*"] & js": ["contentScript.js"]. These options were not needed because I am calling chrome.scripting.executeScript from the popup.js file already, so the call was being duplicated. + += Version 0.0.2 Thursday, May 18th, 2023 = + * New: Replaced model davinci-003 with gpt-3.5-turbo + * New: Updated completions to use /chat/completions + * New: Save user and response text to array to chrome.storage + * New: Complete UI change. Made Dark Mode true by default now. + * New: Regex to parse URLs properly. + * New: Settings: Display Twitter icon to quickly share responses. + * New: Custom Prompts tab. Includes the option to add up to 5 custom prompts or chose to display prompts saved from a GitHub accounts readme.md file. + * New: Showdown.js to convert code to HTML and Prism.js to beatify the code. + * Removed: permissions: tabs. Was not actually needed and was warned by Google it needed to be removed. + += Version 0.0.1 Monday, May 2nd, 2023 = + * Initial Release \ No newline at end of file diff --git a/content.js b/content.js deleted file mode 100644 index debd73a..0000000 --- a/content.js +++ /dev/null @@ -1,18 +0,0 @@ -chrome.runtime.onConnect.addListener((port) => { - if (port.name === "replaceSelectedText") { - port.onMessage.addListener((request) => { - if (request.action === 'replaceSelectedText') { - const selectedText = window.getSelection(); - const activeElement = document.activeElement; - - if (activeElement.tagName.toLowerCase() === 'textarea' && selectedText.toString().length > 0) { - const newText = activeElement.value.substring(0, activeElement.selectionStart) + request.replacementText + activeElement.value.substring(activeElement.selectionEnd); - activeElement.value = newText; - port.postMessage({ success: true }); - } else { - port.postMessage({ success: false, message: 'No text is selected in a textarea.' }); - } - } - }); - } -}); diff --git a/layout/css/custom/background/dark.css b/layout/css/custom/background/dark.css new file mode 100644 index 0000000..1b1f5c7 --- /dev/null +++ b/layout/css/custom/background/dark.css @@ -0,0 +1,93 @@ +body { + background: #202123; +} +.header { + color: #ececf1; +} +.tabcontent p { + color: #c6c6c6; +} +.container input { + color: #ececf1; +} +.toggle-label { + color: #b6b6b6; +} +label { + color: #939393 !important; +} +.suggestionsLink.slickremix-title { + color: #ffffff; +} +span.slickremix-logo:before { + color: #f0f0f0; +} +.switch-description { + color: #f0f0f0; +} +.tab { + background-color: #f2f2f2; + border-bottom: 1px solid #e0e0e0; +} +.settings-content { + background-color: #ececf1; +} +.suggestion { + background: #40414f; + color:#dbdbdb +} +.tabcontent h3 { + color: #f9f8f8; +} +.suggestion.user-input { + background: #202123; +} +#suggestions-tab .content { + background: #40414f; +} +textarea { + border: 1px solid #e0e0e0; + color:#d7d7d7 +} +.copied-overlay { + color: #ececf1; +} +.dot { + background-color: #ececf1; +} +#githubUrlContainer .helper-text { + color: #ececf1 +} +.switch-wrap { + background: #444654; +} +.prompt-wrap { + background: #444654; +} +.material-tooltip { + background: #2e313b; +} +.settings-menu li { + background: #202123; +} +#custom-context-menu { + background-color: #202123; +} +#changelog-container { + color: #fff; + border-top: 1px dashed #4f5052; +} +input:-webkit-autofill, +textarea:-webkit-autofill, +select:-webkit-autofill { + -webkit-box-shadow: 0 0 0 1000px #202123 inset !important; + /*use inset box-shadow to cover background-color*/ + -webkit-text-fill-color: #ffffff !important; + /*use text fill color to cover font color*/ +} +#custom-context-menu-settings-tab input:-webkit-autofill, +#custom-context-menu-settings-tab textarea:-webkit-autofill, +#custom-context-menu-settings-tab select:-webkit-autofill { + -webkit-box-shadow: 0 0 0 1000px #444654 inset !important; + /*use inset box-shadow to cover background-color*/ +} \ No newline at end of file diff --git a/layout/css/custom/background/darker.css b/layout/css/custom/background/darker.css new file mode 100644 index 0000000..c879192 --- /dev/null +++ b/layout/css/custom/background/darker.css @@ -0,0 +1,93 @@ +body { + background: #000000; +} +.header { + color: #ffffff; +} +.tabcontent p { + color: #c6c6c6; +} +.container input { + color: #ececf1; +} +.toggle-label { + color: #b6b6b6; +} +label { + color: #939393 !important; +} +.suggestionsLink.slickremix-title { + color: #ffffff; +} +span.slickremix-logo:before { + color: #f0f0f0; +} +.switch-description { + color: #f0f0f0; +} +.tab { + background-color: #f2f2f2; + border-bottom: 1px solid #e0e0e0; +} +.settings-content { + background-color: #ececf1; +} +.suggestion { + background: #1a1a1a; + color:#dbdbdb +} +.tabcontent h3 { + color: #f9f8f8; +} +.suggestion.user-input { + background: #000000; +} +#suggestions-tab .content { + background: #1a1a1a; +} +textarea { + border: 1px solid #e0e0e0; + color:#d7d7d7 +} +.copied-overlay { + color: #ececf1; +} +.dot { + background-color: #ececf1; +} +#githubUrlContainer .helper-text { + color: #ececf1 +} +.switch-wrap { + background: #19191a; +} +.prompt-wrap { + background: #1a1a1a; +} +.material-tooltip { + background: #2c2c2c; +} +.settings-menu li { + background: #141414; +} +#custom-context-menu { + background-color: #000000; +} +#changelog-container { + color: #fff; + border-top: 1px dashed #4f5052; +} +input:-webkit-autofill, +textarea:-webkit-autofill, +select:-webkit-autofill { + -webkit-box-shadow: 0 0 0 1000px #000000 inset !important; + /*use inset box-shadow to cover background-color*/ + -webkit-text-fill-color: #ececf1 !important; + /*use text fill color to cover font color*/ +} +#custom-context-menu-settings-tab input:-webkit-autofill, +#custom-context-menu-settings-tab textarea:-webkit-autofill, +#custom-context-menu-settings-tab select:-webkit-autofill { + -webkit-box-shadow: 0 0 0 1000px #1a1a1a inset !important; + /*use inset box-shadow to cover background-color*/ +} \ No newline at end of file diff --git a/layout/css/custom/background/default.css b/layout/css/custom/background/default.css new file mode 100644 index 0000000..a145619 --- /dev/null +++ b/layout/css/custom/background/default.css @@ -0,0 +1,69 @@ +.header { + color: #ffffff; +} +.switch-description { + color: #686767; +} +.tabcontent p { + color: #989696; +} +.suggestionsLink.slickremix-title { + color: #ffffff; +} +span.slickremix-logo:before { + color: #f0f0f0; +} +label { + color: #929292 !important; +} +.tab { + background-color: #f2f2f2; + border-bottom: 1px solid #e0e0e0; +} +.settings-content { + background-color: #fff; +} +.suggestion { + background: #f7f7f7; +} +#suggestions-tab .content { + background: #f7f7f7; +} +textarea { + border: 1px solid #e0e0e0; + color:#444444; +} +.copied-overlay { + color: #fff; +} +.dot { + background-color: #939393; +} +#githubUrlContainer .helper-text { + color: #989696 +} +.switch-wrap { + background: #f7f7f7; +} +.prompt-wrap { + box-shadow: 1px 2px 20px 1px #e3dfdf85; + background: ffffff; +} +.settings-menu li { + background: #0e0e0e; +} +#custom-context-menu { + background-color: #222222; +} +#changelog-container { + color: #555555; + border-top: 1px dashed #4f5052; +} +input:-webkit-autofill, +textarea:-webkit-autofill, +select:-webkit-autofill { + -webkit-box-shadow: 0 0 0 1000px #ffffff inset !important; + /*use inset box-shadow to cover background-color*/ + -webkit-text-fill-color: #000000 !important; + /*use text fill color to cover font color*/ +} \ No newline at end of file diff --git a/layout/css/custom/background/twitter.css b/layout/css/custom/background/twitter.css new file mode 100644 index 0000000..adaee19 --- /dev/null +++ b/layout/css/custom/background/twitter.css @@ -0,0 +1,93 @@ +body { + background: #15202b; +} +.header { + color: #ffffff; +} +.tabcontent p { + color: #c6c6c6; +} +.container input { + color: #ffffff; +} +.toggle-label { + color: #b6b6b6; +} +label { + color: #c7c7c7 !important; +} +.suggestionsLink.slickremix-title { + color: #ffffff; +} +span.slickremix-logo:before { + color: #f0f0f0; +} +.switch-description { + color: #f0f0f0; +} +.tab { + background-color: #f2f2f2; + border-bottom: 1px solid #e0e0e0; +} +.settings-content { + background-color: #fff; +} +.suggestion { + background: #18293a; + color:#dbdbdb +} +.tabcontent h3 { + color: #f9f8f8; +} +.suggestion.user-input { + background: #18293a00; +} +#suggestions-tab .content { + background: #18293a; +} +textarea { + border: 1px solid #e0e0e0; + color:#d7d7d7 +} +.copied-overlay { + color: #fff; +} +.dot { + background-color: #fff; +} +#githubUrlContainer .helper-text { + color: #fff +} +.switch-wrap { + background: #18293a; +} +.prompt-wrap { + background: #18293a; +} +.material-tooltip { + background: #152634; +} +.settings-menu li { + background: #15202b; +} +#custom-context-menu { + background-color: #18293a; +} +#changelog-container { + color: #fff; + border-top: 1px dashed #4f5052; +} +input:-webkit-autofill, +textarea:-webkit-autofill, +select:-webkit-autofill { + -webkit-box-shadow: 0 0 0 1000px #15202b inset !important; + /*use inset box-shadow to cover background-color*/ + -webkit-text-fill-color: #ffffff !important; + /*use text fill color to cover font color*/ +} +#custom-context-menu-settings-tab input:-webkit-autofill, +#custom-context-menu-settings-tab textarea:-webkit-autofill, +#custom-context-menu-settings-tab select:-webkit-autofill { + -webkit-box-shadow: 0 0 0 1000px #18293a inset !important; + /*use inset box-shadow to cover background-color*/ +} \ No newline at end of file diff --git a/layout/css/custom/color/black.css b/layout/css/custom/color/black.css new file mode 100644 index 0000000..516acc4 --- /dev/null +++ b/layout/css/custom/color/black.css @@ -0,0 +1,109 @@ +.header { + background-color: #121212; +} +a, .default-mode.black-color a { + color: #020202; +} +.dark-mode a, .twitter-mode a, +.darker-mode a, .twitter-mode a{ + color:#fefefe +} + +.tablinks:hover:not(.active) { + background-color: #000000; +} + +input:not([type]):focus:not([readonly]), input[type=text]:not(.browser-default):focus:not([readonly]), input[type=password]:not(.browser-default):focus:not([readonly]), input[type=email]:not(.browser-default):focus:not([readonly]), input[type=url]:not(.browser-default):focus:not([readonly]), input[type=time]:not(.browser-default):focus:not([readonly]), input[type=date]:not(.browser-default):focus:not([readonly]), input[type=datetime]:not(.browser-default):focus:not([readonly]), input[type=datetime-local]:not(.browser-default):focus:not([readonly]), input[type=tel]:not(.browser-default):focus:not([readonly]), input[type=number]:not(.browser-default):focus:not([readonly]), input[type=search]:not(.browser-default):focus:not([readonly]), textarea.materialize-textarea:focus:not([readonly]) { + border-bottom: 1px solid #000000; + -webkit-box-shadow: 0 1px 0 0 #000000; + box-shadow: 0 1px 0 0 #000000; +} + +.settings-form input:focus { + border-color: #000000; + box-shadow: 0 0 5px rgba(0, 92, 185, 0.25); +} + +button { + background-color: #1a1a1a; +} +.darker-mode .content button.submit-button-suggestions { + background-color: #000000; +} +.darker-mode .content button { + background-color: #1a1a1a; +} +.saved-prompts-list { + background: #1a1a1a; +} +.default-mode .saved-prompts-list a { + color: #FFFFFF; +} +#font-size::-webkit-slider-runnable-track, #popup-size::-webkit-slider-runnable-track { + background: #000000; +} + +.darker-mode .settings-menu li { + background: #1a1a1a; +} + +.settings-menu li.active { + background: #000000; +} + +.darker-mode .settings-menu li.active { + background: #141414; +} + +input[type=range]+.thumb { + background-color: #000000; +} + +#font-size::-webkit-slider-runnable-track, #popup-size::-webkit-slider-runnable-track { + background: #000000; +} + +#customPromptsLink, .suggestionsLink { + color: #444444; +} +.twitter-mode #customPromptsLink, +.twitter-mode .suggestionsLink, +.black-mode #customPromptsLink, +.black-mode .suggestionsLink, +.dark-mode #customPromptsLink, +.dark-mode .suggestionsLink, +.darker-mode #customPromptsLink, +.darker-mode .suggestionsLink{ + color: #ffffff; + font-weight:bold; +} +.default-mode #customPromptsLink, +.default-mode .suggestionsLink{ + color: #000000; +} +.default-mode .header .suggestionsLink, +.dark-mode .header .suggestionsLink, +.darker-mode .header .suggestionsLink{ + color: #e8e6e6; + font-weight:normal; +} +.switch label input[type=checkbox]:checked + .lever { + background-color: #000000 !important; +} + +#custom-context-menu li:hover { + background-color: #1a1a1a; +} + +#changelog-container { + border-top: 2px dashed #4f5052; +} + +textarea:focus { + border-color: #777777; + box-shadow: 0 0 5px rgba(0, 92, 185, 0.25); +} + +button:hover, button:focus { + background-color: #3b3b3b; +} \ No newline at end of file diff --git a/layout/css/custom/color/blue.css b/layout/css/custom/color/blue.css new file mode 100644 index 0000000..3d57f01 --- /dev/null +++ b/layout/css/custom/color/blue.css @@ -0,0 +1,66 @@ +.header { + background-color: #005cb9; +} +a { + color: #197de3; +} +.tablinks:hover:not(.active) { + background-color: #005cb9; +} + +input:not([type]):focus:not([readonly]), input[type=text]:not(.browser-default):focus:not([readonly]), input[type=password]:not(.browser-default):focus:not([readonly]), input[type=email]:not(.browser-default):focus:not([readonly]), input[type=url]:not(.browser-default):focus:not([readonly]), input[type=time]:not(.browser-default):focus:not([readonly]), input[type=date]:not(.browser-default):focus:not([readonly]), input[type=datetime]:not(.browser-default):focus:not([readonly]), input[type=datetime-local]:not(.browser-default):focus:not([readonly]), input[type=tel]:not(.browser-default):focus:not([readonly]), input[type=number]:not(.browser-default):focus:not([readonly]), input[type=search]:not(.browser-default):focus:not([readonly]), textarea.materialize-textarea:focus:not([readonly]) { + border-bottom: 1px solid #005cb9; + -webkit-box-shadow: 0 1px 0 0 #005cb9; + box-shadow: 0 1px 0 0 #005cb9; +} + +.settings-form input:focus { + border-color: #005cb9; + box-shadow: 0 0 5px rgba(0, 92, 185, 0.25); +} + + +button { + background-color: #005cb9; +} + +.saved-prompts-list { + background: #005cb9; +} + +#font-size::-webkit-slider-runnable-track, #popup-size::-webkit-slider-runnable-track { + background: #005cb9; +} + +.settings-menu li.active { + background: #005cb9; +} + +input[type=range]+.thumb { + background-color: #005cb9; +} + +#customPromptsLink, .suggestionsLink { + color: #187de3; +} + +.switch label input[type=checkbox]:checked + .lever { + background-color: #005cb9 !important; +} + +#custom-context-menu li:hover { + background-color: #005cb9; +} + +#changelog-container { + border-top: 2px dashed #005cb9; +} + +textarea:focus { + border-color: #0071ed; + box-shadow: 0 0 5px rgba(0, 92, 185, 0.25); +} + +button:hover, button:focus { + background-color: #0071ed; +} \ No newline at end of file diff --git a/layout/css/custom/color/green.css b/layout/css/custom/color/green.css new file mode 100644 index 0000000..8eb95f3 --- /dev/null +++ b/layout/css/custom/color/green.css @@ -0,0 +1,69 @@ +.header { + background-color: #00ba7c; +} +a { + color:#00ba7c; +} +.tablinks:hover:not(.active) { + background-color: #00ba7c; +} + +input:not([type]):focus:not([readonly]), input[type=text]:not(.browser-default):focus:not([readonly]), input[type=password]:not(.browser-default):focus:not([readonly]), input[type=email]:not(.browser-default):focus:not([readonly]), input[type=url]:not(.browser-default):focus:not([readonly]), input[type=time]:not(.browser-default):focus:not([readonly]), input[type=date]:not(.browser-default):focus:not([readonly]), input[type=datetime]:not(.browser-default):focus:not([readonly]), input[type=datetime-local]:not(.browser-default):focus:not([readonly]), input[type=tel]:not(.browser-default):focus:not([readonly]), input[type=number]:not(.browser-default):focus:not([readonly]), input[type=search]:not(.browser-default):focus:not([readonly]), textarea.materialize-textarea:focus:not([readonly]) { + border-bottom: 1px solid #00ba7c; + -webkit-box-shadow: 0 1px 0 0 #00ba7c; + box-shadow: 0 1px 0 0 #00ba7c; +} + +.settings-form input:focus { + border-color: #00ba7c; + box-shadow: 0 0 5px rgba(0, 92, 185, 0.25); +} + + +button { + background-color: #00ba7c; +} + +.saved-prompts-list { + background: #00ba7c; +} + +#font-size::-webkit-slider-runnable-track, #popup-size::-webkit-slider-runnable-track { + background: #00ba7c; +} + +.settings-menu li.active { + background: #00ba7c; +} + +input[type=range]+.thumb { + background-color: #00ba7c; +} + +#font-size::-webkit-slider-runnable-track, #popup-size::-webkit-slider-runnable-track { + background: #00ba7c; +} + +#customPromptsLink, .suggestionsLink { + color: #00ba7c; +} + +.switch label input[type=checkbox]:checked + .lever { + background-color: #00ba7c !important; +} + +#custom-context-menu li:hover { + background-color: #00ba7c; +} + +#changelog-container { + border-top: 2px dashed #00ba7c; +} + +textarea:focus { + border-color: #0dda94; +} + +button:hover, button:focus { + background-color: #0dda94; +} diff --git a/layout/css/custom/color/orange.css b/layout/css/custom/color/orange.css new file mode 100644 index 0000000..0669313 --- /dev/null +++ b/layout/css/custom/color/orange.css @@ -0,0 +1,69 @@ +.header { + background-color: #ff7900; +} +a { + color:#ff7900; +} +.tablinks:hover:not(.active) { + background-color: #ff7900; +} + +input:not([type]):focus:not([readonly]), input[type=text]:not(.browser-default):focus:not([readonly]), input[type=password]:not(.browser-default):focus:not([readonly]), input[type=email]:not(.browser-default):focus:not([readonly]), input[type=url]:not(.browser-default):focus:not([readonly]), input[type=time]:not(.browser-default):focus:not([readonly]), input[type=date]:not(.browser-default):focus:not([readonly]), input[type=datetime]:not(.browser-default):focus:not([readonly]), input[type=datetime-local]:not(.browser-default):focus:not([readonly]), input[type=tel]:not(.browser-default):focus:not([readonly]), input[type=number]:not(.browser-default):focus:not([readonly]), input[type=search]:not(.browser-default):focus:not([readonly]), textarea.materialize-textarea:focus:not([readonly]) { + border-bottom: 1px solid #ff7900; + -webkit-box-shadow: 0 1px 0 0 #ff7900; + box-shadow: 0 1px 0 0 #ff7900; +} + +.settings-form input:focus { + border-color: #ff7900; + box-shadow: 0 0 5px rgba(0, 92, 185, 0.25); +} + + +button { + background-color: #ff7900; +} + +.saved-prompts-list { + background: #ff7900; +} + +#font-size::-webkit-slider-runnable-track, #popup-size::-webkit-slider-runnable-track { + background: #ff7900; +} + +.settings-menu li.active { + background: #ff7900; +} + +input[type=range]+.thumb { + background-color: #ff7900; +} + +#font-size::-webkit-slider-runnable-track, #popup-size::-webkit-slider-runnable-track { + background: #ff7900; +} + +#customPromptsLink, .suggestionsLink { + color: #ff7900; +} + +.switch label input[type=checkbox]:checked + .lever { + background-color: #ff7900 !important; +} + +#custom-context-menu li:hover { + background-color: #ff7900; +} + +#changelog-container { + border-top: 2px dashed #ff7900; +} + +textarea:focus { + border-color: #ff8018; +} + +button:hover, button:focus { + background-color: #ff9b0d; +} diff --git a/layout/css/custom/color/pink.css b/layout/css/custom/color/pink.css new file mode 100644 index 0000000..b473ec3 --- /dev/null +++ b/layout/css/custom/color/pink.css @@ -0,0 +1,69 @@ +.header { + background-color: #f91980; +} +a { + color:#f91980; +} +.tablinks:hover:not(.active) { + background-color: #f91980; +} + +input:not([type]):focus:not([readonly]), input[type=text]:not(.browser-default):focus:not([readonly]), input[type=password]:not(.browser-default):focus:not([readonly]), input[type=email]:not(.browser-default):focus:not([readonly]), input[type=url]:not(.browser-default):focus:not([readonly]), input[type=time]:not(.browser-default):focus:not([readonly]), input[type=date]:not(.browser-default):focus:not([readonly]), input[type=datetime]:not(.browser-default):focus:not([readonly]), input[type=datetime-local]:not(.browser-default):focus:not([readonly]), input[type=tel]:not(.browser-default):focus:not([readonly]), input[type=number]:not(.browser-default):focus:not([readonly]), input[type=search]:not(.browser-default):focus:not([readonly]), textarea.materialize-textarea:focus:not([readonly]) { + border-bottom: 1px solid #f91980; + -webkit-box-shadow: 0 1px 0 0 #f91980; + box-shadow: 0 1px 0 0 #f91980; +} + +.settings-form input:focus { + border-color: #f91980; + box-shadow: 0 0 5px rgba(0, 92, 185, 0.25); +} + + +button { + background-color: #f91980; +} + +.saved-prompts-list { + background: #f91980; +} + +#font-size::-webkit-slider-runnable-track, #popup-size::-webkit-slider-runnable-track { + background: #f91980; +} + +.settings-menu li.active { + background: #f91980; +} + +input[type=range]+.thumb { + background-color: #f91980; +} + +#font-size::-webkit-slider-runnable-track, #popup-size::-webkit-slider-runnable-track { + background: #f91980; +} + +#customPromptsLink, .suggestionsLink { + color: #f91980; +} + +.switch label input[type=checkbox]:checked + .lever { + background-color: #f91980 !important; +} + +#custom-context-menu li:hover { + background-color: #f91980; +} + +#changelog-container { + border-top: 2px dashed #f91980; +} + +textarea:focus { + border-color: #e31172; +} + +button:hover, button:focus { + background-color: #e31172; +} diff --git a/layout/css/custom/color/purple.css b/layout/css/custom/color/purple.css new file mode 100644 index 0000000..87b4c91 --- /dev/null +++ b/layout/css/custom/color/purple.css @@ -0,0 +1,69 @@ +.header { + background-color: #7856ff; +} +a { + color:#7856ff; +} +.tablinks:hover:not(.active) { + background-color: #7856ff; +} + +input:not([type]):focus:not([readonly]), input[type=text]:not(.browser-default):focus:not([readonly]), input[type=password]:not(.browser-default):focus:not([readonly]), input[type=email]:not(.browser-default):focus:not([readonly]), input[type=url]:not(.browser-default):focus:not([readonly]), input[type=time]:not(.browser-default):focus:not([readonly]), input[type=date]:not(.browser-default):focus:not([readonly]), input[type=datetime]:not(.browser-default):focus:not([readonly]), input[type=datetime-local]:not(.browser-default):focus:not([readonly]), input[type=tel]:not(.browser-default):focus:not([readonly]), input[type=number]:not(.browser-default):focus:not([readonly]), input[type=search]:not(.browser-default):focus:not([readonly]), textarea.materialize-textarea:focus:not([readonly]) { + border-bottom: 1px solid #7856ff; + -webkit-box-shadow: 0 1px 0 0 #7856ff; + box-shadow: 0 1px 0 0 #7856ff; +} + +.settings-form input:focus { + border-color: #7856ff; + box-shadow: 0 0 5px rgba(0, 92, 185, 0.25); +} + + +button { + background-color: #7856ff; +} + +.saved-prompts-list { + background: #7856ff; +} + +#font-size::-webkit-slider-runnable-track, #popup-size::-webkit-slider-runnable-track { + background: #7856ff; +} + +.settings-menu li.active { + background: #7856ff; +} + +input[type=range]+.thumb { + background-color: #7856ff; +} + +#font-size::-webkit-slider-runnable-track, #popup-size::-webkit-slider-runnable-track { + background: #7856ff; +} + +#customPromptsLink, .suggestionsLink { + color: #7856ff; +} + +.switch label input[type=checkbox]:checked + .lever { + background-color: #7856ff !important; +} + +#changelog-container { + border-top: 2px dashed #7856ff; +} + +#custom-context-menu li:hover { + background-color: #7856ff; +} + +textarea:focus { + border-color: #886cff; +} + +button:hover, button:focus { + background-color: #886cff; +} diff --git a/layout/css/custom/color/yellow.css b/layout/css/custom/color/yellow.css new file mode 100644 index 0000000..4147dc3 --- /dev/null +++ b/layout/css/custom/color/yellow.css @@ -0,0 +1,70 @@ +.header { + background-color: #ffd400; +} +a { + color:#ffd400; +} +.tablinks:hover:not(.active) { + background-color: #ffd400; +} + +input:not([type]):focus:not([readonly]), input[type=text]:not(.browser-default):focus:not([readonly]), input[type=password]:not(.browser-default):focus:not([readonly]), input[type=email]:not(.browser-default):focus:not([readonly]), input[type=url]:not(.browser-default):focus:not([readonly]), input[type=time]:not(.browser-default):focus:not([readonly]), input[type=date]:not(.browser-default):focus:not([readonly]), input[type=datetime]:not(.browser-default):focus:not([readonly]), input[type=datetime-local]:not(.browser-default):focus:not([readonly]), input[type=tel]:not(.browser-default):focus:not([readonly]), input[type=number]:not(.browser-default):focus:not([readonly]), input[type=search]:not(.browser-default):focus:not([readonly]), textarea.materialize-textarea:focus:not([readonly]) { + border-bottom: 1px solid #ffd400; + -webkit-box-shadow: 0 1px 0 0 #ffd400; + box-shadow: 0 1px 0 0 #ffd400; +} + +.settings-form input:focus { + border-color: #ffd400; + box-shadow: 0 0 5px rgba(0, 92, 185, 0.25); +} + + +button { + background-color: #ffd400; +} + +.saved-prompts-list { + background: #ffd400; +} + +#font-size::-webkit-slider-runnable-track, #popup-size::-webkit-slider-runnable-track { + background: #ffd400; +} + +.settings-menu li.active { + background: #ffd400; +} + +input[type=range]+.thumb { + background-color: #ffd400; +} + +#font-size::-webkit-slider-runnable-track, #popup-size::-webkit-slider-runnable-track { + background: #ffd400; +} + +#customPromptsLink, .suggestionsLink { + color: #ffd400; +} + +.switch label input[type=checkbox]:checked + .lever { + background-color: #ffd400 !important; +} + +#custom-context-menu li:hover { + background-color: #ffd400; +} + +#changelog-container { + border-top: 2px dashed #ffd400; +} + +textarea:focus { + border-color: #ffe04a; + box-shadow: 0 0 5px rgba(0, 92, 185, 0.25); +} + +button:hover, button:focus { + background-color: #ffe04a; +} diff --git a/layout/css/fonts/SlickRemix.eot b/layout/css/fonts/SlickRemix.eot new file mode 100644 index 0000000..e69de29 diff --git a/layout/css/fonts/SlickRemix.svg b/layout/css/fonts/SlickRemix.svg new file mode 100644 index 0000000..3179ad0 --- /dev/null +++ b/layout/css/fonts/SlickRemix.svg @@ -0,0 +1,15 @@ + + + + Generated by IcoMoon + + + + + + + + + \ No newline at end of file diff --git a/layout/css/fonts/SlickRemix.ttf b/layout/css/fonts/SlickRemix.ttf new file mode 100644 index 0000000..cc2eb08 Binary files /dev/null and b/layout/css/fonts/SlickRemix.ttf differ diff --git a/layout/css/fonts/SlickRemix.woff b/layout/css/fonts/SlickRemix.woff new file mode 100644 index 0000000..e69de29 diff --git a/layout/css/popup.css b/layout/css/popup.css index 9b0d02b..37d733b 100644 --- a/layout/css/popup.css +++ b/layout/css/popup.css @@ -1,35 +1,61 @@ +html { + font-size: var(--font-size, 100%); +} + body { font-family: Arial, sans-serif; margin: 0; - padding: 0; - width: 400px; + padding: 70px 0 0; + width: 450px; display: flex; flex-direction: column; - /* min-height: 100vh; */ } +@font-face { + font-family: SlickRemix; + src: url(fonts/SlickRemix.eot?p9iqv); + src: url(fonts/SlickRemix.eot?#iefixp9iqv) format('embedded-opentype'), url(fonts/SlickRemix.ttf?p9iqv) format('truetype'), url(fonts/SlickRemix.svg?p9iqv#SlickRemix) format('svg'); + font-weight: 400; + font-style: normal +} + +span.slickremix-logo:before { + font-family: SlickRemix; + content: "\e600"; + line-height: 1.2em; + font-size: 22px; + margin-right: 9px; + font-style: normal; +} + +.slickremix-title { + position: relative; + top: -3px; + font-style: italic; + font-size: 16px; +} + + .header { - background-color: #005cb9; - color: white; - text-align: center; - padding: 16px; - font-size: 17px; - font-weight: 600; + text-align: left; + padding: 18px 18px 18px 20px; + font-weight: normal; position: fixed; top: 0; left: 0; width: 100%; - z-index: 10; + z-index: 11; + box-shadow: 0px 0px 3px 0px #000000; } .tab { margin-top: 60px; display: flex; justify-content: space-around; - background-color: #f2f2f2; padding: 10px 0; - border-bottom: 1px solid #e0e0e0; width: 100%; + z-index: 1; + position: relative; } .tablinks { @@ -38,41 +64,45 @@ body { outline: none; cursor: pointer; padding: 10px 16px; - font-size: 14px; - color: #666; + font-size:1.2em; + font-weight: normal; + color: #b0afaf; } .tablinks.active { - background-color: #fffdfd; - color: #333; + /* background-color: #fffdfd; */ + /* color: #333; */ } .suggestion { - padding: 10px 15px; - background:#f7f7f7; - border:1px solid #dcd9d9; - border-radius:3px; - font-size:14px; - margin:10px 0; - overflow: hidden; + white-space: pre-wrap; + padding: 17px 25px; + border-radius: 3px; + font-size:1.2em; + margin: 10px 0; + word-break: break-word; + overflow-wrap: break-word; + position: relative; + color: #444444; } -button:focus { + +.suggestion button:focus { outline: none; - background: none; + background: none !important; } -.submit-button-suggestions:focus, .submit-button:focus { - background:#015cb9; + +button:focus { + outline: none; } .tablinks:hover:not(.active) { - background-color: #ffffff; - color: #333; + color: #fff; } .tabcontent { display: none; flex-grow: 1; - padding: 16px; /* Add padding to the content */ + padding: 15px 10px; /* Add padding to the content */ } .settings-form { @@ -87,34 +117,46 @@ button:focus { } label { - position:relative; - padding-left: 23px; - color: #929292; + font-size: .84rem; + position: relative; + padding-left: 5px; } -.switch label { + +.gpt-description { + padding: 0 0 20px 0; + font-size:1.1em; +} + +.gpt-description strong { + font-weight: bold; +} + +#custom-context-menu-settings-tab label { padding-left: 0px; } +#custom-context-menu-settings-tab input { + margin-bottom: 10px; +} -.toggle-label { - display:inline-block; - min-width:35px; +.switch label { + padding-left: 0px; } -input:not([type]):focus:not([readonly]), input[type=text]:not(.browser-default):focus:not([readonly]), input[type=password]:not(.browser-default):focus:not([readonly]), input[type=email]:not(.browser-default):focus:not([readonly]), input[type=url]:not(.browser-default):focus:not([readonly]), input[type=time]:not(.browser-default):focus:not([readonly]), input[type=date]:not(.browser-default):focus:not([readonly]), input[type=datetime]:not(.browser-default):focus:not([readonly]), input[type=datetime-local]:not(.browser-default):focus:not([readonly]), input[type=tel]:not(.browser-default):focus:not([readonly]), input[type=number]:not(.browser-default):focus:not([readonly]), input[type=search]:not(.browser-default):focus:not([readonly]), textarea.materialize-textarea:focus:not([readonly]) { - border-bottom: 1px solid #1a73e8; - -webkit-box-shadow: 0 1px 0 0 #1a73e8; - box-shadow: 0 1px 0 0 #1a73e8; + +.toggle-label { + display: inline-block; + min-width: 35px; } -input:not([type]):focus:not([readonly])+label, input[type=text]:not(.browser-default):focus:not([readonly])+label, input[type=password]:not(.browser-default):focus:not([readonly])+label, input[type=email]:not(.browser-default):focus:not([readonly])+label, input[type=url]:not(.browser-default):focus:not([readonly])+label, input[type=time]:not(.browser-default):focus:not([readonly])+label, input[type=date]:not(.browser-default):focus:not([readonly])+label, input[type=datetime]:not(.browser-default):focus:not([readonly])+label, input[type=datetime-local]:not(.browser-default):focus:not([readonly])+label, input[type=tel]:not(.browser-default):focus:not([readonly])+label, input[type=number]:not(.browser-default):focus:not([readonly])+label, input[type=search]:not(.browser-default):focus:not([readonly])+label, textarea.materialize-textarea:focus:not([readonly])+label { +input:not([type]):focus:not([readonly]) + label, input[type=text]:not(.browser-default):focus:not([readonly]) + label, input[type=password]:not(.browser-default):focus:not([readonly]) + label, input[type=email]:not(.browser-default):focus:not([readonly]) + label, input[type=url]:not(.browser-default):focus:not([readonly]) + label, input[type=time]:not(.browser-default):focus:not([readonly]) + label, input[type=date]:not(.browser-default):focus:not([readonly]) + label, input[type=datetime]:not(.browser-default):focus:not([readonly]) + label, input[type=datetime-local]:not(.browser-default):focus:not([readonly]) + label, input[type=tel]:not(.browser-default):focus:not([readonly]) + label, input[type=number]:not(.browser-default):focus:not([readonly]) + label, input[type=search]:not(.browser-default):focus:not([readonly]) + label, textarea.materialize-textarea:focus:not([readonly]) + label { color: #a4a6a7; } .settings-form input { width: 100%; padding: 8px; - font-size: 14px; + font-size:1.2em; border: 1px solid #e0e0e0; border-radius: 6px; box-sizing: border-box; @@ -122,35 +164,22 @@ input:not([type]):focus:not([readonly])+label, input[type=text]:not(.browser-def .settings-form input:focus { outline: none; - border-color: #005cb9; - box-shadow: 0 0 5px rgba(0, 92, 185, 0.25); } /* Google Forms-like style for textarea and buttons */ textarea, button { font-family: 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; - font-size: 14px; + font-size: 1.15em; } -textarea { - padding: 8px; - border: 1px solid #e0e0e0; - border-radius: 4px; - box-sizing: border-box; - resize: vertical; - width: 100%; - min-height: 200px; -} textarea:focus { outline: none; - border-color: #005cb9; - box-shadow: 0 0 5px rgba(0, 92, 185, 0.25); + box-shadow: 0 0 5px rgba(117, 117, 117, 0.25); } button { - background-color: #005cb9; color: white; border: none; border-radius: 50px; @@ -158,20 +187,23 @@ button { cursor: pointer; font-weight: 500; transition: background-color 0.3s; -} - -button:hover { - background-color: #0073e6; + line-height:1.1 } .submit-button { margin: 30px 0 15px; } + +#settings-form .submit-button { + margin: 15px 0 15px; +} + .submit-button-suggestions { - margin: 10px 0 15px; + margin: 10px 0 8px; } + #temperature { - margin-bottom: 5px; + margin-bottom: 25px; } .copied-overlay { @@ -179,10 +211,9 @@ button:hover { top: 50%; left: 50%; background-color: rgba(0, 0, 0, 0.7); - color: #fff; padding: 8px 16px; border-radius: 4px; - font-size: 14px; + font-size: 1.15em; font-weight: bold; opacity: 0; animation: fade-in-out 1.5s forwards; @@ -205,19 +236,21 @@ button:hover { #suggestions-container { position: relative; + margin-bottom: 120px; + padding-right: 10px; + margin-right: -10px; } .loading-animation { display: flex; justify-content: center; align-items: center; - margin: 20px 0; + margin: 40px 0; } .dot { width: 8px; height: 8px; - background-color: #939393; border-radius: 50%; margin: 0 3px; animation: dotBounce 1.4s infinite; @@ -241,81 +274,504 @@ button:hover { } -.material-icons { - font-size: 19px; - position: absolute; - margin: 0; - padding: 0; - top: -3px; - left: 0; +.settings-wrap .material-icons { + font-size: 1.5em; + margin:0 !important; + padding:0 !important; + float:left; } .material-tooltip { width: 340px !important; text-align: left; - padding:15px; - z-index:1000; + padding: 15px; + z-index: 1000; + font-size: 1.2em; } .material-tooltip { left: 30px !important; - margin-top:-10px + margin-top: -10px } -.saving-overlay { - font-size: 16px; +.overlay { + font-size: 1.3em; + opacity: 0; + animation: fade-in-out 1.5s forwards; } -.gtp-model-type { - margin: 20px 0 25px; + +.overlay.fade-out { + opacity: 0; } +.switch-wrap { + margin: 4px 0; + padding: 5px 15px 25px 25px; +} +.switch-description { + padding: 15px 0 0; + font-size: 1.08em; + margin: 0 0 15px; +} +.share-container { + display: flex; + justify-content: space-between; + margin-top: 5px; + position: absolute; + right: -4px; + top: -23px; +} +.share-button { + background: none; + border: none; + color: white; + cursor: pointer; + font-size: 14px; + padding: 4px 8px; + text-align: center; + text-decoration: none; + border-radius: 4px; +} -/* Dark mode styles */ -.dark-mode { - background-color: #212121; - color:#ffffff; +.share-button svg { + fill: #1c9cf2; + max-width: 18px; } -.dark-mode .header { - background-color: #424242; +.share-button:hover { + background: none; } -.dark-mode textarea, -.dark-mode input[type="text"] { - color: #ffffff; - border-color: #616161; +.share-button svg:hover { + fill: #0f98cd; } -.dark-mode .suggestion { - background-color: #424242; - color: #ffffff; - border-color: #616161; +.settings-wrap { + margin-bottom: 20px } -.dark-mode .tab { - background-color: #292929; +#githubUrlContainer .helper-text { + font-size: 1.1em; + color: #989696 } -.dark-mode .tab button { - color: #ffffff; +#githubUrlContainer .prompt-wrap { + padding-top: 5px +} + +.switch label input[type=checkbox]:checked + .lever:after { + background-color: #d9d9d9; +} + +.gtp-model-type label { + display: block; +} + +#suggestions-tab .content { + position: fixed; + bottom: 0; + left: 0; + padding: 10px 15px; + box-shadow: -2px 8px 16px 4px #aeacac00; + width: 100%; + z-index: 1; +} + +#suggestions-tab .content textarea { + border: none; + overflow-y: auto; + resize: none; + max-height: 250px; } -.dark-mode .tab button:hover { - background-color: #616161; +textarea { + padding: 8px 8px 0 8px; + border-radius: 4px; + box-sizing: border-box; + width: 100%; + box-shadow: none !important; +} + +#suggestions-tab { + min-height: 40px; +} + +#suggestions-tab.tabcontent { + display: none; + flex-grow: 1; + padding: 0; /* Add padding to the content */ +} + +#submit-button { + margin-left: 5px; +} + +#suggestions-container { + height: 403px; + overflow: auto; +} + +/* Add styles for the custom context menu */ +#custom-context-menu { + display: none; + position: absolute; + border: 1px solid #4f4e4e; + border-radius: 5px; + box-shadow: 0px 5px 20px 0px rgb(0 0 0 / 30%); + z-index: 100; color: #fff; + max-width: 350px; + min-width: 350px; + opacity: 0; + transition: opacity .5s; /* adjust duration as needed */ + overflow: hidden; } -.dark-mode .tab button.active { - background-color: #212121; +#saved-custom-prompts-header { + cursor: pointer; } -.switch label input[type=checkbox]:checked+.lever { - background-color: #6d6d6d !important; +.saved-prompts-list { + padding: 10px 8px; + text-align: center; + font-size: 1.15em; + box-shadow: inset 0px -2px 0px #000; } -.switch label input[type=checkbox]:checked+.lever:after { - background-color: #d9d9d9; + +.saved-prompts-list a { + color: #fff; +} + +#custom-context-menu ul { + margin: 0; + padding: 5px; + list-style: none; + overflow: auto; + max-height: 298px; + transition: .9s; +} + +#custom-context-menu li { + padding: 6px 12px; + cursor: pointer; + font-size: 1.1em; + transition: .2s; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.tabcontent h3 { + font-size: 1.7em; + margin: 0 0 10px 0; + color: #444; + font-weight: bold; +} + +.tabcontent p { + font-size: 1.09em; + margin: 0 0 25px 0; +} + +#save-custom-context-menu-settings { + margin: -10px 0 20px 0; +} + +.prompt-wrap { + padding: 15px; + margin-bottom: 15px; +} + +.hamburger-menu { + cursor: pointer; + position: absolute; + top: 17px; + right: 17px; + width: 28px; + height: 20px; + margin-top: 3px; +} + +.hamburger-menu span { + position: absolute; + width: 100%; + height: 2px; + border-radius: 50px; + background-color: #fffefe; + transition: all 0.3s; +} + +.hamburger-menu span:first-child { + top: 0; +} + +.hamburger-menu span:nth-child(2) { + top: 50%; + transform: translateY(-50%); +} + +.hamburger-menu span:last-child { + bottom: 0; +} + +.settings-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 83%); + z-index: 11; + display: flex; + justify-content: center; + align-items: center; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; +} + +.settings-overlay.visible { + opacity: 1; + pointer-events: auto; +} + +.settings-content { + padding: 20px; + border-radius: 4px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2); +} + +.settings-menu { + position: absolute; + top: 63px; + list-style-type: none; + padding: 0; + margin: 4px 0 0; + text-align: center; /* Add this line to center the menu items horizontally */ + width: 100%; +} + +.settings-menu li { + padding: 15px 16px; + cursor: pointer; + transition: background-color 0.3s; + text-align: center; + margin-bottom: 2px; + color: #fff; +} + +.settings-menu li.active svg, .settings-menu li:hover svg { + /* fill:#0b0b0b; */ +} + +.settings-menu svg { + max-width: 25px; + display: inline-block; + fill: #fff; + margin: 5px 0 2px 0; +} + +.settings-menu div { + position: relative; + margin: 0px 0 5px; +} + +.settings-menu button { + width: 200px; + text-align: left +} + +.container input { + font-size: 1.2em !important; +} + +.github-option { + margin-top: 30px; + margin-left: 1px +} + +.github-option p { + padding-left: 8px; + margin-bottom: 15px; +} + +.suggestion.user-input { + background: #f1f1f100; +} + +.user-input .share-container { + display: none +} + +script { + display: block +} + +#customPromptsLink, .suggestionsLink { + cursor: pointer; +} + +#token-count { + color: #c2bebe; + position:relative; + top:22px; + font-size:1.1em; + float:right; + cursor: pointer; +} + +.theme-option { + width: 72px; + height: 72px; + border-radius: 50%; + margin-right: 11px; + cursor: pointer; + transition:.2s; + display: inline-flex; + align-items: center; + justify-content: center; + margin-bottom: 10px; +} + +.button-color-option { + width: 40px; + height: 40px; + border-radius: 50%; + margin-right: 5px; + cursor: pointer; + transition:.2s; + display: inline-flex; + align-items: center; + justify-content: center; + margin-bottom: 10px; +} +@media(max-width: 400px){ + .button-color-option { + width: 37px; + height: 37px; + margin-right: 2px; + } + .theme-option { + margin-right: 5px; + width:67px; + height:67px; + } +} +.theme-option{ + border: 1px solid #ffffff3d; + outline: 1px solid #aeaeae8a; + outline-offset: -8px; +} +.button-color-option{ + border: 1px solid #ffffff00; + outline: 1px solid #fffdfd36; + outline-offset: -3px; +} + + +.theme-option .material-icons{ + opacity:0; + color: #cdcccc; +} + +.button-color-option .material-icons { + opacity:0; + color: #ffffff; +} + +.theme-option.active .material-icons, +.button-color-option.active .material-icons { + opacity:1; +} +.button-color-option .material-icons { + font-size:20px; +} + +.settings-size-slider { + position: relative; + height: 25px; + margin-right: 9px; +} + +#font-size, #popup-size { + width: 100%; + height: 10px; + background: transparent; + -webkit-appearance: none; + margin: 5px 0; + border-radius: 10px !important; +} + +#font-size::-webkit-slider-runnable-track, #popup-size::-webkit-slider-runnable-track { + width: 100%; + height: 10px; + cursor: pointer; + border-radius: 10px; +} + +#font-size::-webkit-slider-thumb, #popup-size::-webkit-slider-thumb { + height: 20px; + width: 20px; + background: #f3f3f3; + box-shadow:0 0 4px #868484; + cursor: pointer; + -webkit-appearance: none; + border-radius: 50%; + transition: background .15s ease-in-out; +} + +#font-size:active::-webkit-slider-thumb { + background: #ffffff; +} + +input[type=range]+.thumb { + position: absolute; + top:-32px !important; + display: none; + opacity: 0; +} +#changelog-container { + white-space: pre-wrap; + font-family: arial, 'sans-serif'; + font-size:1.1em; + padding:25px 15px; +} +#updateNotification { + color:#ffffff; + opacity: 0; + display:block; + position:absolute; + top:19px; + right:60px; + cursor:pointer; + transition: 1s; +} +.changelog-fade-in { + opacity: 0; + transition: opacity 0.5s; + opacity: 1; +} +.container { + width: 90% !important; +} +body.settings-options, +body.settings-options .header, +body.settings-options #font-size::-webkit-slider-runnable-track, +body.settings-options #popup-size::-webkit-slider-runnable-track, +body.settings-options .switch label input[type=checkbox]:checked + .lever, +body.settings-options .material-icons{ + transition:.4s; +} + +body.settings-options .switch-wrap { + transition: background-color 0.4s linear; +} + +body.settings-options .switch-wrap .switch-description, +body.settings-options .switch-wrap label { + transition:.03s !important; } \ No newline at end of file diff --git a/layout/css/prism.min.css b/layout/css/prism.min.css new file mode 100644 index 0000000..a4f6722 --- /dev/null +++ b/layout/css/prism.min.css @@ -0,0 +1,122 @@ +/** + * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML + * Based on https://github.com/chriskempson/tomorrow-theme + * @author Rose Pritchard + */ + +code[class*="language-"], +pre[class*="language-"] { + color: #ccc; + background: none; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; + +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #2d2d2d; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.block-comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #999; +} + +.token.punctuation { + color: #ccc; +} + +.token.tag, +.token.attr-name, +.token.namespace, +.token.deleted { + color: #e2777a; +} + +.token.function-name { + color: #6196cc; +} + +.token.boolean, +.token.number, +.token.function { + color: #f08d49; +} + +.token.property, +.token.class-name, +.token.constant, +.token.symbol { + color: #f8c555; +} + +.token.selector, +.token.important, +.token.atrule, +.token.keyword, +.token.builtin { + color: #cc99cd; +} + +.token.string, +.token.char, +.token.attr-value, +.token.regex, +.token.variable { + color: #7ec699; +} + +.token.operator, +.token.entity, +.token.url { + color: #67cdcc; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + +.token.inserted { + color: green; +} \ No newline at end of file diff --git a/layout/js/materialize.min.js b/layout/js/code-view/materialize.min.js similarity index 100% rename from layout/js/materialize.min.js rename to layout/js/code-view/materialize.min.js diff --git a/layout/js/code-view/prism.min.js b/layout/js/code-view/prism.min.js new file mode 100644 index 0000000..412adab --- /dev/null +++ b/layout/js/code-view/prism.min.js @@ -0,0 +1 @@ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(o){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,e={},j={manual:o.Prism&&o.Prism.manual,disableWorkerMessageHandler:o.Prism&&o.Prism.disableWorkerMessageHandler,util:{encode:function e(t){return t instanceof C?new C(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/=i.reach);y+=b.value.length,b=b.next){var v=b.value;if(n.length>t.length)return;if(!(v instanceof C)){var F,x=1;if(m){if(!(F=L(f,y,t,p))||F.index>=t.length)break;var k=F.index,w=F.index+F[0].length,A=y;for(A+=b.value.length;A<=k;)b=b.next,A+=b.value.length;if(A-=b.value.length,y=A,b.value instanceof C)continue;for(var P=b;P!==n.tail&&(Ai.reach&&(i.reach=_);v=b.prev;S&&(v=z(n,v,S),y+=S.length),O(n,v,x);$=new C(l,d?j.tokenize($,d):$,h,$);b=z(n,v,$),E&&z(n,b,E),1i.reach&&(i.reach=_.reach))}}}}}(e,r,t,r.head,0),function(e){var t=[],n=e.head.next;for(;n!==e.tail;)t.push(n.value),n=n.next;return t}(r)},hooks:{all:{},add:function(e,t){var n=j.hooks.all;n[e]=n[e]||[],n[e].push(t)},run:function(e,t){var n=j.hooks.all[e];if(n&&n.length)for(var a,r=0;a=n[r++];)a(t)}},Token:C};function C(e,t,n,a){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length}function L(e,t,n,a){e.lastIndex=t;n=e.exec(n);return n&&a&&n[1]&&(a=n[1].length,n.index+=a,n[0]=n[0].slice(a)),n}function s(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function z(e,t,n){var a=t.next,n={value:n,prev:t,next:a};return t.next=n,a.prev=n,e.length++,n}function O(e,t,n){for(var a=t.next,r=0;r"+r.content+""},!o.document)return o.addEventListener&&(j.disableWorkerMessageHandler||o.addEventListener("message",function(e){var t=JSON.parse(e.data),n=t.language,e=t.code,t=t.immediateClose;o.postMessage(j.highlight(e,j.languages[n],n)),t&&o.close()},!1)),j;var a=j.util.currentScript();function r(){j.manual||j.highlightAll()}return a&&(j.filename=a.src,a.hasAttribute("data-manual")&&(j.manual=!0)),j.manual||("loading"===(e=document.readyState)||"interactive"===e&&a&&a.defer?document.addEventListener("DOMContentLoaded",r):window.requestAnimationFrame?window.requestAnimationFrame(r):window.setTimeout(r,16)),j}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism),Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(e,t){var n={};n["language-"+t]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[t]},n.cdata=/^$/i;n={"included-cdata":{pattern://i,inside:n}};n["language-"+t]={pattern:/[\s\S]+/,inside:Prism.languages[t]};t={};t[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,function(){return e}),"i"),lookbehind:!0,greedy:!0,inside:n},Prism.languages.insertBefore("markup","cdata",t)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(e,t){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:Prism.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml,function(e){var t=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-](?:[^;{\s]|\s+(?![\s{]))*(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+t.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:t,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;e=e.languages.markup;e&&(e.tag.addInlined("style","css"),e.tag.addAttribute("style","css"))}(Prism),Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),Prism.languages.js=Prism.languages.javascript,function(){var o,u,g,c,e;void 0!==Prism&&"undefined"!=typeof document&&(Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),o={js:"javascript",py:"python",rb:"ruby",ps1:"powershell",psm1:"powershell",sh:"bash",bat:"batch",h:"c",tex:"latex"},c="pre[data-src]:not(["+(u="data-src-status")+'="loaded"]):not(['+u+'="'+(g="loading")+'"])',Prism.hooks.add("before-highlightall",function(e){e.selector+=", "+c}),Prism.hooks.add("before-sanity-check",function(e){var r,t,n,a,s,i,l=e.element;l.matches(c)&&(e.code="",l.setAttribute(u,g),(r=l.appendChild(document.createElement("CODE"))).textContent="Loading…",n=l.getAttribute("data-src"),"none"===(e=e.language)&&(t=(/\.(\w+)$/.exec(n)||[,"none"])[1],e=o[t]||t),Prism.util.setLanguage(r,e),Prism.util.setLanguage(l,e),(t=Prism.plugins.autoloader)&&t.loadLanguages(e),n=n,a=function(e){l.setAttribute(u,"loaded");var t,n,a=function(e){if(n=/^\s*(\d+)\s*(?:(,)\s*(?:(\d+)\s*)?)?$/.exec(e||"")){var t=Number(n[1]),e=n[2],n=n[3];return e?n?[t,Number(n)]:[t,void 0]:[t,t]}}(l.getAttribute("data-range"));a&&(t=e.split(/\r\n?|\n/g),n=a[0],a=null==a[1]?t.length:a[1],n<0&&(n+=t.length),n=Math.max(0,Math.min(n-1,t.length)),a<0&&(a+=t.length),a=Math.max(0,Math.min(a,t.length)),e=t.slice(n,a).join("\n"),l.hasAttribute("data-start")||l.setAttribute("data-start",String(n+1))),r.textContent=e,Prism.highlightElement(r)},s=function(e){l.setAttribute(u,"failed"),r.textContent=e},(i=new XMLHttpRequest).open("GET",n,!0),i.onreadystatechange=function(){4==i.readyState&&(i.status<400&&i.responseText?a(i.responseText):400<=i.status?s("✖ Error "+i.status+" while fetching file: "+i.statusText):s("✖ Error: File does not exist or is empty"))},i.send(null))}),e=!(Prism.plugins.fileHighlight={highlight:function(e){for(var t,n=(e||document).querySelectorAll(c),a=0;t=n[a++];)Prism.highlightElement(t)}}),Prism.fileHighlight=function(){e||(console.warn("Prism.fileHighlight is deprecated. Use `Prism.plugins.fileHighlight.highlight` instead."),e=!0),Prism.plugins.fileHighlight.highlight.apply(this,arguments)})}(); \ No newline at end of file diff --git a/layout/js/code-view/showdown.min.js b/layout/js/code-view/showdown.min.js new file mode 100644 index 0000000..da89bff --- /dev/null +++ b/layout/js/code-view/showdown.min.js @@ -0,0 +1,3 @@ +/*! showdown v 2.1.0 - 21-04-2022 */ +!function(){function a(e){"use strict";var r={omitExtraWLInCodeBlocks:{defaultValue:!1,describe:"Omit the default extra whiteline added to code blocks",type:"boolean"},noHeaderId:{defaultValue:!1,describe:"Turn on/off generated header id",type:"boolean"},prefixHeaderId:{defaultValue:!1,describe:"Add a prefix to the generated header ids. Passing a string will prefix that string to the header id. Setting to true will add a generic 'section-' prefix",type:"string"},rawPrefixHeaderId:{defaultValue:!1,describe:'Setting this option to true will prevent showdown from modifying the prefix. This might result in malformed IDs (if, for instance, the " char is used in the prefix)',type:"boolean"},ghCompatibleHeaderId:{defaultValue:!1,describe:"Generate header ids compatible with github style (spaces are replaced with dashes, a bunch of non alphanumeric chars are removed)",type:"boolean"},rawHeaderId:{defaultValue:!1,describe:"Remove only spaces, ' and \" from generated header ids (including prefixes), replacing them with dashes (-). WARNING: This might result in malformed ids",type:"boolean"},headerLevelStart:{defaultValue:!1,describe:"The header blocks level start",type:"integer"},parseImgDimensions:{defaultValue:!1,describe:"Turn on/off image dimension parsing",type:"boolean"},simplifiedAutoLink:{defaultValue:!1,describe:"Turn on/off GFM autolink style",type:"boolean"},excludeTrailingPunctuationFromURLs:{defaultValue:!1,describe:"Excludes trailing punctuation from links generated with autoLinking",type:"boolean"},literalMidWordUnderscores:{defaultValue:!1,describe:"Parse midword underscores as literal underscores",type:"boolean"},literalMidWordAsterisks:{defaultValue:!1,describe:"Parse midword asterisks as literal asterisks",type:"boolean"},strikethrough:{defaultValue:!1,describe:"Turn on/off strikethrough support",type:"boolean"},tables:{defaultValue:!1,describe:"Turn on/off tables support",type:"boolean"},tablesHeaderId:{defaultValue:!1,describe:"Add an id to table headers",type:"boolean"},ghCodeBlocks:{defaultValue:!0,describe:"Turn on/off GFM fenced code blocks support",type:"boolean"},tasklists:{defaultValue:!1,describe:"Turn on/off GFM tasklist support",type:"boolean"},smoothLivePreview:{defaultValue:!1,describe:"Prevents weird effects in live previews due to incomplete input",type:"boolean"},smartIndentationFix:{defaultValue:!1,describe:"Tries to smartly fix indentation in es6 strings",type:"boolean"},disableForced4SpacesIndentedSublists:{defaultValue:!1,describe:"Disables the requirement of indenting nested sublists by 4 spaces",type:"boolean"},simpleLineBreaks:{defaultValue:!1,describe:"Parses simple line breaks as
(GFM Style)",type:"boolean"},requireSpaceBeforeHeadingText:{defaultValue:!1,describe:"Makes adding a space between `#` and the header text mandatory (GFM Style)",type:"boolean"},ghMentions:{defaultValue:!1,describe:"Enables github @mentions",type:"boolean"},ghMentionsLink:{defaultValue:"https://github.com/{u}",describe:"Changes the link generated by @mentions. Only applies if ghMentions option is enabled.",type:"string"},encodeEmails:{defaultValue:!0,describe:"Encode e-mail addresses through the use of Character Entities, transforming ASCII e-mail addresses into its equivalent decimal entities",type:"boolean"},openLinksInNewWindow:{defaultValue:!1,describe:"Open all links in new windows",type:"boolean"},backslashEscapesHTMLTags:{defaultValue:!1,describe:"Support for HTML Tag escaping. ex:
foo
",type:"boolean"},emoji:{defaultValue:!1,describe:"Enable emoji support. Ex: `this is a :smile: emoji`",type:"boolean"},underline:{defaultValue:!1,describe:"Enable support for underline. Syntax is double or triple underscores: `__underline word__`. With this option enabled, underscores no longer parses into `` and ``",type:"boolean"},ellipsis:{defaultValue:!0,describe:"Replaces three dots with the ellipsis unicode character",type:"boolean"},completeHTMLDocument:{defaultValue:!1,describe:"Outputs a complete html document, including ``, `` and `` tags",type:"boolean"},metadata:{defaultValue:!1,describe:"Enable support for document metadata (defined at the top of the document between `«««` and `»»»` or between `---` and `---`).",type:"boolean"},splitAdjacentBlockquotes:{defaultValue:!1,describe:"Split adjacent blockquote blocks",type:"boolean"}};if(!1===e)return JSON.parse(JSON.stringify(r));var t,a={};for(t in r)r.hasOwnProperty(t)&&(a[t]=r[t].defaultValue);return a}var x={},t={},d={},p=a(!0),h="vanilla",_={github:{omitExtraWLInCodeBlocks:!0,simplifiedAutoLink:!0,excludeTrailingPunctuationFromURLs:!0,literalMidWordUnderscores:!0,strikethrough:!0,tables:!0,tablesHeaderId:!0,ghCodeBlocks:!0,tasklists:!0,disableForced4SpacesIndentedSublists:!0,simpleLineBreaks:!0,requireSpaceBeforeHeadingText:!0,ghCompatibleHeaderId:!0,ghMentions:!0,backslashEscapesHTMLTags:!0,emoji:!0,splitAdjacentBlockquotes:!0},original:{noHeaderId:!0,ghCodeBlocks:!1},ghost:{omitExtraWLInCodeBlocks:!0,parseImgDimensions:!0,simplifiedAutoLink:!0,excludeTrailingPunctuationFromURLs:!0,literalMidWordUnderscores:!0,strikethrough:!0,tables:!0,tablesHeaderId:!0,ghCodeBlocks:!0,tasklists:!0,smoothLivePreview:!0,simpleLineBreaks:!0,requireSpaceBeforeHeadingText:!0,ghMentions:!1,encodeEmails:!0},vanilla:a(!0),allOn:function(){"use strict";var e,r=a(!0),t={};for(e in r)r.hasOwnProperty(e)&&(t[e]=!0);return t}()};function g(e,r){"use strict";var t=r?"Error in "+r+" extension->":"Error in unnamed extension",a={valid:!0,error:""};x.helper.isArray(e)||(e=[e]);for(var n=0;n").replace(/&/g,"&")};function u(e,r,t,a){"use strict";var n,s,o,i=-1<(a=a||"").indexOf("g"),l=new RegExp(r+"|"+t,"g"+a.replace(/g/g,"")),c=new RegExp(r,a.replace(/g/g,"")),u=[];do{for(n=0;p=l.exec(e);)if(c.test(p[0]))n++||(o=(s=l.lastIndex)-p[0].length);else if(n&&!--n){var d=p.index+p[0].length,p={left:{start:o,end:s},match:{start:s,end:p.index},right:{start:p.index,end:d},wholeMatch:{start:o,end:d}};if(u.push(p),!i)return u}}while(n&&(l.lastIndex=s));return u}function s(u){"use strict";return function(e,r,t,a,n,s,o){var i=t=t.replace(x.helper.regexes.asteriskDashAndColon,x.helper.escapeCharactersCallback),l="",c="",r=r||"",o=o||"";return/^www\./i.test(t)&&(t=t.replace(/^www\./i,"http://www.")),u.excludeTrailingPunctuationFromURLs&&s&&(l=s),r+'"+i+""+l+o}}function o(n,s){"use strict";return function(e,r,t){var a="mailto:";return r=r||"",t=x.subParser("unescapeSpecialChars")(t,n,s),n.encodeEmails?(a=x.helper.encodeEmailAddress(a+t),t=x.helper.encodeEmailAddress(t)):a+=t,r+''+t+""}}x.helper.matchRecursiveRegExp=function(e,r,t,a){"use strict";for(var n=u(e,r,t,a),s=[],o=0;o>=0,t=String(t||" "),e.length>r?String(e):((r-=e.length)>t.length&&(t+=t.repeat(r/t.length)),String(e)+t.slice(0,r))},"undefined"==typeof console&&(console={warn:function(e){"use strict";alert(e)},log:function(e){"use strict";alert(e)},error:function(e){"use strict";throw e}}),x.helper.regexes={asteriskDashAndColon:/([*_:~])/g},x.helper.emojis={"+1":"👍","-1":"👎",100:"💯",1234:"🔢","1st_place_medal":"🥇","2nd_place_medal":"🥈","3rd_place_medal":"🥉","8ball":"🎱",a:"🅰️",ab:"🆎",abc:"🔤",abcd:"🔡",accept:"🉑",aerial_tramway:"🚡",airplane:"✈️",alarm_clock:"⏰",alembic:"⚗️",alien:"👽",ambulance:"🚑",amphora:"🏺",anchor:"⚓️",angel:"👼",anger:"💢",angry:"😠",anguished:"😧",ant:"🐜",apple:"🍎",aquarius:"♒️",aries:"♈️",arrow_backward:"◀️",arrow_double_down:"⏬",arrow_double_up:"⏫",arrow_down:"⬇️",arrow_down_small:"🔽",arrow_forward:"▶️",arrow_heading_down:"⤵️",arrow_heading_up:"⤴️",arrow_left:"⬅️",arrow_lower_left:"↙️",arrow_lower_right:"↘️",arrow_right:"➡️",arrow_right_hook:"↪️",arrow_up:"⬆️",arrow_up_down:"↕️",arrow_up_small:"🔼",arrow_upper_left:"↖️",arrow_upper_right:"↗️",arrows_clockwise:"🔃",arrows_counterclockwise:"🔄",art:"🎨",articulated_lorry:"🚛",artificial_satellite:"🛰",astonished:"😲",athletic_shoe:"👟",atm:"🏧",atom_symbol:"⚛️",avocado:"🥑",b:"🅱️",baby:"👶",baby_bottle:"🍼",baby_chick:"🐤",baby_symbol:"🚼",back:"🔙",bacon:"🥓",badminton:"🏸",baggage_claim:"🛄",baguette_bread:"🥖",balance_scale:"⚖️",balloon:"🎈",ballot_box:"🗳",ballot_box_with_check:"☑️",bamboo:"🎍",banana:"🍌",bangbang:"‼️",bank:"🏦",bar_chart:"📊",barber:"💈",baseball:"⚾️",basketball:"🏀",basketball_man:"⛹️",basketball_woman:"⛹️‍♀️",bat:"🦇",bath:"🛀",bathtub:"🛁",battery:"🔋",beach_umbrella:"🏖",bear:"🐻",bed:"🛏",bee:"🐝",beer:"🍺",beers:"🍻",beetle:"🐞",beginner:"🔰",bell:"🔔",bellhop_bell:"🛎",bento:"🍱",biking_man:"🚴",bike:"🚲",biking_woman:"🚴‍♀️",bikini:"👙",biohazard:"☣️",bird:"🐦",birthday:"🎂",black_circle:"⚫️",black_flag:"🏴",black_heart:"🖤",black_joker:"🃏",black_large_square:"⬛️",black_medium_small_square:"◾️",black_medium_square:"◼️",black_nib:"✒️",black_small_square:"▪️",black_square_button:"🔲",blonde_man:"👱",blonde_woman:"👱‍♀️",blossom:"🌼",blowfish:"🐡",blue_book:"📘",blue_car:"🚙",blue_heart:"💙",blush:"😊",boar:"🐗",boat:"⛵️",bomb:"💣",book:"📖",bookmark:"🔖",bookmark_tabs:"📑",books:"📚",boom:"💥",boot:"👢",bouquet:"💐",bowing_man:"🙇",bow_and_arrow:"🏹",bowing_woman:"🙇‍♀️",bowling:"🎳",boxing_glove:"🥊",boy:"👦",bread:"🍞",bride_with_veil:"👰",bridge_at_night:"🌉",briefcase:"💼",broken_heart:"💔",bug:"🐛",building_construction:"🏗",bulb:"💡",bullettrain_front:"🚅",bullettrain_side:"🚄",burrito:"🌯",bus:"🚌",business_suit_levitating:"🕴",busstop:"🚏",bust_in_silhouette:"👤",busts_in_silhouette:"👥",butterfly:"🦋",cactus:"🌵",cake:"🍰",calendar:"📆",call_me_hand:"🤙",calling:"📲",camel:"🐫",camera:"📷",camera_flash:"📸",camping:"🏕",cancer:"♋️",candle:"🕯",candy:"🍬",canoe:"🛶",capital_abcd:"🔠",capricorn:"♑️",car:"🚗",card_file_box:"🗃",card_index:"📇",card_index_dividers:"🗂",carousel_horse:"🎠",carrot:"🥕",cat:"🐱",cat2:"🐈",cd:"💿",chains:"⛓",champagne:"🍾",chart:"💹",chart_with_downwards_trend:"📉",chart_with_upwards_trend:"📈",checkered_flag:"🏁",cheese:"🧀",cherries:"🍒",cherry_blossom:"🌸",chestnut:"🌰",chicken:"🐔",children_crossing:"🚸",chipmunk:"🐿",chocolate_bar:"🍫",christmas_tree:"🎄",church:"⛪️",cinema:"🎦",circus_tent:"🎪",city_sunrise:"🌇",city_sunset:"🌆",cityscape:"🏙",cl:"🆑",clamp:"🗜",clap:"👏",clapper:"🎬",classical_building:"🏛",clinking_glasses:"🥂",clipboard:"📋",clock1:"🕐",clock10:"🕙",clock1030:"🕥",clock11:"🕚",clock1130:"🕦",clock12:"🕛",clock1230:"🕧",clock130:"🕜",clock2:"🕑",clock230:"🕝",clock3:"🕒",clock330:"🕞",clock4:"🕓",clock430:"🕟",clock5:"🕔",clock530:"🕠",clock6:"🕕",clock630:"🕡",clock7:"🕖",clock730:"🕢",clock8:"🕗",clock830:"🕣",clock9:"🕘",clock930:"🕤",closed_book:"📕",closed_lock_with_key:"🔐",closed_umbrella:"🌂",cloud:"☁️",cloud_with_lightning:"🌩",cloud_with_lightning_and_rain:"⛈",cloud_with_rain:"🌧",cloud_with_snow:"🌨",clown_face:"🤡",clubs:"♣️",cocktail:"🍸",coffee:"☕️",coffin:"⚰️",cold_sweat:"😰",comet:"☄️",computer:"💻",computer_mouse:"🖱",confetti_ball:"🎊",confounded:"😖",confused:"😕",congratulations:"㊗️",construction:"🚧",construction_worker_man:"👷",construction_worker_woman:"👷‍♀️",control_knobs:"🎛",convenience_store:"🏪",cookie:"🍪",cool:"🆒",policeman:"👮",copyright:"©️",corn:"🌽",couch_and_lamp:"🛋",couple:"👫",couple_with_heart_woman_man:"💑",couple_with_heart_man_man:"👨‍❤️‍👨",couple_with_heart_woman_woman:"👩‍❤️‍👩",couplekiss_man_man:"👨‍❤️‍💋‍👨",couplekiss_man_woman:"💏",couplekiss_woman_woman:"👩‍❤️‍💋‍👩",cow:"🐮",cow2:"🐄",cowboy_hat_face:"🤠",crab:"🦀",crayon:"🖍",credit_card:"💳",crescent_moon:"🌙",cricket:"🏏",crocodile:"🐊",croissant:"🥐",crossed_fingers:"🤞",crossed_flags:"🎌",crossed_swords:"⚔️",crown:"👑",cry:"😢",crying_cat_face:"😿",crystal_ball:"🔮",cucumber:"🥒",cupid:"💘",curly_loop:"➰",currency_exchange:"💱",curry:"🍛",custard:"🍮",customs:"🛃",cyclone:"🌀",dagger:"🗡",dancer:"💃",dancing_women:"👯",dancing_men:"👯‍♂️",dango:"🍡",dark_sunglasses:"🕶",dart:"🎯",dash:"💨",date:"📅",deciduous_tree:"🌳",deer:"🦌",department_store:"🏬",derelict_house:"🏚",desert:"🏜",desert_island:"🏝",desktop_computer:"🖥",male_detective:"🕵️",diamond_shape_with_a_dot_inside:"💠",diamonds:"♦️",disappointed:"😞",disappointed_relieved:"😥",dizzy:"💫",dizzy_face:"😵",do_not_litter:"🚯",dog:"🐶",dog2:"🐕",dollar:"💵",dolls:"🎎",dolphin:"🐬",door:"🚪",doughnut:"🍩",dove:"🕊",dragon:"🐉",dragon_face:"🐲",dress:"👗",dromedary_camel:"🐪",drooling_face:"🤤",droplet:"💧",drum:"🥁",duck:"🦆",dvd:"📀","e-mail":"📧",eagle:"🦅",ear:"👂",ear_of_rice:"🌾",earth_africa:"🌍",earth_americas:"🌎",earth_asia:"🌏",egg:"🥚",eggplant:"🍆",eight_pointed_black_star:"✴️",eight_spoked_asterisk:"✳️",electric_plug:"🔌",elephant:"🐘",email:"✉️",end:"🔚",envelope_with_arrow:"📩",euro:"💶",european_castle:"🏰",european_post_office:"🏤",evergreen_tree:"🌲",exclamation:"❗️",expressionless:"😑",eye:"👁",eye_speech_bubble:"👁‍🗨",eyeglasses:"👓",eyes:"👀",face_with_head_bandage:"🤕",face_with_thermometer:"🤒",fist_oncoming:"👊",factory:"🏭",fallen_leaf:"🍂",family_man_woman_boy:"👪",family_man_boy:"👨‍👦",family_man_boy_boy:"👨‍👦‍👦",family_man_girl:"👨‍👧",family_man_girl_boy:"👨‍👧‍👦",family_man_girl_girl:"👨‍👧‍👧",family_man_man_boy:"👨‍👨‍👦",family_man_man_boy_boy:"👨‍👨‍👦‍👦",family_man_man_girl:"👨‍👨‍👧",family_man_man_girl_boy:"👨‍👨‍👧‍👦",family_man_man_girl_girl:"👨‍👨‍👧‍👧",family_man_woman_boy_boy:"👨‍👩‍👦‍👦",family_man_woman_girl:"👨‍👩‍👧",family_man_woman_girl_boy:"👨‍👩‍👧‍👦",family_man_woman_girl_girl:"👨‍👩‍👧‍👧",family_woman_boy:"👩‍👦",family_woman_boy_boy:"👩‍👦‍👦",family_woman_girl:"👩‍👧",family_woman_girl_boy:"👩‍👧‍👦",family_woman_girl_girl:"👩‍👧‍👧",family_woman_woman_boy:"👩‍👩‍👦",family_woman_woman_boy_boy:"👩‍👩‍👦‍👦",family_woman_woman_girl:"👩‍👩‍👧",family_woman_woman_girl_boy:"👩‍👩‍👧‍👦",family_woman_woman_girl_girl:"👩‍👩‍👧‍👧",fast_forward:"⏩",fax:"📠",fearful:"😨",feet:"🐾",female_detective:"🕵️‍♀️",ferris_wheel:"🎡",ferry:"⛴",field_hockey:"🏑",file_cabinet:"🗄",file_folder:"📁",film_projector:"📽",film_strip:"🎞",fire:"🔥",fire_engine:"🚒",fireworks:"🎆",first_quarter_moon:"🌓",first_quarter_moon_with_face:"🌛",fish:"🐟",fish_cake:"🍥",fishing_pole_and_fish:"🎣",fist_raised:"✊",fist_left:"🤛",fist_right:"🤜",flags:"🎏",flashlight:"🔦",fleur_de_lis:"⚜️",flight_arrival:"🛬",flight_departure:"🛫",floppy_disk:"💾",flower_playing_cards:"🎴",flushed:"😳",fog:"🌫",foggy:"🌁",football:"🏈",footprints:"👣",fork_and_knife:"🍴",fountain:"⛲️",fountain_pen:"🖋",four_leaf_clover:"🍀",fox_face:"🦊",framed_picture:"🖼",free:"🆓",fried_egg:"🍳",fried_shrimp:"🍤",fries:"🍟",frog:"🐸",frowning:"😦",frowning_face:"☹️",frowning_man:"🙍‍♂️",frowning_woman:"🙍",middle_finger:"🖕",fuelpump:"⛽️",full_moon:"🌕",full_moon_with_face:"🌝",funeral_urn:"⚱️",game_die:"🎲",gear:"⚙️",gem:"💎",gemini:"♊️",ghost:"👻",gift:"🎁",gift_heart:"💝",girl:"👧",globe_with_meridians:"🌐",goal_net:"🥅",goat:"🐐",golf:"⛳️",golfing_man:"🏌️",golfing_woman:"🏌️‍♀️",gorilla:"🦍",grapes:"🍇",green_apple:"🍏",green_book:"📗",green_heart:"💚",green_salad:"🥗",grey_exclamation:"❕",grey_question:"❔",grimacing:"😬",grin:"😁",grinning:"😀",guardsman:"💂",guardswoman:"💂‍♀️",guitar:"🎸",gun:"🔫",haircut_woman:"💇",haircut_man:"💇‍♂️",hamburger:"🍔",hammer:"🔨",hammer_and_pick:"⚒",hammer_and_wrench:"🛠",hamster:"🐹",hand:"✋",handbag:"👜",handshake:"🤝",hankey:"💩",hatched_chick:"🐥",hatching_chick:"🐣",headphones:"🎧",hear_no_evil:"🙉",heart:"❤️",heart_decoration:"💟",heart_eyes:"😍",heart_eyes_cat:"😻",heartbeat:"💓",heartpulse:"💗",hearts:"♥️",heavy_check_mark:"✔️",heavy_division_sign:"➗",heavy_dollar_sign:"💲",heavy_heart_exclamation:"❣️",heavy_minus_sign:"➖",heavy_multiplication_x:"✖️",heavy_plus_sign:"➕",helicopter:"🚁",herb:"🌿",hibiscus:"🌺",high_brightness:"🔆",high_heel:"👠",hocho:"🔪",hole:"🕳",honey_pot:"🍯",horse:"🐴",horse_racing:"🏇",hospital:"🏥",hot_pepper:"🌶",hotdog:"🌭",hotel:"🏨",hotsprings:"♨️",hourglass:"⌛️",hourglass_flowing_sand:"⏳",house:"🏠",house_with_garden:"🏡",houses:"🏘",hugs:"🤗",hushed:"😯",ice_cream:"🍨",ice_hockey:"🏒",ice_skate:"⛸",icecream:"🍦",id:"🆔",ideograph_advantage:"🉐",imp:"👿",inbox_tray:"📥",incoming_envelope:"📨",tipping_hand_woman:"💁",information_source:"ℹ️",innocent:"😇",interrobang:"⁉️",iphone:"📱",izakaya_lantern:"🏮",jack_o_lantern:"🎃",japan:"🗾",japanese_castle:"🏯",japanese_goblin:"👺",japanese_ogre:"👹",jeans:"👖",joy:"😂",joy_cat:"😹",joystick:"🕹",kaaba:"🕋",key:"🔑",keyboard:"⌨️",keycap_ten:"🔟",kick_scooter:"🛴",kimono:"👘",kiss:"💋",kissing:"😗",kissing_cat:"😽",kissing_closed_eyes:"😚",kissing_heart:"😘",kissing_smiling_eyes:"😙",kiwi_fruit:"🥝",koala:"🐨",koko:"🈁",label:"🏷",large_blue_circle:"🔵",large_blue_diamond:"🔷",large_orange_diamond:"🔶",last_quarter_moon:"🌗",last_quarter_moon_with_face:"🌜",latin_cross:"✝️",laughing:"😆",leaves:"🍃",ledger:"📒",left_luggage:"🛅",left_right_arrow:"↔️",leftwards_arrow_with_hook:"↩️",lemon:"🍋",leo:"♌️",leopard:"🐆",level_slider:"🎚",libra:"♎️",light_rail:"🚈",link:"🔗",lion:"🦁",lips:"👄",lipstick:"💄",lizard:"🦎",lock:"🔒",lock_with_ink_pen:"🔏",lollipop:"🍭",loop:"➿",loud_sound:"🔊",loudspeaker:"📢",love_hotel:"🏩",love_letter:"💌",low_brightness:"🔅",lying_face:"🤥",m:"Ⓜ️",mag:"🔍",mag_right:"🔎",mahjong:"🀄️",mailbox:"📫",mailbox_closed:"📪",mailbox_with_mail:"📬",mailbox_with_no_mail:"📭",man:"👨",man_artist:"👨‍🎨",man_astronaut:"👨‍🚀",man_cartwheeling:"🤸‍♂️",man_cook:"👨‍🍳",man_dancing:"🕺",man_facepalming:"🤦‍♂️",man_factory_worker:"👨‍🏭",man_farmer:"👨‍🌾",man_firefighter:"👨‍🚒",man_health_worker:"👨‍⚕️",man_in_tuxedo:"🤵",man_judge:"👨‍⚖️",man_juggling:"🤹‍♂️",man_mechanic:"👨‍🔧",man_office_worker:"👨‍💼",man_pilot:"👨‍✈️",man_playing_handball:"🤾‍♂️",man_playing_water_polo:"🤽‍♂️",man_scientist:"👨‍🔬",man_shrugging:"🤷‍♂️",man_singer:"👨‍🎤",man_student:"👨‍🎓",man_teacher:"👨‍🏫",man_technologist:"👨‍💻",man_with_gua_pi_mao:"👲",man_with_turban:"👳",tangerine:"🍊",mans_shoe:"👞",mantelpiece_clock:"🕰",maple_leaf:"🍁",martial_arts_uniform:"🥋",mask:"😷",massage_woman:"💆",massage_man:"💆‍♂️",meat_on_bone:"🍖",medal_military:"🎖",medal_sports:"🏅",mega:"📣",melon:"🍈",memo:"📝",men_wrestling:"🤼‍♂️",menorah:"🕎",mens:"🚹",metal:"🤘",metro:"🚇",microphone:"🎤",microscope:"🔬",milk_glass:"🥛",milky_way:"🌌",minibus:"🚐",minidisc:"💽",mobile_phone_off:"📴",money_mouth_face:"🤑",money_with_wings:"💸",moneybag:"💰",monkey:"🐒",monkey_face:"🐵",monorail:"🚝",moon:"🌔",mortar_board:"🎓",mosque:"🕌",motor_boat:"🛥",motor_scooter:"🛵",motorcycle:"🏍",motorway:"🛣",mount_fuji:"🗻",mountain:"⛰",mountain_biking_man:"🚵",mountain_biking_woman:"🚵‍♀️",mountain_cableway:"🚠",mountain_railway:"🚞",mountain_snow:"🏔",mouse:"🐭",mouse2:"🐁",movie_camera:"🎥",moyai:"🗿",mrs_claus:"🤶",muscle:"💪",mushroom:"🍄",musical_keyboard:"🎹",musical_note:"🎵",musical_score:"🎼",mute:"🔇",nail_care:"💅",name_badge:"📛",national_park:"🏞",nauseated_face:"🤢",necktie:"👔",negative_squared_cross_mark:"❎",nerd_face:"🤓",neutral_face:"😐",new:"🆕",new_moon:"🌑",new_moon_with_face:"🌚",newspaper:"📰",newspaper_roll:"🗞",next_track_button:"⏭",ng:"🆖",no_good_man:"🙅‍♂️",no_good_woman:"🙅",night_with_stars:"🌃",no_bell:"🔕",no_bicycles:"🚳",no_entry:"⛔️",no_entry_sign:"🚫",no_mobile_phones:"📵",no_mouth:"😶",no_pedestrians:"🚷",no_smoking:"🚭","non-potable_water":"🚱",nose:"👃",notebook:"📓",notebook_with_decorative_cover:"📔",notes:"🎶",nut_and_bolt:"🔩",o:"⭕️",o2:"🅾️",ocean:"🌊",octopus:"🐙",oden:"🍢",office:"🏢",oil_drum:"🛢",ok:"🆗",ok_hand:"👌",ok_man:"🙆‍♂️",ok_woman:"🙆",old_key:"🗝",older_man:"👴",older_woman:"👵",om:"🕉",on:"🔛",oncoming_automobile:"🚘",oncoming_bus:"🚍",oncoming_police_car:"🚔",oncoming_taxi:"🚖",open_file_folder:"📂",open_hands:"👐",open_mouth:"😮",open_umbrella:"☂️",ophiuchus:"⛎",orange_book:"📙",orthodox_cross:"☦️",outbox_tray:"📤",owl:"🦉",ox:"🐂",package:"📦",page_facing_up:"📄",page_with_curl:"📃",pager:"📟",paintbrush:"🖌",palm_tree:"🌴",pancakes:"🥞",panda_face:"🐼",paperclip:"📎",paperclips:"🖇",parasol_on_ground:"⛱",parking:"🅿️",part_alternation_mark:"〽️",partly_sunny:"⛅️",passenger_ship:"🛳",passport_control:"🛂",pause_button:"⏸",peace_symbol:"☮️",peach:"🍑",peanuts:"🥜",pear:"🍐",pen:"🖊",pencil2:"✏️",penguin:"🐧",pensive:"😔",performing_arts:"🎭",persevere:"😣",person_fencing:"🤺",pouting_woman:"🙎",phone:"☎️",pick:"⛏",pig:"🐷",pig2:"🐖",pig_nose:"🐽",pill:"💊",pineapple:"🍍",ping_pong:"🏓",pisces:"♓️",pizza:"🍕",place_of_worship:"🛐",plate_with_cutlery:"🍽",play_or_pause_button:"⏯",point_down:"👇",point_left:"👈",point_right:"👉",point_up:"☝️",point_up_2:"👆",police_car:"🚓",policewoman:"👮‍♀️",poodle:"🐩",popcorn:"🍿",post_office:"🏣",postal_horn:"📯",postbox:"📮",potable_water:"🚰",potato:"🥔",pouch:"👝",poultry_leg:"🍗",pound:"💷",rage:"😡",pouting_cat:"😾",pouting_man:"🙎‍♂️",pray:"🙏",prayer_beads:"📿",pregnant_woman:"🤰",previous_track_button:"⏮",prince:"🤴",princess:"👸",printer:"🖨",purple_heart:"💜",purse:"👛",pushpin:"📌",put_litter_in_its_place:"🚮",question:"❓",rabbit:"🐰",rabbit2:"🐇",racehorse:"🐎",racing_car:"🏎",radio:"📻",radio_button:"🔘",radioactive:"☢️",railway_car:"🚃",railway_track:"🛤",rainbow:"🌈",rainbow_flag:"🏳️‍🌈",raised_back_of_hand:"🤚",raised_hand_with_fingers_splayed:"🖐",raised_hands:"🙌",raising_hand_woman:"🙋",raising_hand_man:"🙋‍♂️",ram:"🐏",ramen:"🍜",rat:"🐀",record_button:"⏺",recycle:"♻️",red_circle:"🔴",registered:"®️",relaxed:"☺️",relieved:"😌",reminder_ribbon:"🎗",repeat:"🔁",repeat_one:"🔂",rescue_worker_helmet:"⛑",restroom:"🚻",revolving_hearts:"💞",rewind:"⏪",rhinoceros:"🦏",ribbon:"🎀",rice:"🍚",rice_ball:"🍙",rice_cracker:"🍘",rice_scene:"🎑",right_anger_bubble:"🗯",ring:"💍",robot:"🤖",rocket:"🚀",rofl:"🤣",roll_eyes:"🙄",roller_coaster:"🎢",rooster:"🐓",rose:"🌹",rosette:"🏵",rotating_light:"🚨",round_pushpin:"📍",rowing_man:"🚣",rowing_woman:"🚣‍♀️",rugby_football:"🏉",running_man:"🏃",running_shirt_with_sash:"🎽",running_woman:"🏃‍♀️",sa:"🈂️",sagittarius:"♐️",sake:"🍶",sandal:"👡",santa:"🎅",satellite:"📡",saxophone:"🎷",school:"🏫",school_satchel:"🎒",scissors:"✂️",scorpion:"🦂",scorpius:"♏️",scream:"😱",scream_cat:"🙀",scroll:"📜",seat:"💺",secret:"㊙️",see_no_evil:"🙈",seedling:"🌱",selfie:"🤳",shallow_pan_of_food:"🥘",shamrock:"☘️",shark:"🦈",shaved_ice:"🍧",sheep:"🐑",shell:"🐚",shield:"🛡",shinto_shrine:"⛩",ship:"🚢",shirt:"👕",shopping:"🛍",shopping_cart:"🛒",shower:"🚿",shrimp:"🦐",signal_strength:"📶",six_pointed_star:"🔯",ski:"🎿",skier:"⛷",skull:"💀",skull_and_crossbones:"☠️",sleeping:"😴",sleeping_bed:"🛌",sleepy:"😪",slightly_frowning_face:"🙁",slightly_smiling_face:"🙂",slot_machine:"🎰",small_airplane:"🛩",small_blue_diamond:"🔹",small_orange_diamond:"🔸",small_red_triangle:"🔺",small_red_triangle_down:"🔻",smile:"😄",smile_cat:"😸",smiley:"😃",smiley_cat:"😺",smiling_imp:"😈",smirk:"😏",smirk_cat:"😼",smoking:"🚬",snail:"🐌",snake:"🐍",sneezing_face:"🤧",snowboarder:"🏂",snowflake:"❄️",snowman:"⛄️",snowman_with_snow:"☃️",sob:"😭",soccer:"⚽️",soon:"🔜",sos:"🆘",sound:"🔉",space_invader:"👾",spades:"♠️",spaghetti:"🍝",sparkle:"❇️",sparkler:"🎇",sparkles:"✨",sparkling_heart:"💖",speak_no_evil:"🙊",speaker:"🔈",speaking_head:"🗣",speech_balloon:"💬",speedboat:"🚤",spider:"🕷",spider_web:"🕸",spiral_calendar:"🗓",spiral_notepad:"🗒",spoon:"🥄",squid:"🦑",stadium:"🏟",star:"⭐️",star2:"🌟",star_and_crescent:"☪️",star_of_david:"✡️",stars:"🌠",station:"🚉",statue_of_liberty:"🗽",steam_locomotive:"🚂",stew:"🍲",stop_button:"⏹",stop_sign:"🛑",stopwatch:"⏱",straight_ruler:"📏",strawberry:"🍓",stuck_out_tongue:"😛",stuck_out_tongue_closed_eyes:"😝",stuck_out_tongue_winking_eye:"😜",studio_microphone:"🎙",stuffed_flatbread:"🥙",sun_behind_large_cloud:"🌥",sun_behind_rain_cloud:"🌦",sun_behind_small_cloud:"🌤",sun_with_face:"🌞",sunflower:"🌻",sunglasses:"😎",sunny:"☀️",sunrise:"🌅",sunrise_over_mountains:"🌄",surfing_man:"🏄",surfing_woman:"🏄‍♀️",sushi:"🍣",suspension_railway:"🚟",sweat:"😓",sweat_drops:"💦",sweat_smile:"😅",sweet_potato:"🍠",swimming_man:"🏊",swimming_woman:"🏊‍♀️",symbols:"🔣",synagogue:"🕍",syringe:"💉",taco:"🌮",tada:"🎉",tanabata_tree:"🎋",taurus:"♉️",taxi:"🚕",tea:"🍵",telephone_receiver:"📞",telescope:"🔭",tennis:"🎾",tent:"⛺️",thermometer:"🌡",thinking:"🤔",thought_balloon:"💭",ticket:"🎫",tickets:"🎟",tiger:"🐯",tiger2:"🐅",timer_clock:"⏲",tipping_hand_man:"💁‍♂️",tired_face:"😫",tm:"™️",toilet:"🚽",tokyo_tower:"🗼",tomato:"🍅",tongue:"👅",top:"🔝",tophat:"🎩",tornado:"🌪",trackball:"🖲",tractor:"🚜",traffic_light:"🚥",train:"🚋",train2:"🚆",tram:"🚊",triangular_flag_on_post:"🚩",triangular_ruler:"📐",trident:"🔱",triumph:"😤",trolleybus:"🚎",trophy:"🏆",tropical_drink:"🍹",tropical_fish:"🐠",truck:"🚚",trumpet:"🎺",tulip:"🌷",tumbler_glass:"🥃",turkey:"🦃",turtle:"🐢",tv:"📺",twisted_rightwards_arrows:"🔀",two_hearts:"💕",two_men_holding_hands:"👬",two_women_holding_hands:"👭",u5272:"🈹",u5408:"🈴",u55b6:"🈺",u6307:"🈯️",u6708:"🈷️",u6709:"🈶",u6e80:"🈵",u7121:"🈚️",u7533:"🈸",u7981:"🈲",u7a7a:"🈳",umbrella:"☔️",unamused:"😒",underage:"🔞",unicorn:"🦄",unlock:"🔓",up:"🆙",upside_down_face:"🙃",v:"✌️",vertical_traffic_light:"🚦",vhs:"📼",vibration_mode:"📳",video_camera:"📹",video_game:"🎮",violin:"🎻",virgo:"♍️",volcano:"🌋",volleyball:"🏐",vs:"🆚",vulcan_salute:"🖖",walking_man:"🚶",walking_woman:"🚶‍♀️",waning_crescent_moon:"🌘",waning_gibbous_moon:"🌖",warning:"⚠️",wastebasket:"🗑",watch:"⌚️",water_buffalo:"🐃",watermelon:"🍉",wave:"👋",wavy_dash:"〰️",waxing_crescent_moon:"🌒",wc:"🚾",weary:"😩",wedding:"💒",weight_lifting_man:"🏋️",weight_lifting_woman:"🏋️‍♀️",whale:"🐳",whale2:"🐋",wheel_of_dharma:"☸️",wheelchair:"♿️",white_check_mark:"✅",white_circle:"⚪️",white_flag:"🏳️",white_flower:"💮",white_large_square:"⬜️",white_medium_small_square:"◽️",white_medium_square:"◻️",white_small_square:"▫️",white_square_button:"🔳",wilted_flower:"🥀",wind_chime:"🎐",wind_face:"🌬",wine_glass:"🍷",wink:"😉",wolf:"🐺",woman:"👩",woman_artist:"👩‍🎨",woman_astronaut:"👩‍🚀",woman_cartwheeling:"🤸‍♀️",woman_cook:"👩‍🍳",woman_facepalming:"🤦‍♀️",woman_factory_worker:"👩‍🏭",woman_farmer:"👩‍🌾",woman_firefighter:"👩‍🚒",woman_health_worker:"👩‍⚕️",woman_judge:"👩‍⚖️",woman_juggling:"🤹‍♀️",woman_mechanic:"👩‍🔧",woman_office_worker:"👩‍💼",woman_pilot:"👩‍✈️",woman_playing_handball:"🤾‍♀️",woman_playing_water_polo:"🤽‍♀️",woman_scientist:"👩‍🔬",woman_shrugging:"🤷‍♀️",woman_singer:"👩‍🎤",woman_student:"👩‍🎓",woman_teacher:"👩‍🏫",woman_technologist:"👩‍💻",woman_with_turban:"👳‍♀️",womans_clothes:"👚",womans_hat:"👒",women_wrestling:"🤼‍♀️",womens:"🚺",world_map:"🗺",worried:"😟",wrench:"🔧",writing_hand:"✍️",x:"❌",yellow_heart:"💛",yen:"💴",yin_yang:"☯️",yum:"😋",zap:"⚡️",zipper_mouth_face:"🤐",zzz:"💤",octocat:':octocat:',showdown:"S"},x.Converter=function(e){"use strict";var r,t,n={},i=[],l=[],o={},a=h,s={parsed:{},raw:"",format:""};for(r in e=e||{},p)p.hasOwnProperty(r)&&(n[r]=p[r]);if("object"!=typeof e)throw Error("Converter expects the passed parameter to be an object, but "+typeof e+" was passed instead.");for(t in e)e.hasOwnProperty(t)&&(n[t]=e[t]);function c(e,r){if(r=r||null,x.helper.isString(e)){if(r=e=x.helper.stdExtName(e),x.extensions[e]){console.warn("DEPRECATION WARNING: "+e+" is an old extension that uses a deprecated loading method.Please inform the developer that the extension should be updated!");var t=x.extensions[e],a=e;if("function"==typeof t&&(t=t(new x.Converter)),x.helper.isArray(t)||(t=[t]),!(a=g(t,a)).valid)throw Error(a.error);for(var n=0;n[ \t]+¨NBSP;<"),!r){if(!window||!window.document)throw new Error("HTMLParser is undefined. If in a webworker or nodejs environment, you need to provide a WHATWG DOM and HTML such as JSDOM");r=window.document}for(var r=r.createElement("div"),t=(r.innerHTML=e,{preList:function(e){for(var r=e.querySelectorAll("pre"),t=[],a=0;a'}else t.push(r[a].innerHTML),r[a].innerHTML="",r[a].setAttribute("prenum",a.toString());return t}(r)}),a=(!function e(r){for(var t=0;t? ?(['"].*['"])?\)$/m))a="";else if(!a){if(a="#"+(t=t||r.toLowerCase().replace(/ ?\n/g," ")),x.helper.isUndefined(l.gUrls[t]))return e;a=l.gUrls[t],x.helper.isUndefined(l.gTitles[t])||(o=l.gTitles[t])}return e='"}return e=(e=(e=(e=(e=l.converter._dispatch("anchors.before",e,i,l)).replace(/\[((?:\[[^\]]*]|[^\[\]])*)] ?(?:\n *)?\[(.*?)]()()()()/g,r)).replace(/\[((?:\[[^\]]*]|[^\[\]])*)]()[ \t]*\([ \t]?<([^>]*)>(?:[ \t]*((["'])([^"]*?)\5))?[ \t]?\)/g,r)).replace(/\[((?:\[[^\]]*]|[^\[\]])*)]()[ \t]*\([ \t]??(?:[ \t]*((["'])([^"]*?)\5))?[ \t]?\)/g,r)).replace(/\[([^\[\]]+)]()()()()()/g,r),i.ghMentions&&(e=e.replace(/(^|\s)(\\)?(@([a-z\d]+(?:[a-z\d.-]+?[a-z\d]+)*))/gim,function(e,r,t,a,n){if("\\"===t)return r+a;if(!x.helper.isString(i.ghMentionsLink))throw new Error("ghMentionsLink option must be a string");t="";return r+'"+a+""})),e=l.converter._dispatch("anchors.after",e,i,l)});var i=/([*~_]+|\b)(((https?|ftp|dict):\/\/|www\.)[^'">\s]+?\.[^'">\s]+?)()(\1)?(?=\s|$)(?!["<>])/gi,l=/([*~_]+|\b)(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+?)([.!?,()\[\]])?(\1)?(?=\s|$)(?!["<>])/gi,c=/()<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)()>()/gi,m=/(^|\s)(?:mailto:)?([A-Za-z0-9!#$%&'*+-/=?^_`{|}~.]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)(?=$|\s)/gim,f=/<()(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi;x.subParser("autoLinks",function(e,r,t){"use strict";return e=(e=(e=t.converter._dispatch("autoLinks.before",e,r,t)).replace(c,s(r))).replace(f,o(r,t)),e=t.converter._dispatch("autoLinks.after",e,r,t)}),x.subParser("simplifiedAutoLinks",function(e,r,t){"use strict";return r.simplifiedAutoLink?(e=t.converter._dispatch("simplifiedAutoLinks.before",e,r,t),e=(e=r.excludeTrailingPunctuationFromURLs?e.replace(l,s(r)):e.replace(i,s(r))).replace(m,o(r,t)),t.converter._dispatch("simplifiedAutoLinks.after",e,r,t)):e}),x.subParser("blockGamut",function(e,r,t){"use strict";return e=t.converter._dispatch("blockGamut.before",e,r,t),e=x.subParser("blockQuotes")(e,r,t),e=x.subParser("headers")(e,r,t),e=x.subParser("horizontalRule")(e,r,t),e=x.subParser("lists")(e,r,t),e=x.subParser("codeBlocks")(e,r,t),e=x.subParser("tables")(e,r,t),e=x.subParser("hashHTMLBlocks")(e,r,t),e=x.subParser("paragraphs")(e,r,t),e=t.converter._dispatch("blockGamut.after",e,r,t)}),x.subParser("blockQuotes",function(e,r,t){"use strict";e=t.converter._dispatch("blockQuotes.before",e,r,t);var a=/(^ {0,3}>[ \t]?.+\n(.+\n)*\n*)+/gm;return r.splitAdjacentBlockquotes&&(a=/^ {0,3}>[\s\S]*?(?:\n\n)/gm),e=(e+="\n\n").replace(a,function(e){return e=(e=(e=e.replace(/^[ \t]*>[ \t]?/gm,"")).replace(/¨0/g,"")).replace(/^[ \t]+$/gm,""),e=x.subParser("githubCodeBlocks")(e,r,t),e=(e=(e=x.subParser("blockGamut")(e,r,t)).replace(/(^|\n)/g,"$1 ")).replace(/(\s*
[^\r]+?<\/pre>)/gm,function(e,r){return r.replace(/^  /gm,"¨0").replace(/¨0/g,"")}),x.subParser("hashBlock")("
\n"+e+"\n
",r,t)}),e=t.converter._dispatch("blockQuotes.after",e,r,t)}),x.subParser("codeBlocks",function(e,n,s){"use strict";e=s.converter._dispatch("codeBlocks.before",e,n,s);return e=(e=(e+="¨0").replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=¨0))/g,function(e,r,t){var a="\n",r=x.subParser("outdent")(r,n,s);return r=x.subParser("encodeCode")(r,n,s),r="
"+(r=(r=(r=x.subParser("detab")(r,n,s)).replace(/^\n+/g,"")).replace(/\n+$/g,""))+(a=n.omitExtraWLInCodeBlocks?"":a)+"
",x.subParser("hashBlock")(r,n,s)+t})).replace(/¨0/,""),e=s.converter._dispatch("codeBlocks.after",e,n,s)}),x.subParser("codeSpans",function(e,n,s){"use strict";return e=(e=void 0===(e=s.converter._dispatch("codeSpans.before",e,n,s))?"":e).replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(e,r,t,a){return a=(a=a.replace(/^([ \t]*)/g,"")).replace(/[ \t]*$/g,""),a=r+""+(a=x.subParser("encodeCode")(a,n,s))+"",a=x.subParser("hashHTMLSpans")(a,n,s)}),e=s.converter._dispatch("codeSpans.after",e,n,s)}),x.subParser("completeHTMLDocument",function(e,r,t){"use strict";if(!r.completeHTMLDocument)return e;e=t.converter._dispatch("completeHTMLDocument.before",e,r,t);var a,n="html",s="\n",o="",i='\n',l="",c="";for(a in void 0!==t.metadata.parsed.doctype&&(s="\n","html"!==(n=t.metadata.parsed.doctype.toString().toLowerCase())&&"html5"!==n||(i='')),t.metadata.parsed)if(t.metadata.parsed.hasOwnProperty(a))switch(a.toLowerCase()){case"doctype":break;case"title":o=""+t.metadata.parsed.title+"\n";break;case"charset":i="html"===n||"html5"===n?'\n':'\n';break;case"language":case"lang":l=' lang="'+t.metadata.parsed[a]+'"',c+='\n';break;default:c+='\n'}return e=s+"\n\n"+o+i+c+"\n\n"+e.trim()+"\n\n",e=t.converter._dispatch("completeHTMLDocument.after",e,r,t)}),x.subParser("detab",function(e,r,t){"use strict";return e=(e=(e=(e=(e=(e=t.converter._dispatch("detab.before",e,r,t)).replace(/\t(?=\t)/g," ")).replace(/\t/g,"¨A¨B")).replace(/¨B(.+?)¨A/g,function(e,r){for(var t=r,a=4-t.length%4,n=0;n/g,">"),e=t.converter._dispatch("encodeAmpsAndAngles.after",e,r,t)}),x.subParser("encodeBackslashEscapes",function(e,r,t){"use strict";return e=(e=(e=t.converter._dispatch("encodeBackslashEscapes.before",e,r,t)).replace(/\\(\\)/g,x.helper.escapeCharactersCallback)).replace(/\\([`*_{}\[\]()>#+.!~=|:-])/g,x.helper.escapeCharactersCallback),e=t.converter._dispatch("encodeBackslashEscapes.after",e,r,t)}),x.subParser("encodeCode",function(e,r,t){"use strict";return e=(e=t.converter._dispatch("encodeCode.before",e,r,t)).replace(/&/g,"&").replace(//g,">").replace(/([*_{}\[\]\\=~-])/g,x.helper.escapeCharactersCallback),e=t.converter._dispatch("encodeCode.after",e,r,t)}),x.subParser("escapeSpecialCharsWithinTagAttributes",function(e,r,t){"use strict";return e=(e=(e=t.converter._dispatch("escapeSpecialCharsWithinTagAttributes.before",e,r,t)).replace(/<\/?[a-z\d_:-]+(?:[\s]+[\s\S]+?)?>/gi,function(e){return e.replace(/(.)<\/?code>(?=.)/g,"$1`").replace(/([\\`*_~=|])/g,x.helper.escapeCharactersCallback)})).replace(/-]|-[^>])(?:[^-]|-[^-])*)--)>/gi,function(e){return e.replace(/([\\`*_~=|])/g,x.helper.escapeCharactersCallback)}),e=t.converter._dispatch("escapeSpecialCharsWithinTagAttributes.after",e,r,t)}),x.subParser("githubCodeBlocks",function(e,s,o){"use strict";return s.ghCodeBlocks?(e=o.converter._dispatch("githubCodeBlocks.before",e,s,o),e=(e=(e+="¨0").replace(/(?:^|\n)(?: {0,3})(```+|~~~+)(?: *)([^\s`~]*)\n([\s\S]*?)\n(?: {0,3})\1/g,function(e,r,t,a){var n=s.omitExtraWLInCodeBlocks?"":"\n";return a=x.subParser("encodeCode")(a,s,o),a="
"+(a=(a=(a=x.subParser("detab")(a,s,o)).replace(/^\n+/g,"")).replace(/\n+$/g,""))+n+"
",a=x.subParser("hashBlock")(a,s,o),"\n\n¨G"+(o.ghCodeBlocks.push({text:e,codeblock:a})-1)+"G\n\n"})).replace(/¨0/,""),o.converter._dispatch("githubCodeBlocks.after",e,s,o)):e}),x.subParser("hashBlock",function(e,r,t){"use strict";return e=(e=t.converter._dispatch("hashBlock.before",e,r,t)).replace(/(^\n+|\n+$)/g,""),e="\n\n¨K"+(t.gHtmlBlocks.push(e)-1)+"K\n\n",e=t.converter._dispatch("hashBlock.after",e,r,t)}),x.subParser("hashCodeTags",function(e,n,s){"use strict";e=s.converter._dispatch("hashCodeTags.before",e,n,s);return e=x.helper.replaceRecursiveRegExp(e,function(e,r,t,a){t=t+x.subParser("encodeCode")(r,n,s)+a;return"¨C"+(s.gHtmlSpans.push(t)-1)+"C"},"]*>","","gim"),e=s.converter._dispatch("hashCodeTags.after",e,n,s)}),x.subParser("hashElement",function(e,r,t){"use strict";return function(e,r){return r=(r=(r=r.replace(/\n\n/g,"\n")).replace(/^\n/,"")).replace(/\n+$/g,""),r="\n\n¨K"+(t.gHtmlBlocks.push(r)-1)+"K\n\n"}}),x.subParser("hashHTMLBlocks",function(e,r,n){"use strict";e=n.converter._dispatch("hashHTMLBlocks.before",e,r,n);function t(e,r,t,a){return-1!==t.search(/\bmarkdown\b/)&&(e=t+n.converter.makeHtml(r)+a),"\n\n¨K"+(n.gHtmlBlocks.push(e)-1)+"K\n\n"}var a=["pre","div","h1","h2","h3","h4","h5","h6","blockquote","table","dl","ol","ul","script","noscript","form","fieldset","iframe","math","style","section","header","footer","nav","article","aside","address","audio","canvas","figure","hgroup","output","video","p"];r.backslashEscapesHTMLTags&&(e=e.replace(/\\<(\/?[^>]+?)>/g,function(e,r){return"<"+r+">"}));for(var s=0;s]*>)","im"),i="<"+a[s]+"\\b[^>]*>",l="";-1!==(c=x.helper.regexIndexOf(e,o));){var c=x.helper.splitAtIndex(e,c),u=x.helper.replaceRecursiveRegExp(c[1],t,i,l,"im");if(u===c[1])break;e=c[0].concat(u)}return e=e.replace(/(\n {0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,x.subParser("hashElement")(e,r,n)),e=(e=x.helper.replaceRecursiveRegExp(e,function(e){return"\n\n¨K"+(n.gHtmlBlocks.push(e)-1)+"K\n\n"},"^ {0,3}\x3c!--","--\x3e","gm")).replace(/(?:\n\n)( {0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,x.subParser("hashElement")(e,r,n)),e=n.converter._dispatch("hashHTMLBlocks.after",e,r,n)}),x.subParser("hashHTMLSpans",function(e,r,t){"use strict";function a(e){return"¨C"+(t.gHtmlSpans.push(e)-1)+"C"}return e=(e=(e=(e=(e=t.converter._dispatch("hashHTMLSpans.before",e,r,t)).replace(/<[^>]+?\/>/gi,a)).replace(/<([^>]+?)>[\s\S]*?<\/\1>/g,a)).replace(/<([^>]+?)\s[^>]+?>[\s\S]*?<\/\1>/g,a)).replace(/<[^>]+?>/gi,a),e=t.converter._dispatch("hashHTMLSpans.after",e,r,t)}),x.subParser("unhashHTMLSpans",function(e,r,t){"use strict";e=t.converter._dispatch("unhashHTMLSpans.before",e,r,t);for(var a=0;a]*>\\s*]*>","^ {0,3}\\s*
","gim"),e=s.converter._dispatch("hashPreCodeTags.after",e,n,s)}),x.subParser("headers",function(e,n,s){"use strict";e=s.converter._dispatch("headers.before",e,n,s);var o=isNaN(parseInt(n.headerLevelStart))?1:parseInt(n.headerLevelStart),r=n.smoothLivePreview?/^(.+)[ \t]*\n={2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n=+[ \t]*\n+/gm,t=n.smoothLivePreview?/^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n-+[ \t]*\n+/gm,r=(e=(e=e.replace(r,function(e,r){var t=x.subParser("spanGamut")(r,n,s),r=n.noHeaderId?"":' id="'+i(r)+'"',r=""+t+"";return x.subParser("hashBlock")(r,n,s)})).replace(t,function(e,r){var t=x.subParser("spanGamut")(r,n,s),r=n.noHeaderId?"":' id="'+i(r)+'"',a=o+1,r=""+t+"";return x.subParser("hashBlock")(r,n,s)}),n.requireSpaceBeforeHeadingText?/^(#{1,6})[ \t]+(.+?)[ \t]*#*\n+/gm:/^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm);function i(e){var r=e=n.customizedHeaderId&&(r=e.match(/\{([^{]+?)}\s*$/))&&r[1]?r[1]:e,e=x.helper.isString(n.prefixHeaderId)?n.prefixHeaderId:!0===n.prefixHeaderId?"section-":"";return n.rawPrefixHeaderId||(r=e+r),r=(n.ghCompatibleHeaderId?r.replace(/ /g,"-").replace(/&/g,"").replace(/¨T/g,"").replace(/¨D/g,"").replace(/[&+$,\/:;=?@"#{}|^¨~\[\]`\\*)(%.!'<>]/g,""):n.rawHeaderId?r.replace(/ /g,"-").replace(/&/g,"&").replace(/¨T/g,"¨").replace(/¨D/g,"$").replace(/["']/g,"-"):r.replace(/[^\w]/g,"")).toLowerCase(),n.rawPrefixHeaderId&&(r=e+r),s.hashLinkCounts[r]?r=r+"-"+s.hashLinkCounts[r]++:s.hashLinkCounts[r]=1,r}return e=e.replace(r,function(e,r,t){var a=t,a=(n.customizedHeaderId&&(a=t.replace(/\s?\{([^{]+?)}\s*$/,"")),x.subParser("spanGamut")(a,n,s)),t=n.noHeaderId?"":' id="'+i(t)+'"',r=o-1+r.length,t=""+a+"";return x.subParser("hashBlock")(t,n,s)}),e=s.converter._dispatch("headers.after",e,n,s)}),x.subParser("horizontalRule",function(e,r,t){"use strict";e=t.converter._dispatch("horizontalRule.before",e,r,t);var a=x.subParser("hashBlock")("
",r,t);return e=(e=(e=e.replace(/^ {0,2}( ?-){3,}[ \t]*$/gm,a)).replace(/^ {0,2}( ?\*){3,}[ \t]*$/gm,a)).replace(/^ {0,2}( ?_){3,}[ \t]*$/gm,a),e=t.converter._dispatch("horizontalRule.after",e,r,t)}),x.subParser("images",function(e,r,d){"use strict";function l(e,r,t,a,n,s,o,i){var l=d.gUrls,c=d.gTitles,u=d.gDimensions;if(t=t.toLowerCase(),i=i||"",-1? ?(['"].*['"])?\)$/m))a="";else if(""===a||null===a){if(a="#"+(t=""!==t&&null!==t?t:r.toLowerCase().replace(/ ?\n/g," ")),x.helper.isUndefined(l[t]))return e;a=l[t],x.helper.isUndefined(c[t])||(i=c[t]),x.helper.isUndefined(u[t])||(n=u[t].width,s=u[t].height)}r=r.replace(/"/g,""").replace(x.helper.regexes.asteriskDashAndColon,x.helper.escapeCharactersCallback);e=''+r+'"}return e=(e=(e=(e=(e=(e=d.converter._dispatch("images.before",e,r,d)).replace(/!\[([^\]]*?)] ?(?:\n *)?\[([\s\S]*?)]()()()()()/g,l)).replace(/!\[([^\]]*?)][ \t]*()\([ \t]??(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g,function(e,r,t,a,n,s,o,i){return l(e,r,t,a=a.replace(/\s/g,""),n,s,0,i)})).replace(/!\[([^\]]*?)][ \t]*()\([ \t]?<([^>]*)>(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(?:(["'])([^"]*?)\6))?[ \t]?\)/g,l)).replace(/!\[([^\]]*?)][ \t]*()\([ \t]??(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g,l)).replace(/!\[([^\[\]]+)]()()()()()/g,l),e=d.converter._dispatch("images.after",e,r,d)}),x.subParser("italicsAndBold",function(e,r,t){"use strict";return e=t.converter._dispatch("italicsAndBold.before",e,r,t),e=r.literalMidWordUnderscores?(e=(e=e.replace(/\b___(\S[\s\S]*?)___\b/g,function(e,r){return""+r+""})).replace(/\b__(\S[\s\S]*?)__\b/g,function(e,r){return""+r+""})).replace(/\b_(\S[\s\S]*?)_\b/g,function(e,r){return""+r+""}):(e=(e=e.replace(/___(\S[\s\S]*?)___/g,function(e,r){return/\S$/.test(r)?""+r+"":e})).replace(/__(\S[\s\S]*?)__/g,function(e,r){return/\S$/.test(r)?""+r+"":e})).replace(/_([^\s_][\s\S]*?)_/g,function(e,r){return/\S$/.test(r)?""+r+"":e}),e=r.literalMidWordAsterisks?(e=(e=e.replace(/([^*]|^)\B\*\*\*(\S[\s\S]*?)\*\*\*\B(?!\*)/g,function(e,r,t){return r+""+t+""})).replace(/([^*]|^)\B\*\*(\S[\s\S]*?)\*\*\B(?!\*)/g,function(e,r,t){return r+""+t+""})).replace(/([^*]|^)\B\*(\S[\s\S]*?)\*\B(?!\*)/g,function(e,r,t){return r+""+t+""}):(e=(e=e.replace(/\*\*\*(\S[\s\S]*?)\*\*\*/g,function(e,r){return/\S$/.test(r)?""+r+"":e})).replace(/\*\*(\S[\s\S]*?)\*\*/g,function(e,r){return/\S$/.test(r)?""+r+"":e})).replace(/\*([^\s*][\s\S]*?)\*/g,function(e,r){return/\S$/.test(r)?""+r+"":e}),e=t.converter._dispatch("italicsAndBold.after",e,r,t)}),x.subParser("lists",function(e,d,c){"use strict";function p(e,r){c.gListLevel++,e=e.replace(/\n{2,}$/,"\n");var t=/(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0| {0,3}([*+-]|\d+[.])[ \t]+))/gm,l=/\n[ \t]*\n(?!¨0)/.test(e+="¨0");return d.disableForced4SpacesIndentedSublists&&(t=/(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0|\2([*+-]|\d+[.])[ \t]+))/gm),e=(e=e.replace(t,function(e,r,t,a,n,s,o){o=o&&""!==o.trim();var n=x.subParser("outdent")(n,d,c),i="";return s&&d.tasklists&&(i=' class="task-list-item" style="list-style-type: none;"',n=n.replace(/^[ \t]*\[(x|X| )?]/m,function(){var e='"+(n=(n=r||-1\n"})).replace(/¨0/g,""),c.gListLevel--,e=r?e.replace(/\s+$/,""):e}function h(e,r){if("ol"===r){r=e.match(/^ *(\d+)\./);if(r&&"1"!==r[1])return' start="'+r[1]+'"'}return""}function n(n,s,o){var e,i=d.disableForced4SpacesIndentedSublists?/^ ?\d+\.[ \t]/gm:/^ {0,3}\d+\.[ \t]/gm,l=d.disableForced4SpacesIndentedSublists?/^ ?[*+-][ \t]/gm:/^ {0,3}[*+-][ \t]/gm,c="ul"===s?i:l,u="";return-1!==n.search(c)?function e(r){var t=r.search(c),a=h(n,s);-1!==t?(u+="\n\n<"+s+a+">\n"+p(r.slice(0,t),!!o)+"\n",c="ul"===(s="ul"===s?"ol":"ul")?i:l,e(r.slice(t))):u+="\n\n<"+s+a+">\n"+p(r,!!o)+"\n"}(n):(e=h(n,s),u="\n\n<"+s+e+">\n"+p(n,!!o)+"\n"),u}return e=c.converter._dispatch("lists.before",e,d,c),e+="¨0",e=(e=c.gListLevel?e.replace(/^(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,function(e,r,t){return n(r,-1"),i+="

",n.push(i))}for(s=n.length,o=0;o]*>\s*]*>/.test(c)&&(u=!0)}n[o]=c}return e=(e=(e=n.join("\n")).replace(/^\n+/g,"")).replace(/\n+$/g,""),t.converter._dispatch("paragraphs.after",e,r,t)}),x.subParser("runExtension",function(e,r,t,a){"use strict";return e.filter?r=e.filter(r,a.converter,t):e.regex&&((a=e.regex)instanceof RegExp||(a=new RegExp(a,"g")),r=r.replace(a,e.replace)),r}),x.subParser("spanGamut",function(e,r,t){"use strict";return e=t.converter._dispatch("spanGamut.before",e,r,t),e=x.subParser("codeSpans")(e,r,t),e=x.subParser("escapeSpecialCharsWithinTagAttributes")(e,r,t),e=x.subParser("encodeBackslashEscapes")(e,r,t),e=x.subParser("images")(e,r,t),e=x.subParser("anchors")(e,r,t),e=x.subParser("autoLinks")(e,r,t),e=x.subParser("simplifiedAutoLinks")(e,r,t),e=x.subParser("emoji")(e,r,t),e=x.subParser("underline")(e,r,t),e=x.subParser("italicsAndBold")(e,r,t),e=x.subParser("strikethrough")(e,r,t),e=x.subParser("ellipsis")(e,r,t),e=x.subParser("hashHTMLSpans")(e,r,t),e=x.subParser("encodeAmpsAndAngles")(e,r,t),r.simpleLineBreaks?/\n\n¨K/.test(e)||(e=e.replace(/\n+/g,"
\n")):e=e.replace(/ +\n/g,"
\n"),e=t.converter._dispatch("spanGamut.after",e,r,t)}),x.subParser("strikethrough",function(e,t,a){"use strict";return t.strikethrough&&(e=(e=a.converter._dispatch("strikethrough.before",e,t,a)).replace(/(?:~){2}([\s\S]+?)(?:~){2}/g,function(e,r){return r=r,""+(r=t.simplifiedAutoLink?x.subParser("simplifiedAutoLinks")(r,t,a):r)+""}),e=a.converter._dispatch("strikethrough.after",e,t,a)),e}),x.subParser("stripLinkDefinitions",function(i,l,c){"use strict";function e(e,r,t,a,n,s,o){return r=r.toLowerCase(),i.toLowerCase().split(r).length-1<2?e:(t.match(/^data:.+?\/.+?;base64,/)?c.gUrls[r]=t.replace(/\s/g,""):c.gUrls[r]=x.subParser("encodeAmpsAndAngles")(t,l,c),s?s+o:(o&&(c.gTitles[r]=o.replace(/"|'/g,""")),l.parseImgDimensions&&a&&n&&(c.gDimensions[r]={width:a,height:n}),""))}return i=(i=(i=(i+="¨0").replace(/^ {0,3}\[([^\]]+)]:[ \t]*\n?[ \t]*?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n\n|(?=¨0)|(?=\n\[))/gm,e)).replace(/^ {0,3}\[([^\]]+)]:[ \t]*\n?[ \t]*\s]+)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n+|(?=¨0))/gm,e)).replace(/¨0/,"")}),x.subParser("tables",function(e,y,P){"use strict";if(!y.tables)return e;function r(e){for(var r=e.split("\n"),t=0;t"+(n=x.subParser("spanGamut")(n,y,P))+"\n"));for(t=0;t"+x.subParser("spanGamut")(i,y,P)+"\n"));h.push(_)}for(var m=d,f=h,b="\n\n\n",w=m.length,k=0;k\n\n\n",k=0;k\n";for(var v=0;v\n"}return b+="\n
\n"}return e=(e=(e=(e=P.converter._dispatch("tables.before",e,y,P)).replace(/\\(\|)/g,x.helper.escapeCharactersCallback)).replace(/^ {0,3}\|?.+\|.+\n {0,3}\|?[ \t]*:?[ \t]*(?:[-=]){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:[-=]){2,}[\s\S]+?(?:\n\n|¨0)/gm,r)).replace(/^ {0,3}\|.+\|[ \t]*\n {0,3}\|[ \t]*:?[ \t]*(?:[-=]){2,}[ \t]*:?[ \t]*\|[ \t]*\n( {0,3}\|.+\|[ \t]*\n)*(?:\n|¨0)/gm,r),e=P.converter._dispatch("tables.after",e,y,P)}),x.subParser("underline",function(e,r,t){"use strict";return r.underline?(e=t.converter._dispatch("underline.before",e,r,t),e=(e=r.literalMidWordUnderscores?(e=e.replace(/\b___(\S[\s\S]*?)___\b/g,function(e,r){return""+r+""})).replace(/\b__(\S[\s\S]*?)__\b/g,function(e,r){return""+r+""}):(e=e.replace(/___(\S[\s\S]*?)___/g,function(e,r){return/\S$/.test(r)?""+r+"":e})).replace(/__(\S[\s\S]*?)__/g,function(e,r){return/\S$/.test(r)?""+r+"":e})).replace(/(_)/g,x.helper.escapeCharactersCallback),t.converter._dispatch("underline.after",e,r,t)):e}),x.subParser("unescapeSpecialChars",function(e,r,t){"use strict";return e=(e=t.converter._dispatch("unescapeSpecialChars.before",e,r,t)).replace(/¨E(\d+)E/g,function(e,r){r=parseInt(r);return String.fromCharCode(r)}),e=t.converter._dispatch("unescapeSpecialChars.after",e,r,t)}),x.subParser("makeMarkdown.blockquote",function(e,r){"use strict";var t="";if(e.hasChildNodes())for(var a=e.childNodes,n=a.length,s=0;s ")}),x.subParser("makeMarkdown.codeBlock",function(e,r){"use strict";var t=e.getAttribute("language"),e=e.getAttribute("precodenum");return"```"+t+"\n"+r.preList[e]+"\n```"}),x.subParser("makeMarkdown.codeSpan",function(e){"use strict";return"`"+e.innerHTML+"`"}),x.subParser("makeMarkdown.emphasis",function(e,r){"use strict";var t="";if(e.hasChildNodes()){t+="*";for(var a=e.childNodes,n=a.length,s=0;s",e.hasAttribute("width")&&e.hasAttribute("height")&&(r+=" ="+e.getAttribute("width")+"x"+e.getAttribute("height")),e.hasAttribute("title")&&(r+=' "'+e.getAttribute("title")+'"'),r+=")"),r}),x.subParser("makeMarkdown.links",function(e,r){"use strict";var t="";if(e.hasChildNodes()&&e.hasAttribute("href")){for(var a=e.childNodes,n=a.length,t="[",s=0;s"),e.hasAttribute("title")&&(t+=' "'+e.getAttribute("title")+'"'),t+=")"}return t}),x.subParser("makeMarkdown.list",function(e,r,t){"use strict";var a="";if(!e.hasChildNodes())return"";for(var n=e.childNodes,s=n.length,o=e.getAttribute("start")||1,i=0;i"+r.preList[e]+""}),x.subParser("makeMarkdown.strikethrough",function(e,r){"use strict";var t="";if(e.hasChildNodes()){t+="~~";for(var a=e.childNodes,n=a.length,s=0;str>th"),s=e.querySelectorAll("tbody>tr"),o=0;o/g,"\\$1>")).replace(/^#/gm,"\\#")).replace(/^(\s*)([-=]{3,})(\s*)$/,"$1\\$2$3")).replace(/^( {0,3}\d+)\./gm,"$1\\.")).replace(/^( {0,3})([+-])/gm,"$1\\$2")).replace(/]([\s]*)\(/g,"\\]$1\\(")).replace(/^ {0,3}\[([\S \t]*?)]:/gm,"\\[$1]:")});"function"==typeof define&&define.amd?define(function(){"use strict";return x}):"undefined"!=typeof module&&module.exports?module.exports=x:this.showdown=x}.call(this); +//# sourceMappingURL=showdown.min.js.map \ No newline at end of file diff --git a/layout/js/custom-colors/custom-colors.js b/layout/js/custom-colors/custom-colors.js new file mode 100644 index 0000000..9eb21e2 --- /dev/null +++ b/layout/js/custom-colors/custom-colors.js @@ -0,0 +1,214 @@ +class CustomColors { + constructor() { + // Initialize the darker(black) theme and button color + this.currentTheme = "darker"; + this.currentButtonColor = "blue"; + this.addBodyClass(); + } + settingsCustomColors() { + var themeOptions = document.getElementsByClassName('theme-option'); + + for (var i = 0; i < themeOptions.length; i++) { + themeOptions[i].addEventListener('click', function() { + // Remove active class from all theme options + for (var j = 0; j < themeOptions.length; j++) { + themeOptions[j].classList.remove('active'); + } + + // Add active class to selected theme option + this.classList.add('active'); + + var theme = this.id; + chrome.storage.sync.set({theme: theme}, function() { + console.log('Theme is ' + theme); + window.MyAssistant.customColors.applyTheme(theme); + }); + }); + } + + var self = this; + chrome.storage.sync.get('theme', function(data) { + var themeOption; + if (data.theme) { + self.applyTheme(data.theme); + themeOption = document.getElementById(data.theme); + } else { + self.applyTheme(self.currentTheme); + themeOption = document.getElementById(self.currentTheme); + } + + // Add active class to the selected theme option + if (themeOption) { + themeOption.classList.add('active'); + } + }); + } + + applyTheme(theme) { + var oldLink = document.getElementById('theme-style'); + var link = document.createElement('link'); + link.id = 'theme-style'; + link.rel = 'stylesheet'; + link.href = 'layout/css/custom/background/' + theme + '.css'; + if (oldLink) { + document.head.removeChild(oldLink); + } + document.head.appendChild(link); + + // Remove previous theme class if exists + let previousTheme = this.currentTheme; + if (previousTheme) { + document.body.classList.remove(previousTheme + "-mode"); + } + + // Apply new theme class + document.body.classList.add(theme + "-mode"); + this.currentTheme = theme; + } + + + settingsButtonColors() { + var buttonColorOptions = document.getElementsByClassName('button-color-option'); + + for (var i = 0; i < buttonColorOptions.length; i++) { + buttonColorOptions[i].addEventListener('click', function() { + // Remove active class from all button color options + for (var j = 0; j < buttonColorOptions.length; j++) { + buttonColorOptions[j].classList.remove('active'); + } + + // Add active class to selected button color option + this.classList.add('active'); + + var buttonColor = this.id; + chrome.storage.sync.set({buttonColor: buttonColor}, function() { + console.log('Button color is ' + buttonColor); + window.MyAssistant.customColors.applyButtonTheme(buttonColor); + }); + }); + } + + var self = this; + chrome.storage.sync.get('buttonColor', function(data) { + var buttonColorOption; + if (data.buttonColor) { + self.applyButtonTheme(data.buttonColor); + buttonColorOption = document.getElementById(data.buttonColor); + } else { + self.applyButtonTheme(self.currentButtonColor); + buttonColorOption = document.getElementById(self.currentButtonColor); + } + + // Add active class to the selected button color option + if (buttonColorOption) { + buttonColorOption.classList.add('active'); + } + }); + } + + applyButtonTheme(buttonColor) { + var oldLink = document.getElementById('button-color-style'); + var link = document.createElement('link'); + link.id = 'button-color-style'; + link.rel = 'stylesheet'; + link.href = 'layout/css/custom/color/' + buttonColor + '.css'; + if (oldLink) { + document.head.removeChild(oldLink); + } + document.head.appendChild(link); + + // Remove previous button color class if exists + let previousButtonColor = this.currentButtonColor; + if (previousButtonColor) { + document.body.classList.remove(previousButtonColor + "-color"); + } + + // Apply new button color class + document.body.classList.add(buttonColor + "-color"); + this.currentButtonColor = buttonColor; + } + + + settingsFontSize() { + var fontSizeSlider = document.getElementById('font-size'); + var fontSizeSpan = document.getElementById('font-size-span'); + + // Event listener for when the slider is being moved + fontSizeSlider.addEventListener('input', function() { + var fontSize = this.value; + fontSizeSpan.textContent = fontSize; // Update display as slider is moved + }); + + // Event listener for when the slider is released + fontSizeSlider.addEventListener('mouseup', function() { + var fontSize = this.value; + chrome.storage.sync.set({fontSize: fontSize}, function() { + console.log('Font size is ' + fontSize); + window.MyAssistant.customColors.applyFontSize(fontSize); + }); + }); + + chrome.storage.sync.get('fontSize', function(data) { + if (data.fontSize) { + window.MyAssistant.customColors.applyFontSize(data.fontSize); + fontSizeSlider.value = data.fontSize; + fontSizeSpan.textContent = data.fontSize; // Add size to font-size-span div when loading stored value + } + }); + } + + applyFontSize(fontSize) { + document.documentElement.style.setProperty('--font-size', fontSize + "%"); + } + + + + settingsPopupSize() { + var popupSizeSlider = document.getElementById('popup-size'); + var popupWidthSpan = document.getElementById('popup-width-span'); + + // Event listener for when the slider is being moved + popupSizeSlider.addEventListener('input', function() { + var popupSize = this.value; + popupWidthSpan.textContent = popupSize; // Update the display as the slider is being moved + }); + + // Event listener for when the slider is released + popupSizeSlider.addEventListener('mouseup', function() { + var popupSize = this.value; + chrome.storage.sync.set({popupSize: popupSize}, function() { + console.log('Popup size is ' + popupSize); + window.MyAssistant.customColors.applyPopupSize(popupSize); + }); + }); + + chrome.storage.sync.get('popupSize', function(data) { + if (data.popupSize) { + window.MyAssistant.customColors.applyPopupSize(data.popupSize); + popupSizeSlider.value = data.popupSize; + popupWidthSpan.textContent = data.popupSize; + } + }); + } + + applyPopupSize(popupSize) { + document.body.style.setProperty('width', popupSize + "px"); + } + + addBodyClass(apiKeyException){ + const activeTabLink = document.querySelector('.settings-menu-option.tablinks.active'); + + if (activeTabLink || apiKeyException === "active-missing-api-key-exception") { + let apiKeyExceptionCheck = apiKeyException === "active-missing-api-key-exception" ? 1000 : 0; + setTimeout(function() { + document.body.classList.add('settings-options'); + }, apiKeyExceptionCheck); // Time is in milliseconds, so 5000 ms equals 5 seconds + + + } else { + document.body.classList.remove('settings-options'); + } + } + +} +window.MyAssistant.customColors = new CustomColors(); diff --git a/layout/js/popup.js b/layout/js/popup.js deleted file mode 100644 index 78cdfa9..0000000 --- a/layout/js/popup.js +++ /dev/null @@ -1,350 +0,0 @@ -document.addEventListener("DOMContentLoaded", function () { - // Initialize Materialize tooltips - const tooltips = document.querySelectorAll(".tooltipped"); - M.Tooltip.init(tooltips); - - // Add the event listener for the dark mode toggle switch - document.getElementById("darkModeToggle").addEventListener("change", function() { - document.body.classList.toggle("dark-mode", this.checked); - }); - - - const inputText = document.getElementById("input-text"); - - inputText.addEventListener("input", function () { - chrome.storage.local.set({ savedText: this.value }, function () { - console.log("Text saved."); - }); - }); - - document.getElementById("engineToggle").addEventListener("change", function() { - chrome.storage.sync.set({ engine: this.checked ? 'gpt3' : 'gpt4' }, function() { - console.log("Engine setting saved."); - }); - }); - - chrome.storage.local.get("savedText", function (data) { - inputText.value = data.savedText || ""; - }); - - // Add event listeners for the tablinks - document.querySelectorAll(".tablinks").forEach((tab) => { - tab.addEventListener("click", function (event) { - openTab(event.target.getAttribute("data-tab")); - // Remove active class from other tablinks - document.querySelectorAll(".tablinks").forEach(t => t.classList.remove("active")); - event.target.classList.add("active"); - }); - }); - - // Open the Suggestions tab by default - openTab("suggestions-tab"); - document.querySelector('[data-tab="suggestions-tab"]').classList.add("active"); - - var inputs = document.querySelectorAll('input'); - - inputs.forEach(function(input) { - input.addEventListener('focus', function() { - var label = document.querySelector('label[for="' + input.id + '"]'); - if (label) { - label.style.color = '#1a73e8'; // Change this to the desired color - } - }); - - input.addEventListener('blur', function() { - var label = document.querySelector('label[for="' + input.id + '"]'); - if (label) { - label.style.color = ''; // Reset the color to the default - } - }); - }); - - // Add a cancel flag - let cancelRequest = false; - - const submitButton = document.getElementById("submit-button"); - if (submitButton) { - submitButton.addEventListener("click", async () => { - if (submitButton.innerText === "Get Suggestions") { - - const inputText = document.getElementById("input-text").value; - if (inputText.trim() === "") { - displaySuggestions(["Add some text above to get a response."]); - return; - } - - try { - const inputText = document.getElementById("input-text").value; - showLoadingAnimation(); - submitButton.innerText = "Cancel"; - cancelRequest = false; - const engine = document.getElementById("engineToggle").checked ? 'gpt3' : 'gpt4'; - const response = await sendMessageToBackground(inputText, { action: "getSuggestions", engine }); - if (!cancelRequest) { - const suggestions = response.suggestions || []; - displaySuggestions(suggestions); - } - } catch (error) { - console.error("Error:", error.message); - displaySuggestions([`Error: ${error.message}`]); - } finally { - hideLoadingAnimation(); - submitButton.innerText = "Get Suggestions"; - } - } else { - cancelRequest = true; - submitButton.innerText = "Cancel"; - } - }); - } else { - console.error('submit-button not found'); - } - - - - const clearButton = document.getElementById("clear-button"); - if (clearButton) { - clearButton.addEventListener("click", () => { - // Clear the suggestions - displaySuggestions([]); - - // Clear the input field - const inputText = document.getElementById("input-text"); - inputText.value = ""; - - // Clear the savedText in chrome.storage.local - chrome.storage.local.set({ savedText: "" }, function () { - console.log("Text cleared."); - }); - }); - } else { - console.error('clear-button not found'); - } -}); - -(async () => { - // Wrap the event listener assignment in a DOMContentLoaded event listener - document.addEventListener("DOMContentLoaded", () => { - const settingsForm = document.getElementById("settings-form"); - if (settingsForm) { - settingsForm.addEventListener("submit", (e) => { - e.preventDefault(); - - // Show the "Saving" overlay - const savingOverlay = showSavingOverlay(); - - // Save the settings here, for example, using the chrome.storage API - const apiKey = document.getElementById("api-key").value; - const maxTokens = document.getElementById("max-tokens").value; - const n = document.getElementById("n").value; - const stop = document.getElementById("stop").value; - const temperature = document.getElementById("temperature").value; - const darkMode = document.getElementById("darkModeToggle").checked; - - chrome.storage.sync.set({ - apiKey: apiKey, - maxTokens: maxTokens, - n: n, - stop: stop, - temperature: temperature, - darkMode: darkMode - }, () => { - console.log('Settings saved'); - - // Remove the "Saving" overlay after a short delay - setTimeout(() => { - savingOverlay.remove(); - }, 1000); - }); - }); - } else { - console.error('settings-form not found'); - } - }); - - - // Load the settings when the popup is opened, for example, using the chrome.storage API - chrome.storage.sync.get(['apiKey', 'maxTokens', 'n', 'stop', 'temperature', 'darkMode', 'engine'], (result) => { - document.getElementById("api-key").value = result.apiKey || ''; - document.getElementById("max-tokens").value = result.maxTokens || ''; - document.getElementById("n").value = result.n || ''; - document.getElementById("stop").value = result.stop || ''; - document.getElementById("temperature").value = result.temperature || ''; - document.getElementById("darkModeToggle").checked = result.darkMode || false; - document.body.classList.toggle("dark-mode", result.darkMode); - document.getElementById("engineToggle").checked = result.engine === 'gpt3'; - }); -})(); - -chrome.storage.local.get("savedSuggestions", function (data) { - const suggestions = data.savedSuggestions || []; - displaySuggestions(suggestions); -}); - - -// Add this function to copy text to the clipboard -function copyToClipboard(text) { - const el = document.createElement('textarea'); - el.value = text; - el.setAttribute('readonly', ''); - el.style.position = 'absolute'; - el.style.left = '-9999px'; - document.body.appendChild(el); - el.select(); - document.execCommand('copy'); - document.body.removeChild(el); -} - -function showCopiedOverlay(target) { - const overlay = document.createElement('div'); - overlay.classList.add('copied-overlay'); - overlay.textContent = 'Copied'; - - // Set position and size of the overlay - const suggestionRect = target.getBoundingClientRect(); - - const containerRect = target.parentElement.getBoundingClientRect(); - - overlay.style.position = 'absolute'; - overlay.style.top = (suggestionRect.top - containerRect.top) + 'px'; - overlay.style.left = (suggestionRect.left - containerRect.left) + 'px'; - overlay.style.width = suggestionRect.width + 'px'; - overlay.style.height = suggestionRect.height + 'px'; - overlay.style.display = 'flex'; - overlay.style.justifyContent = 'center'; - overlay.style.alignItems = 'center'; - - document.getElementById("suggestions-container").appendChild(overlay); - setTimeout(() => { - document.getElementById("suggestions-container").removeChild(overlay); - }, 1500); -} - -function openTab(tabName) { - var i, tabcontent, tablinks; - tabcontent = document.getElementsByClassName("tabcontent"); - for (i = 0; i < tabcontent.length; i++) { - tabcontent[i].style.display = "none"; - } - tablinks = document.getElementsByClassName("tablinks"); - for (i = 0; i < tablinks.length; i++) { - tablinks[i].className = tablinks[i].className.replace(" active", ""); - } - document.getElementById(tabName).style.display = "block"; -} - -document.querySelectorAll(".tablinks").forEach((tab) => { - tab.addEventListener("click", function (event) { - openTab(event.target.getAttribute("data-tab")); - }); -}); - -function sendMessageToBackground(text, message, cancelSignal) { - return new Promise((resolve, reject) => { - const listener = function (response) { - if (chrome.runtime.lastError) { - reject(chrome.runtime.lastError); - } else if (response.error) { - reject(new Error(response.error)); - } else { - resolve(response); - } - }; - - chrome.runtime.sendMessage({ ...message, text }, listener); - - if (cancelSignal) { - cancelSignal.addEventListener('cancel', () => { - chrome.runtime.onMessage.removeListener(listener); - reject(new Error('Request canceled')); - }); - } - }); -} - - -function createSuggestionElement(text) { - const suggestion = document.createElement("div"); - suggestion.classList.add("suggestion"); - suggestion.textContent = text; - - // Add an event listener to the suggestion element - suggestion.addEventListener('click', (e) => { - copyToClipboard(text); - showCopiedOverlay(e.target); - }); - - return suggestion; -} - -function displaySuggestions(suggestions) { - const suggestionsContainer = document.getElementById("suggestions-container"); - suggestionsContainer.innerHTML = ""; - - for (const suggestion of suggestions) { - const suggestionElement = createSuggestionElement(suggestion); - suggestionsContainer.appendChild(suggestionElement); - } - - // Save the suggestions to local storage - chrome.storage.local.set({ savedSuggestions: suggestions }, function () { - console.log("Suggestions saved."); - }); - - // Show or hide the "Clear" button - const clearButton = document.getElementById("clear-button"); - if (clearButton) { - if (suggestions.length > 0) { - clearButton.style.display = "inline-block"; - } else { - clearButton.style.display = "none"; - } - } -} - - -function showLoadingAnimation() { - const suggestionsContainer = document.getElementById("suggestions-container"); - suggestionsContainer.innerHTML = ""; // Clear the suggestions container - - const loadingAnimation = document.createElement("div"); - loadingAnimation.classList.add("loading-animation"); - - for (let i = 0; i < 3; i++) { - const dot = document.createElement("div"); - dot.classList.add("dot"); - loadingAnimation.appendChild(dot); - } - - suggestionsContainer.appendChild(loadingAnimation); -} - -function hideLoadingAnimation() { - const loadingAnimation = document.querySelector(".loading-animation"); - if (loadingAnimation) { - document.getElementById("suggestions-container").removeChild(loadingAnimation); - } -} - -function showSavingOverlay() { - const settingsContent = document.getElementById("settings-tab"); - const savingOverlay = document.createElement("div"); - savingOverlay.classList.add("saving-overlay"); - savingOverlay.textContent = "Saving..."; - - savingOverlay.style.position = "absolute"; - savingOverlay.style.top = "0"; - savingOverlay.style.left = "0"; - savingOverlay.style.width = "100%"; - savingOverlay.style.height = "100%"; - savingOverlay.style.display = "flex"; - savingOverlay.style.justifyContent = "center"; - savingOverlay.style.alignItems = "center"; - savingOverlay.style.backgroundColor = "rgba(0, 0, 0, 0.5)"; - savingOverlay.style.color = "white"; - savingOverlay.style.zIndex = "10"; - - settingsContent.appendChild(savingOverlay); - - return savingOverlay; -} \ No newline at end of file diff --git a/layout/js/popup/popup.js b/layout/js/popup/popup.js new file mode 100644 index 0000000..daedd92 --- /dev/null +++ b/layout/js/popup/popup.js @@ -0,0 +1,549 @@ +class Popup { + constructor() { + this.handleDOMContentLoaded(); + this.settings(); + this.documentTabLinks(); + this.registerDocumentEvents(); + } + + handleDOMContentLoaded() { + + document.addEventListener("DOMContentLoaded", () => { + + window.MyAssistant.updates.checkForUpdates(); + window.MyAssistant.tokenCount.refreshTokenCount(); + window.MyAssistant.promptsMenu.updateCustomContextMenuOptions(); + + document.getElementById('shareButtonsToggle').addEventListener('change', (event) => { + chrome.storage.sync.set({ showShareButtons: event.target.checked }); + }); + + chrome.storage.sync.get('showShareButtons', (data) => { + document.getElementById('shareButtonsToggle').checked = data.showShareButtons; + }); + + document.getElementById("useGithubToggle").addEventListener("change", function () { + const useGithub = this.checked; + chrome.storage.sync.set({ useGithub }, function () { + console.log("Use GitHub setting saved."); + }); + + // Show or hide the GitHub URL input field based on the toggle state + // const githubUrlContainer = document.getElementById("githubUrlContainer"); + const customPromptContainer = document.getElementById("customPromptContainer"); + if (useGithub) { + // githubUrlContainer.style.display = "block"; + customPromptContainer.style.display = "none"; + } else { + // githubUrlContainer.style.display = "none"; + customPromptContainer.style.display = "block"; + } + }); + + document.getElementById("githubUrl").addEventListener("input", function () { + const githubUrl = this.value; + chrome.storage.sync.set({ githubUrl }, function () { + console.log("GitHub URL saved."); + }); + }); + + // Load the saved settings when the popup is opened + chrome.storage.sync.get(["useGithub", "githubUrl"], function (result) { + const useGithubToggle = document.getElementById("useGithubToggle"); + const githubUrl = document.getElementById("githubUrl"); + const githubUrlContainer = document.getElementById("githubUrlContainer"); + const customPromptContainer = document.getElementById("customPromptContainer"); + + useGithubToggle.checked = result.useGithub || false; + githubUrl.value = result.githubUrl || ""; + + if (useGithubToggle.checked) { + // githubUrlContainer.style.display = "block"; + customPromptContainer.style.display = "none"; + } else { + // githubUrlContainer.style.display = "none"; + customPromptContainer.style.display = "block"; + } + }); + + // Get the hamburger menu and settings overlay elements + const hamburgerMenu = document.querySelector(".hamburger-menu"); + const settingsOverlay = document.querySelector(".settings-overlay"); + const settingsMenuItems = document.querySelectorAll(".settings-menu li"); + + // Add event listener for the hamburger menu click + hamburgerMenu.addEventListener("click", () => { + if (settingsOverlay.classList.contains("visible")) { + settingsOverlay.classList.remove("visible"); + } else { + settingsOverlay.classList.add("visible"); + } + }); + + // Add event listener for the menu items click + settingsMenuItems.forEach((menuItem) => { + menuItem.addEventListener("click", () => { + settingsOverlay.classList.remove("visible"); + }); + }); + + document.body.addEventListener("contextmenu", function (event) { + if (event.target.closest('#suggestions-container') || event.target.closest('#custom-context-menu-directions')) { + event.preventDefault(); + + showCustomContextMenu(); + } + }); + + // Hide the custom context menu when clicking outside of it + document.addEventListener('click', (e) => { + const customContextMenu = document.getElementById('custom-context-menu'); + if (e.target.closest('#custom-context-menu') === null) { + customContextMenu.style.display = 'none'; + } + }); + + // load the custom context menu options to the menu. + chrome.storage.sync.get("customContextMenuPrompts", (result) => { + const prompts = result.customContextMenuPrompts || []; + const customContextMenuList = document.querySelector("#custom-context-menu > ul"); + customContextMenuList.innerHTML = ""; + + prompts.forEach((prompt, index) => { + const listItem = document.createElement("li"); + listItem.id = `option-${index}`; + listItem.textContent = prompt.description; + listItem.setAttribute("value", prompt.value); + customContextMenuList.appendChild(listItem); + }); + }); + + + window.MyAssistant.promptsMenu.promptsSettingsForm(); + window.MyAssistant.promptsMenu.promptsGetMenuPrompts(); + + + // Define the "adjustTextarea" function to resize the textarea + const textarea = document.querySelector('#input-text'); + const submitBtn = document.querySelector('#submit-button'); + + textarea.addEventListener('input', adjustTextarea); + submitBtn.addEventListener('click', resetTextareaHeight); + textarea.addEventListener('focus', adjustTextarea); // Add this line to resize the textarea on focus + + function adjustTextarea() { + textarea.style.height = 'auto'; + textarea.style.height = `${textarea.scrollHeight}px`; + } + + document.addEventListener('click', function(event) { + if (event.target !== submitBtn && event.target !== textarea) { + resetTextareaHeight(); + } + }); + + function resetTextareaHeight() { + textarea.style.height = 'auto'; + } + + // Initialize Materialize tooltips + const tooltips = document.querySelectorAll(".tooltipped"); + M.Tooltip.init(tooltips); + + // Function to show the custom context menu + function showCustomContextMenu() { + const customContextMenu = document.getElementById("custom-context-menu"); + customContextMenu.style.display = "block"; + customContextMenu.style.position = "fixed"; + customContextMenu.style.opacity = '0'; // initially hide the menu + + const bodyWidth = document.body.clientWidth; + const bodyHeight = document.body.clientHeight; + const menuWidth = customContextMenu.offsetWidth; + const menuHeight = customContextMenu.offsetHeight; + + // Define the vertical position from the top + const verticalPosition = 100; // Change this value to adjust the position + + customContextMenu.style.left = `${(bodyWidth - menuWidth) / 2}px`; + customContextMenu.style.top = `${verticalPosition}px`; + + // delay and fade in + setTimeout(() => { + customContextMenu.style.opacity = '1'; + }, 100); // delay of 100ms, adjust as needed + } + + window.MyAssistant.customColors.settingsFontSize(); + window.MyAssistant.customColors.settingsPopupSize(); + window.MyAssistant.customColors.settingsCustomColors(); + window.MyAssistant.customColors.settingsButtonColors(); + + const inputText = document.getElementById("input-text"); + + inputText.addEventListener("input", function () { + chrome.storage.local.set({ savedText: this.value }, function () { + + console.log("Text saved."); + }); + }); + + document.getElementById("engineToggle").addEventListener("change", function() { + chrome.storage.sync.set({ engine: this.checked ? 'gpt4' : 'gpt3' }, function() { + console.log("Engine setting saved."); + }); + }); + + // This gets the saved text that might be in the input should the user close the popup and reopen. + chrome.storage.local.get("savedText", function (data) { + inputText.value = data.savedText || ""; + + // Check if there are any suggestions in the suggestions-container + const suggestionsContainer = document.getElementById("suggestions-container"); + const hasSuggestions = suggestionsContainer.childElementCount > 0; + + // Check if the input-text field is empty. This only works for text that was pasted into the textarea, + // and the popup is closed and reponed. It does not work for pre-selected text on the page that gets added to textarea, + // that is fixed in getSelectedTextFromActiveTab function. + const inputTextEmpty = !inputText.value || inputText.value.trim() === ""; + + if (!hasSuggestions && inputTextEmpty) { + showCustomContextMenu(); + } else { + const customContextMenu = document.getElementById("custom-context-menu"); + customContextMenu.style.display = "none"; + } + }); + + // Add event listeners for the tablinks + document.querySelectorAll(".settings-menu .tablinks").forEach((tab) => { + tab.addEventListener("click", (event) => { + this.openTab(event.target.closest("li").getAttribute("data-tab")); + // Remove active class from other tablinks + document.querySelectorAll(".settings-menu .tablinks").forEach(t => t.classList.remove("active")); + event.target.closest("li").classList.add("active"); + window.MyAssistant.customColors.addBodyClass(); + }); + }); + + + var inputs = document.querySelectorAll('input'); + + inputs.forEach(function(input) { + input.addEventListener('focus', function() { + var label = document.querySelector('label[for="' + input.id + '"]'); + if (label) { + label.style.color = '#1a73e8'; // Change this to the desired color + } + }); + + input.addEventListener('blur', function() { + var label = document.querySelector('label[for="' + input.id + '"]'); + if (label) { + label.style.color = ''; // Reset the color to the default + } + }); + }); + + // Add a cancel flag + let cancelRequest = false; + + // Start Suggestions and adding them to storage using my sendMessageToBackground function. + const submitButton = document.getElementById("submit-button"); + if (submitButton) { + submitButton.addEventListener("click", async () => { + if (submitButton.innerText === "Submit") { + const inputText = document.getElementById("input-text").value; + if (inputText.trim() === "") { + window.MyAssistant.loadingSuggestions.showOverlay("Type text below..."); + return; + } + try { + const inputTextObj = { text: inputText, isUserInput: true }; + window.MyAssistant.loadingSuggestions.displaySuggestions(inputTextObj, []); + + const loadingAnimation = window.MyAssistant.loadingSuggestions.showLoadingAnimation(); + window.MyAssistant.loadingSuggestions.scrollToLoadingAnimation(loadingAnimation); + + submitButton.innerText = "Cancel"; + cancelRequest = false; + const engine = document.getElementById("engineToggle").checked ? 'gpt4' : 'gpt3'; + const response = await this.sendMessageToBackground(inputText, { action: "getSuggestions", engine }); + + if (!cancelRequest) { + // If there is no error message, display the suggestions + const suggestions = response.suggestions.map(suggestion => ({ text: suggestion, isUserInput: false })); + window.MyAssistant.loadingSuggestions.displaySuggestions(null, suggestions); + } + } catch (error) { + console.error("Error:", error.message); + const errorMessage = { text: `Error: ${error.message}`, isUserInput: false }; + window.MyAssistant.loadingSuggestions.displaySuggestions(null, [errorMessage]); + + } finally { + window.MyAssistant.loadingSuggestions.hideLoadingAnimation(); + submitButton.innerText = "Submit"; + document.getElementById("input-text").value = ""; + } + } else { + cancelRequest = true; + submitButton.innerText = "Cancel"; + } + }); + } else { + console.error('submit-button not found'); + } + + + // Clear Suggestions from container and storage + const clearButton = document.getElementById("clear-button"); + if (clearButton) { + clearButton.addEventListener("click", () => { + // Clear the suggestions + const suggestionsContainer = document.getElementById("suggestions-container"); + suggestionsContainer.innerHTML = ""; // Clear the suggestions container + + // Also clear the saved suggestions in local storage + chrome.storage.local.remove('savedSuggestions', function() { + console.log('Saved suggestions cleared.'); + }); + + // Also clear the total token count in local storage + chrome.storage.local.remove('totalTokenCount', function() { + console.log('Total token count cleared.'); + }); + + // Clear conversation and reset token count + chrome.runtime.sendMessage({ action: 'clearConversationAndTokenCount' }, function(response) { + console.log(response.message); + window.MyAssistant.tokenCount.refreshTokenCount(); // refresh the token count after it's cleared + }); + + }); + } + + + + // Scroll to the bottom of the suggestionsContainer + setTimeout(() => { + const suggestionsContainer = document.getElementById("suggestions-container"); + suggestionsContainer.scrollTop = suggestionsContainer.scrollHeight; + }, 0); + + + + // Get the active tab + // Additional Script selected text in a tab and copy/paste it back. + chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { + const activeTab = tabs[0]; + + // Execute the contentScript.js file in the active tab + chrome.scripting.executeScript( + { + target: { tabId: activeTab.id }, + files: ["layout/js/selected-text/contentScript.js"] + }, + function() { + window.MyAssistant.selectedText.getSelectedTextFromActiveTab(); + } + ); + }); + + // Add a message listener to handle the "textCopied" message from the content script + chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.action === "textCopied") { + adjustTextarea(); // Adjust the textarea height when the "textCopied" message is received + } + }); + + + + }); // End addEventListener("DOMContentLoaded") + + } + + + async settings() { + // Wrap the event listener assignment in a DOMContentLoaded event listener + document.addEventListener("DOMContentLoaded", () => { + const settingsForm = document.getElementById("settings-form"); + if (settingsForm) { + settingsForm.addEventListener("submit", (e) => { + e.preventDefault(); + + // Save the settings here, for example, using the chrome.storage API + const apiKey = document.getElementById("api-key").value; + const maxTokens = document.getElementById("max-tokens").value; + const n = document.getElementById("n").value; + const stop = document.getElementById("stop").value; + const temperature = document.getElementById("temperature").value; + + chrome.storage.sync.set({ + apiKey: apiKey, + maxTokens: maxTokens, + n: n, + stop: stop, + temperature: temperature, + }, () => { + console.log('Settings saved'); + window.MyAssistant.loadingSuggestions.showOverlay("Saving..."); + }); + }); + } else { + console.error('settings-form not found'); + } + }); + + // Load the settings when the popup is opened, for example, using the chrome.storage API + chrome.storage.sync.get(['apiKey', 'maxTokens', 'n', 'stop', 'temperature', 'engine'], (result) => { + document.getElementById("api-key").value = result.apiKey || ''; + document.getElementById("max-tokens").value = result.maxTokens || ''; + document.getElementById("n").value = result.n || ''; + document.getElementById("stop").value = result.stop || ''; + document.getElementById("temperature").value = result.temperature || ''; + document.getElementById("engineToggle").checked = result.engine === 'gpt4'; + + // Add your tab selection logic here + // Remove active class from all tab links + const tablinks = document.querySelectorAll(".tablinks"); + tablinks.forEach((tab) => { + tab.classList.remove("active"); + }); + + if (!result.apiKey) { + // If api-key is blank, open settings-tab + this.openTab("settings-tab"); + document.querySelector('[data-tab="settings-tab"]').classList.add("active"); + window.MyAssistant.customColors.addBodyClass("active-missing-api-key-exception"); + } else { + // If api-key has some value, open suggestions-tab + this.openTab("suggestions-tab"); + document.querySelector('[data-tab="suggestions-tab"]').classList.add("active"); + } + }); + } + + + // Copy text to the clipboard + copyToClipboard(text) { + const el = document.createElement('textarea'); + el.value = text; + el.setAttribute('readonly', ''); + el.style.position = 'absolute'; + el.style.left = '-9999px'; + document.body.appendChild(el); + el.select(); + document.execCommand('copy'); + document.body.removeChild(el); + } + + + openTab(tabName) { + var i, tabcontent, tablinks; + tabcontent = document.getElementsByClassName("tabcontent"); + for (i = 0; i < tabcontent.length; i++) { + tabcontent[i].style.display = "none"; + } + tablinks = document.getElementsByClassName("tablinks"); + for (i = 0; i < tablinks.length; i++) { + tablinks[i].className = tablinks[i].className.replace(" active", ""); + } + if(tabName){ + document.getElementById(tabName).style.display = "block"; + } + } + + + documentTabLinks(){ + document.querySelectorAll(".tablinks").forEach((tab) => { + tab.addEventListener("click", (event) => { + this.openTab(event.target.getAttribute("data-tab")); + }); + }); + } + + + registerDocumentEvents() { + window.addEventListener('DOMContentLoaded', (event) => { + + const updateNotification = document.getElementsByClassName('clickUpdateNotification'); + const customPromptsLink = document.getElementById('customPromptsLink'); + const suggestionsLink = document.getElementsByClassName('suggestionsLink'); + const settingsLink = document.getElementsByClassName('settingsLink'); + + if (updateNotification && updateNotification.length > 0) { + for (let i = 0; i < updateNotification.length; i++) { + updateNotification[i].addEventListener('click', () => { + this.openTab("changelog-tab"); + document.querySelector('[data-tab="changelog-tab"]').classList.add("active"); + window.MyAssistant.updates.setupNotificationClickListener(); + }); + } + } + + if (customPromptsLink) { + customPromptsLink.addEventListener('click', () => { + this.openTab("custom-context-menu-settings-tab"); + document.querySelector('[data-tab="custom-context-menu-settings-tab"]').classList.add("active"); + window.MyAssistant.customColors.addBodyClass(); + }); + } + + if (suggestionsLink && suggestionsLink.length > 0) { + for (let i = 0; i < suggestionsLink.length; i++) { + suggestionsLink[i].addEventListener('click', () => { + this.openTab("suggestions-tab"); + document.querySelector('[data-tab="suggestions-tab"]').classList.add("active"); + window.MyAssistant.customColors.addBodyClass(); + }); + } + } + + if (settingsLink && settingsLink.length > 0) { + for (let i = 0; i < settingsLink.length; i++) { + settingsLink[i].addEventListener('click', () => { + this.openTab("settings-tab"); + document.querySelector('[data-tab="settings-tab"]').classList.add("active"); + window.MyAssistant.customColors.addBodyClass(); + }); + } + } + + const savedPromptsHeader = document.getElementById('saved-custom-prompts-header'); + const customContextMenuOption = document.querySelector('.custom-context-menu-option'); + + if (savedPromptsHeader && customContextMenuOption) { + savedPromptsHeader.addEventListener('click', () => { + customContextMenuOption.click(); + }); + } + + }); + } + + + sendMessageToBackground(text, message, cancelSignal) { + return new Promise((resolve, reject) => { + const listener = function (response) { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError); + } else if (response.error) { + reject(new Error(response.error)); + } else { + resolve(response); + } + }; + + chrome.runtime.sendMessage({ ...message, text }, listener); + + if (cancelSignal) { + cancelSignal.addEventListener('cancel', () => { + chrome.runtime.onMessage.removeListener(listener); + reject(new Error('Request canceled')); + }); + } + }); + } + +} +window.MyAssistant.popup = new Popup(); \ No newline at end of file diff --git a/layout/js/prompts/prompts-menu.js b/layout/js/prompts/prompts-menu.js new file mode 100644 index 0000000..0284aa9 --- /dev/null +++ b/layout/js/prompts/prompts-menu.js @@ -0,0 +1,218 @@ +class PromptsMenu { + constructor() { + this.customContextMenuInitialization(); + this.updateCustomContextMenuOptions(); + } + + customContextMenuInitialization() { + // This is for the custom context menu items. + // Wrap the entire code block with an IIFE to avoid conflicts and isolate the scope + (function () { + // Declare the customContextMenu variable and assign the element reference + const customContextMenu = document.querySelector('#custom-context-menu'); + + const onClickHandler = (event) => { + const target = event.target; + + if (target.tagName.toLowerCase() === 'li') { + event.stopPropagation(); // Prevent event bubbling + + // Replace the "STOP:" text with the selected prompt + const selectedPrompt = target.getAttribute("value"); + const textarea = document.getElementById('input-text'); + const stopIndex = textarea.value.indexOf('STOP:'); + + if (stopIndex !== -1) { + // Replace "STOP:" with the selected prompt + textarea.setRangeText(selectedPrompt, stopIndex, stopIndex + 5, 'select'); + } else { + // Insert the selected prompt after a line break + const insertionIndex = textarea.value.length; + textarea.setRangeText( selectedPrompt, insertionIndex, insertionIndex, 'select'); + } + document.getElementById('submit-button').click(); + } + }; + + if (customContextMenu) { + customContextMenu.removeEventListener('click', onClickHandler); + customContextMenu.addEventListener('click', onClickHandler); + } + })(); + } + + convertRepoUrlToApiUrl(repoUrl) { + // Remove trailing slash if present + if (repoUrl.endsWith('/')) { + repoUrl = repoUrl.slice(0, -1); + } + + // Extract the username and repository name from the URL + const repoPath = new URL(repoUrl).pathname; + const [username, repoName] = repoPath.split('/').filter(Boolean); + + // Construct and return the GitHub API URL for the README.md file + return `https://api.github.com/repos/${username}/${repoName}/contents/README.md`; + } + + async updateCustomContextMenuOptions() { + chrome.storage.sync.get(["useGithub", "githubUrl"], async (result) => { + const useGithub = result.useGithub || false; + const githubUrl = result.githubUrl || ""; + let prompts = []; + + const customContextMenuList = document.querySelector("#custom-context-menu > ul"); + const customContextMenu = document.querySelector("#custom-context-menu"); + customContextMenu.style.opacity = '0'; // hide the list initially + + if (useGithub && githubUrl) { + const apiUrl = this.convertRepoUrlToApiUrl(githubUrl); + try { + const readmeContent = await this.fetchReadmeFromRepo(apiUrl); + prompts = this.parseReadmeContent(readmeContent); + } catch (error) { + console.error("Error fetching prompts from the repo:", error.message); + prompts = await this.loadPromptsFromChromeStorage(); + } + } else { + prompts = await this.loadPromptsFromChromeStorage(); + } + + // Filter out empty prompts + const promptsCheck = prompts.filter(prompt => prompt.description !== "" && prompt.value !== ""); + + // If there are no prompts, return from the function early + if ( promptsCheck.length === 0) { + customContextMenu.style.display = "none"; + return; + } + + const savedPromptsList = document.querySelector(".saved-prompts-list"); + + // Check if there's already an a element in savedPromptsList, if not create one. + let linkElement = savedPromptsList.querySelector("a"); + if (!linkElement) { + linkElement = document.createElement("a"); + savedPromptsList.appendChild(linkElement); + } + + // Set properties of the link element + linkElement.textContent = "Saved Prompts"; + linkElement.style.display = useGithub ? "block" : "none"; // display only when useGithub is true + if (useGithub) { + linkElement.href = githubUrl; + + const savedCustomPromptsHeader = document.getElementById("saved-custom-prompts-header"); + savedCustomPromptsHeader.style.display = "none"; + } + else { + const savedCustomPromptsHeader = document.getElementById("saved-custom-prompts-header"); + savedCustomPromptsHeader.style.display = "block"; + } + + linkElement.target = "_blank"; // to open in a new tab + + customContextMenuList.innerHTML = ""; + + prompts.forEach((prompt, index) => { + const listItem = document.createElement("li"); + listItem.id = `option-${index}`; + listItem.textContent = prompt.description; + listItem.setAttribute("value", prompt.value); + customContextMenuList.appendChild(listItem); + }); + + setTimeout(() => { + customContextMenu.style.opacity = '1'; // fade the menu in after a delay + }, 200); // adjust delay as needed + }); + } + + async fetchReadmeFromRepo(repoUrl) { + const response = await fetch(repoUrl); + const data = await response.json(); + // Decode the base64 encoded content + return atob(data.content); + } + + parseReadmeContent(content) { + const lines = content.split('\n'); + const prompts = []; + let startParsing = false; // flag to indicate when to start parsing + + lines.forEach(line => { + // check if the current line starts with a dash (-) + if (line.trim().startsWith('-')) { + startParsing = true; // start parsing from the current line + } + + // if startParsing is true, parse the line + if (startParsing) { + const match = line.match(/^\-\s(.+):\s(.+)$/); + if (match) { + prompts.push({ description: match[1], value: match[2] }); + } + } + }); + + return prompts; + } + + loadPromptsFromChromeStorage() { + return new Promise((resolve) => { + chrome.storage.sync.get("customContextMenuPrompts", (result) => { + const prompts = result.customContextMenuPrompts || []; + resolve(prompts); + }); + }); + } + + + + promptsSettingsForm(){ + // Add this code in the DOMContentLoaded event listener in popup.js + const customContextMenuSettingsForm = document.getElementById("custom-context-menu-settings-tab"); + if (customContextMenuSettingsForm) { + customContextMenuSettingsForm.addEventListener("submit", (e) => { + e.preventDefault(); + + const prompts = []; + for (let i = 1; i <= 5; i++) { + const promptDescriptionInput = document.getElementById(`prompt-${i}-description`); + const promptValueInput = document.getElementById(`prompt-${i}-value`); + if (promptDescriptionInput && promptValueInput) { + prompts.push({ + description: promptDescriptionInput.value, + value: promptValueInput.value + }); + } + } + + chrome.storage.sync.set({ customContextMenuPrompts: prompts }, () => { + console.log("Custom context menu prompts saved."); + window.MyAssistant.loadingSuggestions.showOverlay("Saving..."); + this.updateCustomContextMenuOptions(); // Call the function to update the custom context menu options + }); + }); + } else { + console.error("Custom context menu settings form not found"); + } + } + + promptsGetMenuPrompts(){ + // Load the custom context menu settings when the popup is opened + chrome.storage.sync.get("customContextMenuPrompts", (result) => { + const prompts = result.customContextMenuPrompts || []; + for (let i = 1; i <= 5; i++) { + const promptDescriptionInput = document.getElementById(`prompt-${i}-description`); + const promptValueInput = document.getElementById(`prompt-${i}-value`); + if (promptDescriptionInput && promptValueInput) { + promptDescriptionInput.value = prompts[i - 1]?.description || ""; + promptValueInput.value = prompts[i - 1]?.value || ""; + } + } + }); + } + +} +window.MyAssistant.promptsMenu = new PromptsMenu; \ No newline at end of file diff --git a/layout/js/selected-text/contentScript.js b/layout/js/selected-text/contentScript.js new file mode 100644 index 0000000..c1a43f9 --- /dev/null +++ b/layout/js/selected-text/contentScript.js @@ -0,0 +1,70 @@ +// This script is to allow us to get selected text on the webpage +// and then copy it to our input field for a quicker user experience. +if (!window.hasRunContentScript) { + window.hasRunContentScript = true; + + console.log('contentScript.js is loading ok.'); + + chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + + if (request.action === "getSelectedText") { + const selectedText = window.getSelection().toString(); + + //console.log('Runtime.onMessage get selected text' ); + //console.log( selectedText ); + + chrome.runtime.sendMessage({ action: "textCopied" }); + + sendResponse(selectedText); + + } else if (request.action === 'replaceSelectedText') { + const replaced = replaceSelectedText(request.replacementText); + sendResponse({ success: true, replaced }); + } + }); + + let isReplacing = false; + + function replaceSelectedText(replacementText) { + if (isReplacing) { + return false; // Prevent multiple calls during the same event + } + + isReplacing = true; + + const activeElement = document.activeElement; + const isContentEditable = activeElement.getAttribute("contentEditable") === "true"; + const selection = window.getSelection(); + let replaced = false; + + if (activeElement.tagName.toLowerCase() === 'textarea' || activeElement.tagName.toLowerCase() === 'input') { + const start = activeElement.selectionStart; + const end = activeElement.selectionEnd; + activeElement.setRangeText(replacementText, start, end, 'select'); + activeElement.focus(); + replaced = true; + } else if (isContentEditable) { + if (selection.rangeCount > 0) { + const range = selection.getRangeAt(0); + range.deleteContents(); + const textNode = document.createTextNode(replacementText); + range.insertNode(textNode); + + // Move the caret to the end of the inserted text + range.setStartAfter(textNode); + range.setEndAfter(textNode); + range.collapse(false); + + selection.removeAllRanges(); + selection.addRange(range); + activeElement.focus(); + replaced = true; + } + } else { + console.log('Cannot replace selected text: active element is not an input, textarea, or contentEditable element'); + } + + isReplacing = false; // Reset the flag after the function has finished + return replaced; + } +} diff --git a/layout/js/selected-text/selected-text.js b/layout/js/selected-text/selected-text.js new file mode 100644 index 0000000..7e5d368 --- /dev/null +++ b/layout/js/selected-text/selected-text.js @@ -0,0 +1,41 @@ +class SelectedText { + + getSelectedTextFromActiveTab() { + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + const activeTab = tabs[0]; + chrome.tabs.sendMessage(activeTab.id, { action: "getSelectedText" }, (response) => { + // Check if response exists before trying to trim it + if (response && response.trim() !== "") { + const inputText = document.getElementById("input-text"); + inputText.value = response + '\n\n' + 'STOP: '; + + // Check if there are any suggestions in the suggestions-container + const suggestionsContainer = document.getElementById("suggestions-container"); + const hasSuggestions = suggestionsContainer.childElementCount > 0; + + // Check if the input-text field is empty + const inputTextEmpty = !inputText.value || inputText.value.trim() === ""; + + if (!hasSuggestions && inputTextEmpty) { + showCustomContextMenu(); + } else { + const customContextMenu = document.getElementById("custom-context-menu"); + customContextMenu.style.display = "none"; + } + } + }); + }); + } + + replaceSelectedTextOnActiveTab(replacementText) { + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + const activeTab = tabs[0]; + chrome.tabs.sendMessage(activeTab.id, { action: 'replaceSelectedText', replacementText }, (response) => { + if (!response.replaced) { + window.MyAssistant.popup.copyToClipboard(replacementText); + } + }); + }); + } +} +window.MyAssistant.selectedText = new SelectedText(); \ No newline at end of file diff --git a/layout/js/share/share.js b/layout/js/share/share.js new file mode 100644 index 0000000..62fce42 --- /dev/null +++ b/layout/js/share/share.js @@ -0,0 +1,25 @@ +class Share { + createShareButtons(responseTexts) { + const shareContainer = document.createElement("div"); + shareContainer.classList.add("share-container"); + + responseTexts.forEach(responseText => { + const twitterShare = document.createElement("button"); + twitterShare.classList.add("share-button", "twitter-share"); + twitterShare.innerHTML = ` + + + + `; + twitterShare.addEventListener("click", () => { + const url = `https://twitter.com/intent/tweet?text=${encodeURIComponent(responseText)}`; + window.open(url, "_blank"); + }); + + shareContainer.appendChild(twitterShare); + }); + + return shareContainer; + } +} +window.MyAssistant.share = new Share(); \ No newline at end of file diff --git a/layout/js/suggestions/loading-suggestions.js b/layout/js/suggestions/loading-suggestions.js new file mode 100644 index 0000000..1d69e30 --- /dev/null +++ b/layout/js/suggestions/loading-suggestions.js @@ -0,0 +1,289 @@ +class LoadingSuggestions { + constructor() { + this.getSavedSuggestions(); + } + urlify(text) { + + // Here are a few examples of the URLs that this regex would capture: + // https://www.example.com + // http://www.example.com + // https://www.example.com/path/to/resource + // https://www.example.com?query=string + // https://www.example.com#fragment + // https://sub-domain.example.com + //And here are a few examples of strings that this regex would not consider to be URLs: + // https://www.example.com. + // https://www.example.com! + // https://www.example.com, + // https://www.example.com... + // https://www..example.com + let urlRegex = /(https?:\/\/[^\s\/)]+(\/[^\s)]*)?)/g; + + text = text.replace(urlRegex, function(url) { + return '' + url + ''; + }); + + let twitterHandleRegex = /(@[a-zA-Z0-9_]{1,15})/g; + text = text.replace(twitterHandleRegex, function(handle) { + let handleWithoutAt = handle.substring(1); + return '' + handle + ''; + }); + + let twitterHashtagRegex = /(#[a-zA-Z0-9_]+)/g; + text = text.replace(twitterHashtagRegex, function(hashtag) { + let hashtagWithoutHash = encodeURIComponent(hashtag.substring(1)); + return '' + hashtag + ''; + }); + + let codeBlockRegex = /(```[\s\S]*?```)/g; + text = text.replace(codeBlockRegex, function(code) { + // Convert markdown to HTML + let html = new showdown.Converter({backslashEscapesHTMLTags: true}).makeHtml(code); + + return html; + }); + + return text; + } + + createSuggestionElement(suggestionObj) { + const suggestion = document.createElement("div"); + suggestion.classList.add("suggestion"); + + if (suggestionObj.isUserInput) { + suggestion.classList.add("user-input"); + } + + suggestion.innerHTML = this.urlify(suggestionObj.text); + + // Add an event listener to the suggestion element + suggestion.addEventListener('click', (e) => { + + let selectedText = window.getSelection().toString(); + + // If user selected some text, copy that + if (selectedText.length > 0) { + window.MyAssistant.selectedText.replaceSelectedTextOnActiveTab(selectedText); + } + // If no text is selected, copy all the text within the div + else { + window.MyAssistant.selectedText.replaceSelectedTextOnActiveTab(suggestionObj.text); + } + this.showCopiedOverlay(e.target); + }); + + const shareButtons = window.MyAssistant.share.createShareButtons([suggestionObj.text]); + + chrome.storage.sync.get('showShareButtons', (data) => { + if (data.showShareButtons) { + const shareButtons = window.MyAssistant.share.createShareButtons([suggestionObj.text]); + suggestion.appendChild(shareButtons); + } + }); + + return suggestion; + } + + // This needs to be out of the addEventListener("DOMContentLoaded") so the + // custom context menu will not load if there are suggestions in the local storage + // that need to be loaded. + // Get Saved Suggestions from local storage if there before anything else. + getSavedSuggestions() { + + chrome.storage.local.get("savedSuggestions", (data) => { + + console.log(data); + const suggestions = data.savedSuggestions || []; + let totalTokenCount = 0; + const suggestionsContainer = document.getElementById("suggestions-container"); + suggestions.forEach(suggestionObj => { + const suggestionElement = this.createSuggestionElement(suggestionObj); + suggestionsContainer.appendChild(suggestionElement); + // Call Prism.highlightAll() after the content has been added to the DOM + Prism.highlightAll(); + + console.log("Current suggestion tokens: ", suggestionObj.tokens); + + // Add the token count of the current suggestion to the total token count + totalTokenCount += suggestionObj.tokens; + }); + // Now totalTokenCount contains the sum of tokens of all saved suggestions + console.log(totalTokenCount); + // Find the HTML element where the total token count will be displayed + const totalTokenCountElement = document.getElementById("token-count"); + + // Update its text content + totalTokenCountElement.textContent = `Total Tokens: ${totalTokenCount}`; + }); + } + + displaySuggestions(inputTextObj, suggestions) { + const suggestionsContainer = document.getElementById("suggestions-container"); + + let totalTokenCount = 0; // Initialize total token count + + if (inputTextObj) { + const inputTextElement = this.createSuggestionElement(inputTextObj); + suggestionsContainer.appendChild(inputTextElement); + totalTokenCount += inputTextObj.tokens; // Add the token count of the inputTextObj to the total token count + } + + for (const suggestionObj of suggestions) { + const suggestionElement = this.createSuggestionElement(suggestionObj); + suggestionsContainer.appendChild(suggestionElement); + totalTokenCount += suggestionObj.tokens; // Add the token count of the suggestionObj to the total token count + } + + // Save total token count to local storage + chrome.storage.local.set({ totalTokenCount: totalTokenCount }, function() { + + // Testing + // console.log("Total token count saved to local storage: " + totalTokenCount); + + window.MyAssistant.tokenCount.refreshTokenCount(); // Move the call to refreshTokenCount here + + if (chrome.runtime.lastError) { + console.log("Runtime error: ", chrome.runtime.lastError); + } else { + // Testing + // console.log("Total token count saved to local storage: " + totalTokenCount); + } + }); + + // Call Prism.highlightAll() after the content has been added to the DOM + Prism.highlightAll(); + + chrome.storage.local.get(['savedSuggestions'], function(result) { + let existingSuggestions = result.savedSuggestions; + if (existingSuggestions === undefined) { + existingSuggestions = []; + } + + // Add input text to existingSuggestions + if (inputTextObj) { + inputTextObj.tokens = inputTextObj.text.split(" ").length; + existingSuggestions.push(inputTextObj); + } + + // Add all new suggestions to existingSuggestions + suggestions.forEach(suggestionObj => { + suggestionObj.tokens = suggestionObj.text.split(" ").length; + existingSuggestions.push(suggestionObj); + }); + + // Save the updated list back to storage + chrome.storage.local.set({ savedSuggestions: existingSuggestions }, function () { + console.log("Suggestions saved."); + // Update total token count + let totalTokenCount = 0; + existingSuggestions.forEach(suggestionObj => { + totalTokenCount += suggestionObj.tokens; + }); + // Save the total token count in the local storage + chrome.storage.local.set({ totalTokenCount: totalTokenCount }, function() { + // Testing + // console.log("Total token count saved to local storage: " + totalTokenCount); + window.MyAssistant.tokenCount.refreshTokenCount(); // Refresh the count after it's saved + }); + }); + }); + + // Show or hide the "Clear" button + const clearButton = document.getElementById("clear-button"); + if (clearButton) { + if (suggestions.length > 0) { + clearButton.style.display = "inline-block"; + } else { + clearButton.style.display = "none"; + } + } + } + + showLoadingAnimation() { + const suggestionsContainer = document.getElementById("suggestions-container"); + // this will clear the suggestions if you click the Get Suggestions button. + // suggestionsContainer.innerHTML = ""; // Clear the suggestions container + + const loadingAnimation = document.createElement("div"); + loadingAnimation.classList.add("loading-animation"); + + for (let i = 0; i < 3; i++) { + const dot = document.createElement("div"); + dot.classList.add("dot"); + loadingAnimation.appendChild(dot); + } + + suggestionsContainer.appendChild(loadingAnimation); + return loadingAnimation; // Add this line + } + + scrollToLoadingAnimation(loadingAnimation) { + const container = document.getElementById("suggestions-container"); + const loadingAnimationOffset = loadingAnimation.offsetTop - 140; + container.scrollTo({ + top: loadingAnimationOffset, + behavior: 'smooth' + }); + } + + + hideLoadingAnimation() { + const loadingAnimation = document.querySelector(".loading-animation"); + if (loadingAnimation) { + document.getElementById("suggestions-container").removeChild(loadingAnimation); + } + } + +// For Get Suggestions specifically + showCopiedOverlay(target) { + const overlay = document.createElement('div'); + overlay.classList.add('copied-overlay'); + overlay.textContent = 'Copied'; + + // Set position and size of the overlay + const suggestionRect = target.getBoundingClientRect(); + + overlay.style.position = 'fixed'; + overlay.style.top = suggestionRect.top + 'px'; + overlay.style.left = suggestionRect.left + 'px'; + overlay.style.width = suggestionRect.width + 'px'; + overlay.style.height = suggestionRect.height + 'px'; + overlay.style.display = 'flex'; + overlay.style.justifyContent = 'center'; + overlay.style.alignItems = 'center'; + + document.body.appendChild(overlay); + setTimeout(() => { + document.body.removeChild(overlay); + }, 1500); + } + +// For Saving + showOverlay(text) { + const targetElement = document.body; + const overlay = document.createElement("div"); + overlay.classList.add("overlay"); + overlay.textContent = text; + + overlay.style.position = "fixed"; + overlay.style.top = "0"; + overlay.style.left = "0"; + overlay.style.width = "100%"; + overlay.style.height = "100%"; + overlay.style.display = "flex"; + overlay.style.justifyContent = "center"; + overlay.style.alignItems = "center"; + overlay.style.backgroundColor = "rgba(0, 0, 0, .8)"; + overlay.style.color = "white"; + overlay.style.zIndex = "10"; + + targetElement.appendChild(overlay); + + setTimeout(() => { + document.body.removeChild(overlay); + }, 1500); + + // return overlay; + } +} +window.MyAssistant.loadingSuggestions = new LoadingSuggestions(); \ No newline at end of file diff --git a/layout/js/token-count/token-count.js b/layout/js/token-count/token-count.js new file mode 100644 index 0000000..1cbe9c4 --- /dev/null +++ b/layout/js/token-count/token-count.js @@ -0,0 +1,19 @@ +class TokenCount { + refreshTokenCount() { + // Retrieve total token count from local storage + chrome.storage.local.get("totalTokenCount", function(data) { + let totalTokenCount = data.totalTokenCount || 0; // If there's no value in the local storage, default to 0 + + // Find the HTML element where the total token count will be displayed + const totalTokenCountElement = document.getElementById("token-count"); + + // Used for testing. This will return NAN if using the clear button, be best to set an option to + // not run it in the future. + // console.log("Total token count loaded from local storage: " + data.totalTokenCount); + + // Update its text content + totalTokenCountElement.textContent = `Total Tokens: ${totalTokenCount}`; + }); + } +} +window.MyAssistant.tokenCount = new TokenCount(); \ No newline at end of file diff --git a/layout/js/updates/updates.js b/layout/js/updates/updates.js new file mode 100644 index 0000000..75cf7b7 --- /dev/null +++ b/layout/js/updates/updates.js @@ -0,0 +1,67 @@ +class Updates { + checkForUpdates() { + // Fetch the last notified version and last clicked version from storage + chrome.storage.sync.get(['lastNotifiedVersion', 'lastClickedVersion'], ({ lastNotifiedVersion, lastClickedVersion }) => { + // Fetch the current version from the extension's manifest + const currentVersion = chrome.runtime.getManifest().version; + + // If the current version doesn't match the last notified version or the last clicked version, show the update notification + if (!lastNotifiedVersion || currentVersion !== lastNotifiedVersion || currentVersion !== lastClickedVersion) { + document.getElementById('updateNotification').style.opacity = '1'; + console.log( currentVersion ); + console.log( lastClickedVersion ); + } else { + // If the versions match, hide the update notification + document.getElementById('updateNotification').style.opacity = '0'; + document.getElementById('updateNotification').style.pointerEvents = 'none'; + } + }); + } + + setupNotificationClickListener() { + // When the notification is clicked, update the last clicked version to the current version + const currentVersion = chrome.runtime.getManifest().version; + chrome.storage.sync.set({ lastClickedVersion: currentVersion }, () => { + // Hide the notification + document.getElementById('updateNotification').style.opacity = '0'; + document.getElementById('updateNotification').style.pointerEvents = 'none'; + + // Request the changelog from the background script + this.fetchChangeLog(); + }); + } + + + + fetchChangeLog() { + const changelogContainer = document.getElementById("changelog-container"); + + // Create and display loading animation + const loadingAnimation = document.createElement("div"); + loadingAnimation.classList.add("loading-animation"); + for (let i = 0; i < 3; i++) { + const dot = document.createElement("div"); + dot.classList.add("dot"); + loadingAnimation.appendChild(dot); + } + changelogContainer.appendChild(loadingAnimation); + + // Request the changelog from the background script + chrome.runtime.sendMessage({action: "fetchChangelog"}, response => { + // Remove loading animation + changelogContainer.removeChild(loadingAnimation); + + // Handle fetch failure + if (response.error) { + changelogContainer.textContent = "Failed to load the changelog. Please try again."; + return; + } + + // Display and fade in changelog text + changelogContainer.textContent = response.changelog; + changelogContainer.classList.add("changelog-fade-in"); + }); + } + +} +window.MyAssistant.updates = new Updates(); \ No newline at end of file diff --git a/manifest.json b/manifest.json index 921b8b7..ea9a4a1 100644 --- a/manifest.json +++ b/manifest.json @@ -1,9 +1,9 @@ { "manifest_version": 3, "name": "SlickRemix: Your ChatGPT Web Assistant", - "version": "0.0.1", - "description": "Swiftly optimize text, code, and creativity on any website using GPT-3 or GPT-4.", - "permissions": ["storage", "tabs", "activeTab"], + "version": "0.0.3", + "description": "Swiftly optimize text, code, and creativity on any website using GPT-3 Turbo or GPT-4.", + "permissions": ["storage","activeTab","scripting"], "action": { "default_popup": "popup.html", "default_icon": { @@ -15,12 +15,6 @@ "background": { "service_worker": "background.js" }, - "content_scripts": [ - { - "matches": [""], - "js": ["content.js"] - } - ], "icons": { "16": "icons/icon16.png", "48": "icons/icon48.png", diff --git a/namespace.js b/namespace.js new file mode 100644 index 0000000..12ae35a --- /dev/null +++ b/namespace.js @@ -0,0 +1,2 @@ +// My namespace.js +window.MyAssistant = window.MyAssistant || {}; \ No newline at end of file diff --git a/popup.html b/popup.html index 4c25e44..2f9990b 100644 --- a/popup.html +++ b/popup.html @@ -1,92 +1,365 @@ - + + - - - + +
+
+
Saved Prompts
+
+
+
-
-
Your Personal Automated Assistant
-
- - + + +
Your Personal Automated Assistant + +
+ notifications +
+ +
+ + +
+
+ +
+
+
- - + + -
+ Token count: 0
+

Settings Options

+

To get started, add an API Key below. If you don't have one, you can sign up for Free to gain your API Key from OpenAI. After + adding your API Key, click the menu in the top right corner and choose Custom Prompts; + otherwise, click Suggestions.

+
- - - - - - - - - -
-
- + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/readme.md b/readme.md index c3961d9..179d15e 100644 --- a/readme.md +++ b/readme.md @@ -1,43 +1,98 @@ -# SlickRemix: ChatGPT Chrome Extension Web Assistant +# Slick Personal Assistant :robot: ![440x280.png](https://raw.githubusercontent.com/spencerslickremix/slickGPT/main/screenshots/440x280.png) -A Chrome extension that adds a customized version of [ChatGPT](https://chat.openai.com) to your browser. It can assist in creating content or code for any website based on your suggestions. -Pin it to your browser so you can easily use it on any website. No need to switch between tabs. +## :book: Description -Choose between GPT-3 or GPT-4, the Number of Tokens, Alternative Completions, Temperature, and Light and Dark Mode. An easy click to copy content and paste where ever you need it. +A Chrome extension that adds [ChatGPT](https://chat.openai.com) to your browser. It can assist in creating content or code for any website based on your suggestions. -You will need to add your own [API Key](https://platform.openai.com/account/api-keys). Setup is really simple, and if you don't already have an account with OpenAI, you will receive a free credit of $18 US dollars. +Pin the Extension to your browser so you can easily use on any tab. It will work independently between tabs too. -More options coming very soon. +### **Options** +- GPT-3 Turbo or GPT-4 +- Custom Prompts or GitHub Prompts +- Number of Tokens +- Alternative Completions +- Temperature +- Twitter Share Suggestion Icon +- Light and Dark Mode -![Screenshot 1.png](https://raw.githubusercontent.com/spencerslickremix/slickGPT/main/screenshots/Screenshot%201.png) +Below are some use cases and also a breakdown of the Custom Options, Settings and their meaning. -![Screenshot 2.png](https://raw.githubusercontent.com/spencerslickremix/slickGPT/main/screenshots/Screenshot%202.png) +--- -![Screenshot 3.png](https://raw.githubusercontent.com/spencerslickremix/slickGPT/main/screenshots/Screenshot%203.png) +## :bulb: Use Cases +1. Say you are editing your website and want to modify some text, or create a blog post. It would be easier to select that text on your website, have it appear automatically in the popup, and then ask ChatGPT to make the changes you want after the STOP. Once a suggestion(s) appears, click it and the selected text on the page will be replaced. By adjusting the Temperature from the Settings tab, you can have the response be less or more creative. +2. You don't have to use this in conjunction with a website. You can also simply use it like ChatGPT for having a conversation and creating or modifying code. +3. Great for writing a creative Tweet that you can easily share on Twitter once you are done. +4. Useful for creative writing in an email or text message. All you have to do is click the response when you are done, and the text is copied to your clipboard. +5. Quickly Translate Text. -![Screenshot 4.png](https://raw.githubusercontent.com/spencerslickremix/slickGPT/main/screenshots/Screenshot%204.png) +--- -![Screenshot 5.png](https://raw.githubusercontent.com/spencerslickremix/slickGPT/main/screenshots/Screenshot%205.png) +## :wrench: Custom Options +1. Select text on the web page and click the pinned extension; the selected text will automatically be pasted to the textarea in the chrome extension popup. +2. I added a STOP: after the pasted text so you can easily prompt your request, or not. +3. Once you click the "Submit" button, one or more suggestions will appear. +4. You can then click on a suggestion, and the text from that suggestion will be copied in place of the selected text on the webpage if it's a content editable area. +5. The text will also be added to the clipboard, allowing you to paste it in another tab or anywhere. +6. **Bonus:** Click the Twitter icon that appears in the top right of a suggestion(s), and it will open Twitter so you can share the suggestion. -## Install +--- -Simply click green CODE button on this repo and download the ZIP file and unpack it wherever you want on your computer. +## :pencil: Custom Prompts +1. Add up to 5 custom Descriptions and Prompts. +2. Connect a GitHub account to display as many Prompts as you want. See the [ChatGPT Custom Prompts](https://github.com/spencerslickremix/chatGPT-Prompts) and learn how to connect them to the Chrome Extension. -Add the extension +--- -1. Go to chrome://extensions in your Google Chrome browser + +## :gear: Settings +1. **[API Key](https://platform.openai.com/account/api-keys):** Your OpenAI API key is required to access ChatGPT. Click the API Key link to get yours. You must have an account setup. OpenAI gives you $18 free to start; after that, you will need to add a payment method to continue use. Cheaper than paying $20 a month for ChatGPT Pro. +2. **Max Tokens:** The maximum number of tokens (words or word pieces) in the generated text. 4097 is the maximum allowed per prompt. +3. **Alternative Completions:** The number of alternative completions to generate for each prompt. +4. **Temperature:** Controls the randomness of the generated text. Higher values (e.g., 1.0) result in more creative output, while lower values (e.g., 0.1) produce more coherent text. +5. **Type:** GPT-4 or GPT-3 +6. **Share:** Twitter Share option for Suggestions. +7 **Mode:** Light or Dark + +--- + +## :camera: Screenshots + +![Screenshot 11.png](https://raw.githubusercontent.com/spencerslickremix/Slick-Personal-Assistant/main/screenshots/Screenshot%206.png) + +![Screenshot 1.png](https://raw.githubusercontent.com/spencerslickremix/Slick-Personal-Assistant/main/screenshots/Screenshot%201.png) + +![Screenshot 2.png](https://raw.githubusercontent.com/spencerslickremix/Slick-Personal-Assistant/main/screenshots/Screenshot%202.png) + +![Screenshot 8.png](https://raw.githubusercontent.com/spencerslickremix/Slick-Personal-Assistant/main/screenshots/Screenshot%203.png) + +![Screenshot 9.png](https://raw.githubusercontent.com/spencerslickremix/Slick-Personal-Assistant/main/screenshots/Screenshot%204.png) + +![Screenshot 10.png](https://raw.githubusercontent.com/spencerslickremix/Slick-Personal-Assistant/main/screenshots/Screenshot%205.png) + +--- + +## :rocket: Install +Simply click the green CODE button near the top right of this repo. Download the ZIP file and unpack it wherever you want on your computer. + +--- + +### Add the extension + +1. Go to `chrome://extensions` in your Google Chrome browser 2. Check the Developer mode checkbox in the top right-hand corner 3. Click "Load Unpacked" to see a file-selection dialog -4. Select the location where the slickGPT folder is. +4. Select the location where the Slick-Personal-Assistant folder is. 5. Click the Google Extensions icon in your browser tab, looks like a puzzle piece. -6. Pin the slickGPT extension to your browser. +6. Pin the "SlickRemix: Your ChatGPT Web Assistant" extension to your browser. 7. Add your own API key by clicking the Settings button. It's that simple, have fun and enjoy! -## License +--- -MIT © Spencer Labadie (follow me on Twitter) \ No newline at end of file +## :scroll: License +2023 MIT © Spencer Labadie . Follow me on diff --git a/screenshots/Screenshot 1.png b/screenshots/Screenshot 1.png index d53ce5c..d97fd27 100644 Binary files a/screenshots/Screenshot 1.png and b/screenshots/Screenshot 1.png differ diff --git a/screenshots/Screenshot 2.png b/screenshots/Screenshot 2.png index ac28729..c1c406c 100644 Binary files a/screenshots/Screenshot 2.png and b/screenshots/Screenshot 2.png differ diff --git a/screenshots/Screenshot 3.png b/screenshots/Screenshot 3.png index d579cdd..78d90aa 100644 Binary files a/screenshots/Screenshot 3.png and b/screenshots/Screenshot 3.png differ diff --git a/screenshots/Screenshot 4.png b/screenshots/Screenshot 4.png index 3f0d246..8f43b69 100644 Binary files a/screenshots/Screenshot 4.png and b/screenshots/Screenshot 4.png differ diff --git a/screenshots/Screenshot 5.png b/screenshots/Screenshot 5.png index 3b8adfa..81fd3a0 100644 Binary files a/screenshots/Screenshot 5.png and b/screenshots/Screenshot 5.png differ diff --git a/screenshots/Screenshot 6.png b/screenshots/Screenshot 6.png new file mode 100644 index 0000000..ef87880 Binary files /dev/null and b/screenshots/Screenshot 6.png differ