From 511cfcfcc77e3ca4e8018978f8f2521e165d2063 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Fri, 17 Apr 2026 11:33:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E4=BC=98=E5=8C=96292?= =?UTF-8?q?5=E9=82=AE=E7=AE=B1=E5=92=8Cgmail=E9=82=AE=E7=AE=B1=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- background.js | 276 +++++++++++++++++- background/auto-run-controller.js | 2 + background/generated-email-helpers.js | 26 ++ background/signup-flow-helpers.js | 5 +- managed-alias-utils.js | 146 +++++++++ sidepanel/sidepanel.html | 5 +- sidepanel/sidepanel.js | 219 ++++++++++++-- tests/auto-run-fresh-attempt-reset.test.js | 6 + .../background-signup-step2-branching.test.js | 39 +++ tests/managed-alias-utils.test.js | 28 ++ 10 files changed, 711 insertions(+), 41 deletions(-) create mode 100644 managed-alias-utils.js create mode 100644 tests/managed-alias-utils.test.js diff --git a/background.js b/background.js index eeb4a0b..3dc801e 100644 --- a/background.js +++ b/background.js @@ -1,6 +1,7 @@ // background.js — Service Worker: orchestration, state, tab management, message routing importScripts( + 'managed-alias-utils.js', 'background/account-run-history.js', 'background/panel-bridge.js', 'background/generated-email-helpers.js', @@ -215,6 +216,8 @@ const PERSISTED_SETTING_DEFAULTS = { icloudHostPreference: 'auto', accountRunHistoryTextEnabled: false, accountRunHistoryHelperBaseUrl: DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL, + gmailBaseEmail: '', + mail2925BaseEmail: '', emailPrefix: '', inbucketHost: '', inbucketMailbox: '', @@ -780,6 +783,8 @@ function normalizePersistentSettingValue(key, value) { return normalizeIcloudHost(value) || 'auto'; case 'accountRunHistoryHelperBaseUrl': return normalizeAccountRunHistoryHelperBaseUrl(value); + case 'gmailBaseEmail': + case 'mail2925BaseEmail': case 'emailPrefix': return String(value || '').trim(); case 'inbucketHost': @@ -1928,13 +1933,11 @@ function isGeneratedAliasProvider(stateOrProvider, mail2925Mode = undefined) { const provider = typeof stateOrProvider === 'string' ? stateOrProvider : stateOrProvider?.mailProvider; - if (provider === GMAIL_PROVIDER) { - return true; + const utils = (typeof self !== 'undefined' ? self : globalThis).MultiPageManagedAliasUtils || null; + if (utils?.isManagedAliasProvider) { + return utils.isManagedAliasProvider(provider); } - const resolvedMail2925Mode = mail2925Mode !== undefined - ? normalizeMail2925Mode(mail2925Mode) - : getMail2925Mode(stateOrProvider); - return provider === '2925' && resolvedMail2925Mode === MAIL_2925_MODE_PROVIDE; + return provider === GMAIL_PROVIDER || provider === '2925'; } function shouldUseCustomRegistrationEmail(state = {}) { @@ -1970,6 +1973,163 @@ function buildGeneratedAliasEmail(state) { throw new Error(`未支持的别名邮箱类型:${provider}`); } +function getManagedAliasUtils() { + return (typeof self !== 'undefined' ? self : globalThis).MultiPageManagedAliasUtils || null; +} + +function parseGmailBaseEmail(rawValue) { + const utils = getManagedAliasUtils(); + if (utils?.parseManagedAliasBaseEmail) { + return utils.parseManagedAliasBaseEmail(rawValue, GMAIL_PROVIDER); + } + + const value = String(rawValue || '').trim().toLowerCase(); + const match = value.match(/^([^@\s+]+)@((?:gmail|googlemail)\.com)$/i); + if (!match) return null; + return { + localPart: match[1], + domain: match[2].toLowerCase(), + }; +} + +function parseManagedAliasBaseEmail(rawValue, provider) { + const utils = getManagedAliasUtils(); + if (utils?.parseManagedAliasBaseEmail) { + return utils.parseManagedAliasBaseEmail(rawValue, provider); + } + + if (provider === GMAIL_PROVIDER) { + return parseGmailBaseEmail(rawValue); + } + + const value = String(rawValue || '').trim().toLowerCase(); + const match = value.match(/^([^@\s+]+)@(2925\.com)$/i); + if (!match) return null; + return { + localPart: match[1], + domain: match[2].toLowerCase(), + }; +} + +function isManagedAliasEmail(value, provider, baseEmail = '') { + const utils = getManagedAliasUtils(); + if (utils?.isManagedAliasEmail) { + return utils.isManagedAliasEmail(value, provider, baseEmail); + } + + const normalizedValue = String(value || '').trim().toLowerCase(); + if (!normalizedValue) return false; + const parsedEmail = normalizedValue.match(/^([^@\s]+)@([^@\s]+\.[^@\s]+)$/); + if (!parsedEmail) return false; + + const candidateLocalPart = parsedEmail[1]; + const candidateDomain = parsedEmail[2]; + if (provider === GMAIL_PROVIDER) { + if (!/^(?:gmail|googlemail)\.com$/i.test(candidateDomain)) { + return false; + } + const parsedBaseEmail = parseManagedAliasBaseEmail(baseEmail, provider); + if (!parsedBaseEmail) { + return true; + } + return candidateDomain === parsedBaseEmail.domain + && candidateLocalPart.split('+')[0] === parsedBaseEmail.localPart; + } + + if (provider !== '2925' || candidateDomain !== '2925.com') { + return false; + } + + const parsedBaseEmail = parseManagedAliasBaseEmail(baseEmail, provider); + if (!parsedBaseEmail) { + return true; + } + + return candidateLocalPart === parsedBaseEmail.localPart || candidateLocalPart.startsWith(parsedBaseEmail.localPart); +} + +function getManagedAliasBaseEmail(state = {}, provider = state?.mailProvider) { + const normalizedProvider = String(provider || '').trim().toLowerCase(); + const legacyEmailPrefix = String(state?.emailPrefix || '').trim(); + if (normalizedProvider === GMAIL_PROVIDER) { + const gmailBaseEmail = String(state?.gmailBaseEmail || '').trim(); + if (gmailBaseEmail) { + return gmailBaseEmail; + } + return parseManagedAliasBaseEmail(legacyEmailPrefix, normalizedProvider) ? legacyEmailPrefix : ''; + } + + if (normalizedProvider === '2925') { + const mail2925BaseEmail = String(state?.mail2925BaseEmail || '').trim(); + if (mail2925BaseEmail) { + return mail2925BaseEmail; + } + return parseManagedAliasBaseEmail(legacyEmailPrefix, normalizedProvider) ? legacyEmailPrefix : ''; + } + + return ''; +} + +function isGeneratedAliasProvider(stateOrProvider, mail2925Mode = undefined) { + const provider = typeof stateOrProvider === 'string' + ? stateOrProvider + : stateOrProvider?.mailProvider; + const utils = getManagedAliasUtils(); + if (utils?.isManagedAliasProvider) { + return utils.isManagedAliasProvider(provider); + } + return provider === GMAIL_PROVIDER || provider === '2925'; +} + +function shouldUseCustomRegistrationEmail(state = {}) { + return isCustomMailProvider(state) + || (!isHotmailProvider(state) + && !isGeneratedAliasProvider(state) + && normalizeEmailGenerator(state.emailGenerator) === 'custom'); +} + +function isReusableGeneratedAliasEmail(state = {}, email = state?.email) { + if (!isGeneratedAliasProvider(state)) { + return false; + } + + return isManagedAliasEmail(email, state?.mailProvider, getManagedAliasBaseEmail(state)); +} + +function buildGeneratedAliasEmail(state) { + const provider = state.mailProvider || '163'; + const baseEmail = getManagedAliasBaseEmail(state, provider); + const baseLabel = provider === GMAIL_PROVIDER ? 'Gmail 原邮箱' : '2925 基邮箱'; + const exampleEmail = provider === GMAIL_PROVIDER ? 'name@gmail.com' : 'name@2925.com'; + + if (!baseEmail) { + throw new Error(`${baseLabel}未设置,请先在侧边栏填写,或直接在“注册邮箱”中手动填写完整邮箱。`); + } + + if (!parseManagedAliasBaseEmail(baseEmail, provider)) { + throw new Error(`${baseLabel}格式不正确,请填写类似 ${exampleEmail} 的地址。`); + } + + const utils = getManagedAliasUtils(); + if (utils?.buildManagedAliasEmail) { + return utils.buildManagedAliasEmail( + provider, + baseEmail, + provider === GMAIL_PROVIDER ? generateRandomWordAliasTag() : generateRandomSuffix(6) + ); + } + + const parsedBaseEmail = parseManagedAliasBaseEmail(baseEmail, provider); + if (provider === GMAIL_PROVIDER) { + return `${parsedBaseEmail.localPart}+${generateRandomWordAliasTag()}@${parsedBaseEmail.domain}`; + } + if (provider === '2925') { + return `${parsedBaseEmail.localPart}${generateRandomSuffix(6)}@${parsedBaseEmail.domain}`; + } + + throw new Error(`未支持的别名邮箱类型:${provider}`); +} + function getLuckmailSessionConfig(state = {}) { return { apiKey: String(state.luckmailApiKey || ''), @@ -4742,6 +4902,7 @@ function getEmailGeneratorLabel(generator) { } const generatedEmailHelpers = self.MultiPageGeneratedEmailHelpers?.createGeneratedEmailHelpers({ addLog, + buildGeneratedAliasEmail, buildCloudflareTempEmailHeaders, CLOUDFLARE_TEMP_EMAIL_GENERATOR, DUCK_AUTOFILL_URL, @@ -4754,6 +4915,7 @@ const generatedEmailHelpers = self.MultiPageGeneratedEmailHelpers?.createGenerat normalizeCloudflareDomain, normalizeCloudflareTempEmailAddress, normalizeEmailGenerator, + isGeneratedAliasProvider, reuseOrCreateTab, sendToContentScript, setEmailState, @@ -5009,6 +5171,107 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) { return resumedState.email; } +async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) { + const currentState = await getState(); + if (isHotmailProvider(currentState)) { + const account = await ensureHotmailAccountForFlow({ + allowAllocate: true, + markUsed: true, + preferredAccountId: null, + }); + await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:已分配 Hotmail 账号 ${account.email}(第 ${attemptRuns} 次尝试)===`, 'ok'); + return account.email; + } + + if (isLuckmailProvider(currentState)) { + const purchase = await ensureLuckmailPurchaseForFlow({ allowReuse: true }); + await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:LuckMail 邮箱已就绪:${purchase.email_address}(第 ${attemptRuns} 次尝试)===`, 'ok'); + return purchase.email_address; + } + + if (isGeneratedAliasProvider(currentState)) { + if (isReusableGeneratedAliasEmail(currentState)) { + await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:当前已复用 ${currentState.email},将直接继续执行(第 ${attemptRuns} 次尝试)===`, 'info'); + return currentState.email; + } + + const baseEmail = getManagedAliasBaseEmail(currentState); + if (!baseEmail && !currentState.email) { + const baseLabel = currentState.mailProvider === GMAIL_PROVIDER ? 'Gmail 原邮箱' : '2925 基邮箱'; + throw new Error(`${baseLabel}未设置,请先填写,或直接在“注册邮箱”中手动填写完整邮箱。`); + } + + await addLog( + `=== 目标 ${targetRun}/${totalRuns} 轮:${currentState.mailProvider === GMAIL_PROVIDER ? 'Gmail +tag' : '2925'} 模式已启用,将在步骤 3 自动生成邮箱(第 ${attemptRuns} 次尝试)===`, + 'info' + ); + return null; + } + + if (currentState.email) { + return currentState.email; + } + + if (shouldUseCustomRegistrationEmail(currentState)) { + await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮已暂停:请先填写自定义注册邮箱,然后继续 ===`, 'warn'); + await broadcastAutoRunStatus('waiting_email', { + currentRun: targetRun, + totalRuns, + attemptRun: attemptRuns, + }); + + await waitForResume(); + + const resumedState = await getState(); + if (!resumedState.email) { + throw new Error('无法继续:当前没有注册邮箱。'); + } + return resumedState.email; + } + + const generator = normalizeEmailGenerator(currentState.emailGenerator); + const generatorLabel = getEmailGeneratorLabel(generator); + let lastError = null; + for (let attempt = 1; attempt <= EMAIL_FETCH_MAX_ATTEMPTS; attempt++) { + try { + if (attempt > 1) { + await addLog(`${generatorLabel}:正在进行第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次自动获取重试...`, 'warn'); + } + const generatedEmail = await fetchGeneratedEmail(currentState, { generateNew: true, generator }); + await addLog( + `=== 目标 ${targetRun}/${totalRuns} 轮:${generatorLabel}已就绪:${generatedEmail}(第 ${attemptRuns} 次尝试,第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次获取)===`, + 'ok' + ); + return generatedEmail; + } catch (err) { + lastError = err; + await addLog(`${generatorLabel}自动获取失败(${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS}):${err.message}`, 'warn'); + if ( + (generator === 'cloudflare' && /域名/.test(String(err.message || ''))) + || (generator === CLOUDFLARE_TEMP_EMAIL_GENERATOR && /(服务地址|Admin Auth|域名)/.test(String(err.message || ''))) + ) { + break; + } + } + } + + await addLog(`${generatorLabel}自动获取已连续失败 ${EMAIL_FETCH_MAX_ATTEMPTS} 次:${lastError?.message || '未知错误'}`, 'error'); + await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮已暂停:请先自动获取邮箱或手动粘贴邮箱,然后继续 ===`, 'warn'); + await broadcastAutoRunStatus('waiting_email', { + currentRun: targetRun, + totalRuns, + attemptRun: attemptRuns, + }); + + await waitForResume(); + + const resumedState = await getState(); + if (!resumedState.email) { + throw new Error('无法继续:当前没有邮箱地址。'); + } + return resumedState.email; +} + async function runAutoSequenceFromStep(startStep, context = {}) { const { targetRun, totalRuns, attemptRuns, continued = false } = context; let postStep6RestartCount = 0; @@ -5226,6 +5489,7 @@ const signupFlowHelpers = self.MultiPageSignupFlowHelpers?.createSignupFlowHelpe ensureLuckmailPurchaseForFlow, getTabId, isGeneratedAliasProvider, + isReusableGeneratedAliasEmail, isSignupEmailVerificationPageUrl, isHotmailProvider, isLuckmailProvider, diff --git a/background/auto-run-controller.js b/background/auto-run-controller.js index 5105ccb..a7d663e 100644 --- a/background/auto-run-controller.js +++ b/background/auto-run-controller.js @@ -352,6 +352,8 @@ autoStepDelaySeconds: prevState.autoStepDelaySeconds, mailProvider: prevState.mailProvider, emailGenerator: prevState.emailGenerator, + gmailBaseEmail: prevState.gmailBaseEmail, + mail2925BaseEmail: prevState.mail2925BaseEmail, emailPrefix: prevState.emailPrefix, inbucketHost: prevState.inbucketHost, inbucketMailbox: prevState.inbucketMailbox, diff --git a/background/generated-email-helpers.js b/background/generated-email-helpers.js index c070318..208b1a3 100644 --- a/background/generated-email-helpers.js +++ b/background/generated-email-helpers.js @@ -4,6 +4,7 @@ function createGeneratedEmailHelpers(deps = {}) { const { addLog, + buildGeneratedAliasEmail, buildCloudflareTempEmailHeaders, CLOUDFLARE_TEMP_EMAIL_GENERATOR, DUCK_AUTOFILL_URL, @@ -16,6 +17,7 @@ normalizeCloudflareDomain, normalizeCloudflareTempEmailAddress, normalizeEmailGenerator, + isGeneratedAliasProvider, reuseOrCreateTab, sendToContentScript, setEmailState, @@ -185,8 +187,32 @@ return result.email; } + async function fetchManagedAliasEmail(state, options = {}) { + throwIfStopped(); + const provider = String(options.mailProvider || state?.mailProvider || '').trim().toLowerCase(); + const mergedState = { + ...(state || {}), + mailProvider: provider, + }; + if (options.gmailBaseEmail !== undefined) { + mergedState.gmailBaseEmail = String(options.gmailBaseEmail || '').trim(); + } + if (options.mail2925BaseEmail !== undefined) { + mergedState.mail2925BaseEmail = String(options.mail2925BaseEmail || '').trim(); + } + + const email = buildGeneratedAliasEmail(mergedState); + await setEmailState(email); + await addLog(`${provider === 'gmail' ? 'Gmail +tag' : '2925'}:已生成 ${email}`, 'ok'); + return email; + } + async function fetchGeneratedEmail(state, options = {}) { const currentState = state || await getState(); + const provider = String(options.mailProvider || currentState.mailProvider || '').trim().toLowerCase(); + if (isGeneratedAliasProvider?.(provider)) { + return fetchManagedAliasEmail(currentState, options); + } const generator = normalizeEmailGenerator(options.generator ?? currentState.emailGenerator); if (generator === 'custom') { throw new Error('当前邮箱生成方式为自定义邮箱,请直接填写注册邮箱。'); diff --git a/background/signup-flow-helpers.js b/background/signup-flow-helpers.js index 9a82c40..faa3ce3 100644 --- a/background/signup-flow-helpers.js +++ b/background/signup-flow-helpers.js @@ -9,6 +9,7 @@ ensureHotmailAccountForFlow, ensureLuckmailPurchaseForFlow, isGeneratedAliasProvider, + isReusableGeneratedAliasEmail, isHotmailProvider, isLuckmailProvider, isSignupEmailVerificationPageUrl, @@ -162,7 +163,9 @@ const purchase = await ensureLuckmailPurchaseForFlow({ allowReuse: true }); resolvedEmail = purchase.email_address; } else if (isGeneratedAliasProvider(state)) { - resolvedEmail = buildGeneratedAliasEmail(state); + if (!isReusableGeneratedAliasEmail?.(state, resolvedEmail)) { + resolvedEmail = buildGeneratedAliasEmail(state); + } } if (!resolvedEmail) { diff --git a/managed-alias-utils.js b/managed-alias-utils.js new file mode 100644 index 0000000..5213097 --- /dev/null +++ b/managed-alias-utils.js @@ -0,0 +1,146 @@ +(function attachManagedAliasUtils(root, factory) { + root.MultiPageManagedAliasUtils = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createManagedAliasUtilsModule() { + const GMAIL_PROVIDER = 'gmail'; + const MAIL_2925_PROVIDER = '2925'; + + const PROVIDER_CONFIGS = { + [GMAIL_PROVIDER]: { + baseLabel: 'Gmail 原邮箱', + basePlaceholder: '例如 yourname@gmail.com', + label: 'Gmail +tag 邮箱', + parseBaseEmail(rawValue = '') { + const value = String(rawValue || '').trim().toLowerCase(); + const match = value.match(/^([^@\s+]+)@((?:gmail|googlemail)\.com)$/i); + if (!match) return null; + return { + localPart: match[1], + domain: match[2].toLowerCase(), + }; + }, + matchesProviderDomain(domain = '') { + return /^(?:gmail|googlemail)\.com$/i.test(String(domain || '').trim()); + }, + matchesAliasLocalPart(baseLocalPart = '', candidateLocalPart = '') { + return String(candidateLocalPart || '').split('+')[0] === String(baseLocalPart || ''); + }, + buildAlias(parsedBaseEmail, tag) { + return `${parsedBaseEmail.localPart}+${tag}@${parsedBaseEmail.domain}`; + }, + generationHint: '先填写 Gmail 原邮箱后点“生成”,也可以直接手动填写完整的 Gmail 邮箱。', + registrationPlaceholder: '点击生成 Gmail +tag 邮箱,或手动填写完整邮箱', + }, + [MAIL_2925_PROVIDER]: { + baseLabel: '2925 基邮箱', + basePlaceholder: '例如 yourname@2925.com', + label: '2925 邮箱', + parseBaseEmail(rawValue = '') { + const value = String(rawValue || '').trim().toLowerCase(); + const match = value.match(/^([^@\s+]+)@(2925\.com)$/i); + if (!match) return null; + return { + localPart: match[1], + domain: match[2].toLowerCase(), + }; + }, + matchesProviderDomain(domain = '') { + return String(domain || '').trim().toLowerCase() === '2925.com'; + }, + matchesAliasLocalPart(baseLocalPart = '', candidateLocalPart = '') { + const normalizedBaseLocalPart = String(baseLocalPart || ''); + const normalizedCandidateLocalPart = String(candidateLocalPart || ''); + return normalizedCandidateLocalPart === normalizedBaseLocalPart + || normalizedCandidateLocalPart.startsWith(normalizedBaseLocalPart); + }, + buildAlias(parsedBaseEmail, tag) { + return `${parsedBaseEmail.localPart}${tag}@${parsedBaseEmail.domain}`; + }, + generationHint: '先填写 2925 基邮箱后点“生成”,也可以直接手动填写完整的 2925 邮箱。', + registrationPlaceholder: '点击生成 2925 邮箱,或手动填写完整邮箱', + }, + }; + + function getManagedAliasProviderConfig(provider = '') { + return PROVIDER_CONFIGS[String(provider || '').trim().toLowerCase()] || null; + } + + function isManagedAliasProvider(provider = '') { + return Boolean(getManagedAliasProviderConfig(provider)); + } + + function parseEmailParts(rawValue = '') { + const value = String(rawValue || '').trim().toLowerCase(); + const match = value.match(/^([^@\s]+)@([^@\s]+\.[^@\s]+)$/); + if (!match) return null; + return { + localPart: match[1], + domain: match[2], + }; + } + + function parseManagedAliasBaseEmail(rawValue, provider = '') { + const config = getManagedAliasProviderConfig(provider); + return config?.parseBaseEmail(rawValue) || null; + } + + function isManagedAliasEmail(value, provider = '', baseEmail = '') { + const config = getManagedAliasProviderConfig(provider); + if (!config) return false; + + const parsedEmail = parseEmailParts(value); + if (!parsedEmail || !config.matchesProviderDomain(parsedEmail.domain)) { + return false; + } + + const parsedBaseEmail = parseManagedAliasBaseEmail(baseEmail, provider); + if (!parsedBaseEmail) { + return true; + } + + return parsedEmail.domain === parsedBaseEmail.domain + && config.matchesAliasLocalPart(parsedBaseEmail.localPart, parsedEmail.localPart); + } + + function buildManagedAliasEmail(provider = '', baseEmail = '', tag = '') { + const config = getManagedAliasProviderConfig(provider); + if (!config) { + throw new Error(`Unsupported managed alias provider: ${provider}`); + } + + const parsedBaseEmail = parseManagedAliasBaseEmail(baseEmail, provider); + if (!parsedBaseEmail) { + throw new Error(`${config.baseLabel}格式不正确`); + } + + const normalizedTag = String(tag || '').trim(); + if (!normalizedTag) { + throw new Error(`${config.label}生成标签为空`); + } + + return config.buildAlias(parsedBaseEmail, normalizedTag); + } + + function getManagedAliasProviderUiCopy(provider = '') { + const config = getManagedAliasProviderConfig(provider); + if (!config) return null; + return { + baseLabel: config.baseLabel, + basePlaceholder: config.basePlaceholder, + buttonLabel: '生成', + successVerb: '生成', + label: config.label, + placeholder: config.registrationPlaceholder, + hint: config.generationHint, + }; + } + + return { + buildManagedAliasEmail, + getManagedAliasProviderConfig, + getManagedAliasProviderUiCopy, + isManagedAliasEmail, + isManagedAliasProvider, + parseEmailParts, + parseManagedAliasBaseEmail, + }; +}); diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 878c67f..32bd058 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -225,8 +225,8 @@