Corrigindo Popup e Menu

This commit is contained in:
Lucas Santana 2024-12-17 20:05:22 -03:00
parent 439e26b0a5
commit 4d2609c646
16 changed files with 1014 additions and 373 deletions

BIN
.DS_Store vendored

Binary file not shown.

66
.dockerignore Normal file
View File

@ -0,0 +1,66 @@
# Controle de versão
.git
.gitignore
.gitattributes
.github/
# Dependências
node_modules/
package-lock.json
yarn.lock
# Arquivos de build
dist/
build/
*.zip
*.crx
*.pem
# Arquivos de ambiente e configuração
.env*
.chrome/
.vscode/
.idea/
*.config.js
*.config.ts
# Arquivos de documentação
README.md
CHANGELOG.md
LICENSE
docs/
# Arquivos de teste
__tests__/
test/
tests/
coverage/
.nyc_output/
# Arquivos do sistema
.DS_Store
Thumbs.db
desktop.ini
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Arquivos temporários
.tmp/
.temp/
.cache/
*.swp
*.swo
# Arquivos específicos da extensão que devem ser baixados/construídos
lib/bootstrap.bundle.min.js
lib/tinymce/
# Outros
.history/
*.bak
*.backup
*.old

55
.gitignore vendored Normal file
View File

@ -0,0 +1,55 @@
# Dependências
node_modules/
package-lock.json
yarn.lock
# Arquivos de build
dist/
build/
*.zip
# Arquivos de ambiente
.env
.env.local
.env.*.local
# Arquivos do sistema
.DS_Store
Thumbs.db
desktop.ini
# Arquivos de IDE/Editor
.idea/
.vscode/
*.swp
*.swo
*.sublime-workspace
*.sublime-project
# Arquivos de log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
debug.log
# Arquivos temporários
*.tmp
*.temp
.cache/
# Arquivos de teste
coverage/
.nyc_output/
# Arquivos específicos da extensão
lib/bootstrap.bundle.min.js
lib/tinymce/
# Arquivos de configuração local
.chrome/
# Outros
*.pem
*.crx
.history/
.env.*

View File

@ -0,0 +1,30 @@
# Launchr Chrome Extension
Extensão do Chrome para salvar posts do LinkedIn diretamente no Launchr. Permite capturar conteúdo e gerenciar posts de forma eficiente.
## Funcionalidades
- 🔒 Login seguro com sua conta Launchr
- 💾 Salvar posts do LinkedIn com um clique
- 📝 Criar novos posts diretamente da extensão
- 🏢 Gerenciar múltiplos negócios
- 📋 Captura automática de:
- Autor do post
- Link do perfil
- Conteúdo do post
- URL do post
## Instalação
### Para Usuários
1. Baixe a última versão da extensão
2. Abra o Chrome e vá para `chrome://extensions/`
3. Ative o "Modo do desenvolvedor" no canto superior direito
4. Arraste o arquivo .zip baixado para a página ou clique em "Carregar sem compactação" e selecione a pasta da extensão
### Para Desenvolvedores
1. Clone o repositório:
```bash
git clone https://github.com/seu

View File

@ -177,5 +177,56 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
sendResponse({ success: true });
});
return true;
} else if (request.action === 'getUserData') {
fetch(`${API_URL}/user/data`, {
headers: {
'Authorization': authToken
}
})
.then(response => response.json())
.then(data => {
sendResponse({ success: true, data });
})
.catch(error => {
console.error('Erro ao buscar dados do usuário:', error);
sendResponse({ success: false, error: 'Erro ao buscar dados do usuário' });
});
return true;
} else if (request.action === 'saveSwipeFile') {
fetch(`${API_URL}/novo_swipe_file`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': authToken
},
body: JSON.stringify(request.data)
})
.then(response => {
if (!response.ok) throw new Error('Erro na requisição');
return response.json();
})
.then(data => {
sendResponse({ success: true, data });
})
.catch(error => {
console.error('Erro ao salvar swipe file:', error);
sendResponse({ success: false, error: error.message });
});
return true;
} else if (request.action === 'openPopupWithData') {
// Armazenar os dados temporariamente
chrome.storage.local.set({ currentPostData: request.data }, () => {
// Criar uma nova janela popup
chrome.windows.create({
url: 'popup.html',
type: 'popup',
width: 400,
height: 600,
focused: true
});
});
return true;
}
});

View File

@ -1,60 +1,45 @@
/* Estilos do botão */
.launchr-save-button {
display: inline-flex;
background: none;
border: none;
padding: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
transition: all 0.2s;
margin-right: 8px;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
background-color: #4F46E5;
color: white;
border: none;
margin-left: 8px;
min-width: 150px;
}
.launchr-save-button:hover:not(:disabled) {
background-color: #4338CA;
.launchr-save-button:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.launchr-save-button:disabled {
.launchr-icon {
width: 20px;
height: 20px;
border-radius: 2px;
}
/* Posicionamento no menu de controle */
.feed-shared-control-menu .launchr-save-button,
.feed-shared-update-v2__control-menu .launchr-save-button {
position: relative;
z-index: 2;
}
.launchr-save-button.saving {
opacity: 0.7;
cursor: not-allowed;
cursor: wait;
}
.launchr-save-button.loading {
background-color: #6B7280;
.launchr-save-button.saved {
background-color: rgba(10, 102, 194, 0.1);
}
.launchr-save-button.success {
background-color: #10B981;
}
.launchr-save-button.error {
background-color: #EF4444;
}
.launchr-save-button.auth {
background-color: #F59E0B;
}
/* Animação de loading */
.launchr-save-button.loading::after {
content: '';
width: 16px;
height: 16px;
margin-left: 8px;
border: 2px solid #fff;
border-radius: 50%;
border-top-color: transparent;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
/* Ajustes para o container do botão */
.feed-shared-control-menu .display-flex,
.feed-shared-update-v2__control-menu .display-flex {
align-items: center;
}

View File

@ -1,74 +1,109 @@
// Função para extrair dados do post atual
function extractPostData() {
// Selecionar o post atual
const post = document.querySelector('.occludable-update');
if (!post) {
console.error('Post não encontrado.');
return null;
}
// Extrair informações do post
const contentElement = post.querySelector('.feed-shared-update-v2__description, .break-words, [data-test-id="feed-update-message"]');
const authorElement = post.querySelector('.feed-shared-actor__name, .update-components-actor__name');
const dateElement = post.querySelector('span.feed-shared-actor__sub-description span[aria-hidden="true"], span.update-components-actor__sub-description time');
const postContent = contentElement ? contentElement.innerText.trim() : '';
const postAuthor = authorElement ? authorElement.innerText.trim() : '';
// Obter o link do perfil do autor
const authorProfileLinkElement = authorElement ? authorElement.closest('a') : null;
const baseUrl = 'https://www.linkedin.com';
const authorProfileLink = authorProfileLinkElement ? new URL(authorProfileLinkElement.getAttribute('href'), baseUrl).href : '';
// Obter a data do post
const postDate = dateElement ? dateElement.innerText.trim() : '';
// Obter o link do post
const postLinkElement = post.querySelector('a[href*="/feed/update/"], a[data-control-name="share_link"], a[href*="/posts/"]');
const postLink = postLinkElement ? postLinkElement.href : window.location.href;
const postData = {
content: postContent,
author: postAuthor,
authorProfileLink: authorProfileLink,
date: postDate,
postLink: postLink
};
return postData;
function createSaveButton() {
const button = document.createElement('button');
button.className = 'launchr-save-button';
button.title = 'Salvar no Launchr'; // Tooltip ao passar o mouse
// Adicionar ícone do Launchr
const icon = document.createElement('img');
icon.src = chrome.runtime.getURL('images/logo.png');
icon.alt = 'Launchr';
icon.className = 'launchr-icon';
button.appendChild(icon);
return button;
}
// Adicionar um botão na página para salvar o post
function addSavePostButton() {
const button = document.createElement('button');
button.innerText = 'Salvar Post';
button.id = 'savePostButton';
button.style.position = 'fixed';
button.style.top = '10px';
button.style.right = '10px';
button.style.zIndex = '9999';
function extractPostData(postElement) {
// Encontrar o autor
const authorElement = postElement.querySelector('.feed-shared-actor__name, .update-components-actor__name');
const author = authorElement?.textContent?.trim() || '';
button.addEventListener('click', () => {
const postData = extractPostData();
// Encontrar o link do perfil do autor
const authorProfileLink = authorElement?.closest('a')?.href || '';
if (postData) {
// Enviar os dados do post para o background script
chrome.runtime.sendMessage({ action: 'storePostData', data: postData }, (response) => {
if (response && response.success) {
console.log('Dados do post enviados para o background script.');
alert('Dados do post prontos para serem salvos. Clique no ícone da extensão para prosseguir.');
} else {
console.error('Erro ao enviar dados do post:', response.error);
}
});
} else {
alert('Não foi possível extrair os dados do post.');
// Encontrar o link do post
const postLinkElement = postElement.querySelector('a[href*="/feed/update/"], a[data-control-name="share_link"]');
const postLink = postLinkElement?.href || window.location.href;
// Encontrar o conteúdo do post
const contentElement = postElement.querySelector('.feed-shared-update-v2__description-wrapper, .feed-shared-update-v2__description, .feed-shared-text');
const content = contentElement?.textContent?.trim() || '';
return {
author,
authorProfileLink,
postLink,
content
};
}
function addSaveButtonToPost(postElement) {
// Verificar se o botão já existe
if (postElement.querySelector('.launchr-save-button')) {
return;
}
});
document.body.appendChild(button);
// Encontrar o menu de controle do post
const controlMenu = postElement.querySelector('.feed-shared-control-menu, .feed-shared-update-v2__control-menu');
if (!controlMenu) return;
// Criar container para o botão (para manter o alinhamento)
const buttonContainer = document.createElement('div');
buttonContainer.className = 'display-flex align-items-center';
// Criar e adicionar o botão
const saveButton = createSaveButton();
buttonContainer.appendChild(saveButton);
// Inserir antes do menu de controle existente
controlMenu.insertBefore(buttonContainer, controlMenu.firstChild);
// Adicionar evento de clique
saveButton.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
try {
saveButton.classList.add('saving');
const postData = extractPostData(postElement);
// Armazenar dados do post temporariamente
chrome.storage.local.set({ currentPostData: postData }, () => {
// Abrir o popup usando chrome.action
chrome.runtime.sendMessage({
action: 'openPopupWithData',
data: postData
});
});
saveButton.classList.remove('saving');
} catch (error) {
console.error('Erro ao processar post:', error);
saveButton.classList.remove('saving');
alert('Erro ao processar o post. Por favor, tente novamente.');
}
});
}
// Chamar a função para adicionar o botão
addSavePostButton();
// Observer para detectar novos posts
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node instanceof Element) {
// Procurar por posts no elemento adicionado
const posts = node.querySelectorAll('.feed-shared-update-v2, .occludable-update');
posts.forEach(addSaveButtonToPost);
}
});
});
});
// Iniciar observação
observer.observe(document.body, {
childList: true,
subtree: true
});
// Adicionar botões aos posts existentes
document.querySelectorAll('.feed-shared-update-v2, .occludable-update').forEach(addSaveButtonToPost);

6
lib/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
{
"manifest_version": 2,
"manifest_version": 3,
"name": "Sua Extensão",
"version": "1.0",
"description": "Extensão para salvar posts do LinkedIn e gerar mensagens personalizadas.",
@ -7,15 +7,19 @@
"activeTab",
"storage",
"contextMenus",
"tabs",
"scripting",
"windows"
],
"host_permissions": [
"https://launchr.com.br/*",
"https://api.openai.com/*"
"https://api.openai.com/*",
"*://www.linkedin.com/*"
],
"background": {
"scripts": ["background.js"],
"persistent": false
"service_worker": "background.js",
"type": "module"
},
"browser_action": {
"action": {
"default_popup": "popup.html",
"default_title": "Minha Extensão",
"default_icon": {
@ -27,6 +31,7 @@
"content_scripts": [
{
"matches": ["*://www.linkedin.com/*"],
"css": ["content.css"],
"js": ["content.js"]
}
],
@ -34,5 +39,12 @@
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
}
},
"web_accessible_resources": [{
"resources": [
"images/*",
"lib/*"
],
"matches": ["<all_urls>"]
}]
}

View File

@ -2,52 +2,144 @@
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Salvar Post</title>
<link rel="stylesheet" href="styles.css">
<title>Launchr</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head>
<body>
<div class="container">
<h2>Salvar Post</h2>
<form id="postForm">
<label for="author">Autor:</label>
<input type="text" id="author" name="author" readonly>
<body class="bg-light" style="width: 400px; min-height: 500px;">
<!-- Área de Login -->
<div id="loginSection" class="container p-4">
<!-- Logo -->
<div class="d-flex align-items-center mb-4">
<img src="./images/logo.png" alt="Launchr" height="32" class="me-2">
<small class="text-muted">Version 1.3.6</small>
</div>
<label for="authorProfileLink">Link do Perfil do Autor:</label>
<input type="text" id="authorProfileLink" name="authorProfileLink" readonly>
<h1 class="display-6 text-primary mb-4">Let's get started</h1>
<form id="loginForm">
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-envelope"></i>
</span>
<input type="email" class="form-control" id="email" required>
</div>
</div>
<label for="postLink">Link do Post:</label>
<input type="text" id="postLink" name="postLink" readonly>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-lock"></i>
</span>
<input type="password" class="form-control" id="password" required>
<button class="btn btn-outline-secondary" type="button" id="togglePasswordBtn">
<i class="bi bi-eye"></i>
</button>
</div>
<a href="#" class="text-decoration-none small d-block mt-2">Forgot password?</a>
</div>
<label for="date">Data:</label>
<input type="text" id="date" name="date" readonly>
<button type="submit" class="btn btn-primary w-100 py-2 mt-3">LOGIN</button>
<label for="content">Conteúdo:</label>
<textarea id="content" name="content" rows="4" readonly></textarea>
<label for="category">Categoria:</label>
<select id="category" name="category">
<option value="">Selecione uma categoria</option>
<option value="Marketing">Marketing</option>
<option value="Vendas">Vendas</option>
<option value="Tecnologia">Tecnologia</option>
<!-- Adicione outras categorias conforme necessário -->
</select>
<button type="button" id="saveButton">Salvar</button>
<div class="text-center mt-4">
<span class="text-muted">New here? </span>
<a href="#" class="text-decoration-none">Create your account</a>
</div>
</form>
</div>
<!-- Popup de confirmação -->
<div id="successPopup" class="popup" style="display: none;">
<div class="popup-content">
<p>O post foi enviado com sucesso!</p>
<div class="popup-buttons">
<button id="cancelButton">Fechar</button>
<button id="viewButton">Visualizar Swipe File</button>
<!-- Área Logada -->
<div id="loggedInSection" class="d-none">
<!-- Navbar -->
<nav class="navbar navbar-expand navbar-light bg-white border-bottom px-3">
<div class="container-fluid px-0">
<!-- Logo -->
<a class="navbar-brand" href="#">
<img src="./images/logo.png" alt="Launchr" height="32">
</a>
<!-- Links centralizados -->
<div class="navbar-nav mx-auto">
<button id="navSwipeFile" class="nav-link active fw-medium border-0 bg-transparent px-4">
SWIPE FILE
</button>
<button id="navNewPost" class="nav-link fw-medium border-0 bg-transparent px-4">
NOVO POST
</button>
</div>
<!-- Avatar do usuário com dropdown -->
<div class="navbar-nav">
<div class="nav-item dropdown">
<div class="rounded-circle bg-primary d-flex align-items-center justify-content-center"
style="width: 40px; height: 40px; cursor: pointer;"
id="userDropdown"
data-bs-toggle="dropdown"
aria-expanded="false">
<span class="text-white fw-medium">IL</span>
</div>
<ul class="dropdown-menu dropdown-menu-end py-2" style="min-width: 200px;" aria-labelledby="userDropdown">
<!-- Seleção de Negócio -->
<li class="px-3 mb-2">
<small class="text-muted d-block mb-1">Selecione o Negócio</small>
<select id="negociosSelect" class="form-select form-select-sm">
<!-- Opções serão preenchidas via JavaScript -->
</select>
</li>
<li><hr class="dropdown-divider"></li>
<li><button class="dropdown-item text-danger" id="logoutButton">Sair</button></li>
</ul>
</div>
</div>
</div>
</nav>
<!-- Conteúdo Principal -->
<div class="container p-4">
<!-- Seção Swipe File -->
<div id="swipeFileSection">
<form id="postForm" class="bg-white p-4 rounded shadow-sm">
<div class="mb-3">
<label for="author" class="form-label">Autor</label>
<input type="text" class="form-control" id="author" readonly>
</div>
<div class="mb-3">
<label for="content" class="form-label">Conteúdo</label>
<textarea class="form-control" id="content" rows="4"></textarea>
</div>
<button type="button" id="saveSwipeFileButton" class="btn btn-primary w-100">
Enviar Post
</button>
</form>
</div>
<!-- Seção Novo Post -->
<div id="newPostSection" class="d-none">
<form id="newPostForm" class="bg-white p-4 rounded shadow-sm">
<div class="mb-3">
<label for="postTitle" class="form-label">Título do Post</label>
<input type="text" class="form-control" id="postTitle">
</div>
<div class="mb-3">
<label for="postEditor" class="form-label">Conteúdo do Post</label>
<textarea id="postEditor" class="form-control" rows="6"></textarea>
</div>
<button type="button" id="savePostButton" class="btn btn-primary w-100">
Publicar Post
</button>
</form>
</div>
</div>
</div>
<script src="lib/bootstrap.bundle.min.js"></script>
<script src="popup.js"></script>
</body>
</html>

349
popup.js
View File

@ -1,130 +1,241 @@
let editorLoaded = false;
async function loadEditor() {
if (editorLoaded) return;
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'tinymce/tinymce.min.js';
script.onload = async () => {
const { initTinyMCE } = await import('./src/editor.js');
initTinyMCE();
editorLoaded = true;
resolve();
};
script.onerror = reject;
document.head.appendChild(script);
});
}
document.addEventListener('DOMContentLoaded', () => {
// Recuperar os dados do post do background script
chrome.runtime.sendMessage({ action: 'getPostData' }, (response) => {
if (response && response.data) {
const postData = response.data;
// Preencher os campos com os dados do post
document.getElementById('author').value = postData.author || '';
document.getElementById('authorProfileLink').value = postData.authorProfileLink || '';
document.getElementById('postLink').value = postData.postLink || '';
document.getElementById('date').value = postData.date || '';
document.getElementById('content').value = postData.content || '';
} else {
console.log('Nenhum dado do post disponível.');
}
});
// Referência ao popup e aos botões
const successPopup = document.getElementById('successPopup');
const cancelButton = document.getElementById('cancelButton');
const viewButton = document.getElementById('viewButton');
let negocios_nome = '';
let negocios_id = '';
// Recuperar negocios_nome e negocios_id do storage
chrome.storage.local.get(['negocios_nome', 'negocios_id'], (result) => {
if (result.negocios_nome && result.negocios_id) {
negocios_nome = result.negocios_nome;
negocios_id = result.negocios_id;
} else {
console.error('Não foi possível obter as informações do negócio.');
}
});
// Função para exibir o popup de sucesso
function showSuccessPopup() {
successPopup.style.display = 'flex';
// Inicializar o dropdown do usuário
const userDropdown = document.getElementById('userDropdown');
if (userDropdown) {
new bootstrap.Dropdown(userDropdown);
}
// Evento para o botão de cancelar e fechar o popup
cancelButton.addEventListener('click', () => {
successPopup.style.display = 'none';
// Fechar o popup principal
window.close();
// Inicializar todos os dropdowns do Bootstrap
const dropdownElementList = [].slice.call(document.querySelectorAll('[data-bs-toggle="dropdown"]'));
const dropdownList = dropdownElementList.map(function (dropdownToggleEl) {
return new bootstrap.Dropdown(dropdownToggleEl);
});
// Evento para o botão de visualizar o Swipe File
viewButton.addEventListener('click', () => {
if (negocios_nome && negocios_id) {
// Construir o link para visualizar o Swipe File
const baseUrl = 'https://launchr.com.br';
const encodedNegociosNome = encodeURIComponent(negocios_nome);
const swipeFileUrl = `${baseUrl}/posts/${encodedNegociosNome}-${negocios_id}?tab=Swipe%20File`;
// Abrir o link em uma nova aba
chrome.tabs.create({ url: swipeFileUrl }, () => {
// Fechar o popup após abrir a nova aba
window.close();
const loginSection = document.getElementById('loginSection');
const loggedInSection = document.getElementById('loggedInSection');
// Verificar estado de autenticação
chrome.storage.local.get(['authToken', 'negocios_id', 'negocios_nome'], (result) => {
if (result.authToken) {
showLoggedInSection();
} else {
showLoginSection();
}
});
// Funções de navegação
function setActiveNav(activeId) {
['navSwipeFile', 'navNewPost'].forEach(id => {
const element = document.getElementById(id);
if (element) {
if (id === activeId) {
element.classList.add('active');
element.style.borderBottom = '2px solid var(--bs-primary)';
} else {
element.classList.remove('active');
element.style.borderBottom = 'none';
}
}
});
} else {
alert('Informações do negócio não disponíveis.');
}
// Mostrar/esconder seções
const swipeFileSection = document.getElementById('swipeFileSection');
const newPostSection = document.getElementById('newPostSection');
if (activeId === 'navSwipeFile') {
swipeFileSection.classList.remove('d-none');
newPostSection.classList.add('d-none');
} else {
swipeFileSection.classList.add('d-none');
newPostSection.classList.remove('d-none');
}
}
// Event listeners do menu
document.getElementById('navSwipeFile')?.addEventListener('click', () => {
setActiveNav('navSwipeFile');
});
// Lidar com o clique no botão de salvar
const saveButton = document.getElementById('saveButton');
if (saveButton) {
saveButton.addEventListener('click', () => {
try {
// Obter dados atualizados do formulário
const updatedData = {
author: document.getElementById('author')?.value || '',
authorProfileLink: document.getElementById('authorProfileLink')?.value || '',
postLink: document.getElementById('postLink')?.value || '',
date: document.getElementById('date')?.value || '',
content: document.getElementById('content')?.value || '',
category: document.getElementById('category')?.value || ''
};
if (!updatedData.category) {
alert('Por favor, selecione uma categoria.');
document.getElementById('navNewPost')?.addEventListener('click', () => {
setActiveNav('navNewPost');
});
// Toggle de senha
const togglePasswordBtn = document.getElementById('togglePasswordBtn');
togglePasswordBtn?.addEventListener('click', () => {
const passwordInput = document.getElementById('password');
const type = passwordInput.type === 'password' ? 'text' : 'password';
passwordInput.type = type;
const icon = togglePasswordBtn.querySelector('i');
icon.classList.toggle('bi-eye');
icon.classList.toggle('bi-eye-slash');
});
function showLoginSection() {
loginSection.classList.remove('d-none');
loggedInSection.classList.add('d-none');
}
function showLoggedInSection() {
loginSection.classList.add('d-none');
loggedInSection.classList.remove('d-none');
setActiveNav('navSwipeFile'); // Ativa a aba SWIPE FILE por padrão
}
// Gerenciar novo post
document.getElementById('savePostButton')?.addEventListener('click', () => {
const title = document.getElementById('postTitle').value;
const content = tinymce.get('postEditor').getContent();
if (!title || !content) {
alert('Por favor, preencha todos os campos');
return;
}
// Desabilitar o botão para evitar múltiplos cliques
saveButton.disabled = true;
saveButton.innerText = 'Salvando...';
// Enviar dados para o background script para salvar
chrome.runtime.sendMessage({ action: 'savePost', data: updatedData }, (response) => {
if (response && response.success) {
// Exibir o popup de sucesso
showSuccessPopup();
} else {
// Exibir mensagem de erro
alert(response.error || 'Houve um erro ao enviar o post. Por favor, tente novamente.');
// Reativar o botão de salvar
saveButton.disabled = false;
saveButton.innerText = 'Salvar';
}
});
} catch (error) {
console.error('Erro ao preparar os dados para salvar:', error);
alert('Ocorreu um erro ao preparar os dados. Por favor, tente novamente.');
// Reativar o botão de salvar
saveButton.disabled = false;
saveButton.innerText = 'Salvar';
}
});
} else {
console.error('Elemento saveButton não encontrado.');
chrome.runtime.sendMessage({
action: 'createPost',
data: { title, content }
}, (response) => {
if (response.success) {
alert('Post criado com sucesso!');
document.getElementById('postTitle').value = '';
tinymce.get('postEditor').setContent('');
} else {
alert(response.error || 'Erro ao criar post');
}
});
});
// Carregar dados do post
function loadPostData() {
chrome.runtime.sendMessage({ action: 'getPostData' }, (response) => {
try {
if (response && response.data) {
const postData = response.data;
const fields = ['author', 'authorProfileLink', 'postLink', 'content'];
fields.forEach(field => {
const element = document.getElementById(field);
if (element) element.value = postData[field] || '';
});
}
} catch (error) {
console.error('Erro ao carregar dados:', error);
}
});
}
// Lidar com o clique no botão de logout (se aplicável)
// Gerenciar formulário de login
const loginForm = document.getElementById('loginForm');
loginForm?.addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
chrome.runtime.sendMessage(
{
action: 'login',
data: { email, password }
},
(response) => {
if (response.success) {
showLoggedInSection();
loadPostData();
} else {
alert(response.error || 'Erro ao fazer login');
}
}
);
});
// Gerenciar envio do post
document.getElementById('saveSwipeFileButton')?.addEventListener('click', async () => {
const content = document.getElementById('content').value;
// Buscar ID do negócio do storage
chrome.storage.local.get(['negocios_id'], async (result) => {
if (!result.negocios_id) {
alert('Erro ao identificar o negócio.');
return;
}
try {
const response = await chrome.runtime.sendMessage({
action: 'saveSwipeFile',
data: {
negocio: result.negocios_id,
conteudo: content
}
});
if (response.success) {
alert('Post salvo com sucesso!');
window.close();
} else {
throw new Error(response.error || 'Erro ao salvar o post');
}
} catch (error) {
console.error('Erro ao salvar:', error);
alert('Erro ao salvar o post. Por favor, tente novamente.');
}
});
});
// Gerenciar logout
const logoutButton = document.getElementById('logoutButton');
if (logoutButton) {
logoutButton.addEventListener('click', () => {
if (confirm('Você realmente deseja fazer logout?')) {
chrome.runtime.sendMessage({ action: 'logout' }, (response) => {
if (response && response.success) {
window.close();
} else {
alert('Erro ao realizar logout.');
logoutButton?.addEventListener('click', () => {
chrome.runtime.sendMessage({ action: 'logout' }, (response) => {
if (response.success) {
showLoginSection();
}
});
});
});
// Gerenciar salvamento do post
const saveButton = document.getElementById('saveButton');
saveButton?.addEventListener('click', () => {
const data = {
author: document.getElementById('author').value,
authorProfileLink: document.getElementById('authorProfileLink').value,
postLink: document.getElementById('postLink').value,
content: document.getElementById('content').value
};
chrome.runtime.sendMessage({ action: 'savePost', data }, (response) => {
if (response.success) {
alert('Post salvo com sucesso!');
} else {
alert(response.error || 'Erro ao salvar o post');
}
});
});
// Carregar dados do post atual
chrome.storage.local.get(['currentPostData', 'negocios_id'], (result) => {
if (result.currentPostData) {
document.getElementById('author').value = result.currentPostData.author || '';
document.getElementById('content').value = result.currentPostData.content || '';
}
});
}
});
});
});

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}

16
src/editor.js Normal file
View File

@ -0,0 +1,16 @@
export function initTinyMCE() {
tinymce.init({
selector: '#postEditor',
height: 300,
menubar: false,
plugins: [
'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
'insertdatetime', 'media', 'table', 'help', 'wordcount'
],
toolbar: 'undo redo | blocks | ' +
'bold italic backcolor | alignleft aligncenter ' +
'alignright alignjustify | bullist numlist outdent indent | ' +
'removeformat | help'
});
}

View File

@ -1,103 +1,252 @@
/* Estilos gerais */
:root {
--primary: #4F46E5;
--primary-hover: #4338CA;
--gray-900: #111827;
--gray-800: #1F2937;
--gray-700: #374151;
--gray-600: #4B5563;
--gray-500: #6B7280;
--gray-400: #9CA3AF;
--gray-300: #D1D5DB;
--gray-200: #E5E7EB;
--gray-100: #F3F4F6;
--gray-50: #F9FAFB;
--white: #FFFFFF;
--error: #EF4444;
--success: #10B981;
}
/* Reset e estilos base */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
width: 400px;
min-height: 500px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--white);
color: var(--gray-900);
}
/* Layout */
.container {
padding: 2rem;
}
.header {
display: flex;
align-items: center;
margin-bottom: 2rem;
}
.logo {
height: 2rem;
width: auto;
}
.version {
margin-left: 0.5rem;
font-size: 0.875rem;
color: var(--gray-500);
}
.title {
font-size: 1.875rem;
font-weight: 700;
color: var(--primary);
margin-bottom: 2rem;
}
/* Formulários */
.form-group {
margin-bottom: 1.5rem;
}
.label {
display: block;
font-size: 1.125rem;
font-weight: 500;
color: var(--gray-700);
margin-bottom: 0.5rem;
}
.input-with-icon {
position: relative;
}
.input {
width: 100%;
padding: 0.625rem 1rem;
padding-left: 2.5rem;
border: 1px solid var(--gray-300);
border-radius: 0.5rem;
font-size: 1rem;
transition: all 0.2s;
}
.input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1);
}
.input-icon {
position: absolute;
left: 0.75rem;
top: 50%;
transform: translateY(-50%);
color: var(--gray-400);
pointer-events: none;
}
.icon {
width: 1.25rem;
height: 1.25rem;
}
/* Botões */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.625rem 1rem;
border-radius: 0.5rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border: none;
}
.btn-primary {
width: 100%;
background-color: var(--primary);
color: var(--white);
font-size: 1.125rem;
font-weight: 600;
padding: 0.75rem;
margin-top: 1rem;
}
.btn-primary:hover {
background-color: var(--primary-hover);
}
.btn-primary:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.5);
}
.btn-icon {
position: absolute;
right: 0.75rem;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: var(--gray-400);
cursor: pointer;
padding: 0.25rem;
}
.btn-icon:hover {
color: var(--gray-600);
}
/* Links */
.link {
color: var(--primary);
text-decoration: none;
font-size: 0.875rem;
transition: color 0.2s;
display: inline-block;
margin-top: 0.5rem;
}
.link:hover {
color: var(--primary-hover);
}
/* Footer */
.footer {
text-align: center;
margin-top: 1.5rem;
color: var(--gray-600);
}
/* Navegação */
.nav {
display: flex;
align-items: center;
padding: 1rem;
background-color: var(--white);
border-bottom: 1px solid var(--gray-200);
}
.nav-item {
padding: 0.5rem 1rem;
color: var(--gray-700);
text-decoration: none;
border-bottom: 2px solid transparent;
transition: all 0.2s;
}
.nav-item.active {
color: var(--primary);
border-bottom-color: var(--primary);
}
/* Utilitários */
.hidden {
display: none;
}
.flex {
display: flex;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
/* Estados */
.disabled {
opacity: 0.7;
cursor: not-allowed;
}
/* Área logada */
#loggedInSection {
background-color: var(--gray-50);
}
/* Formulário de post */
.post-form {
background: var(--white);
padding: 1.5rem;
border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
/* Campos somente leitura */
.input[readonly] {
background-color: var(--gray-50);
cursor: not-allowed;
}
/* Responsividade */
@media (max-width: 400px) {
body {
width: 100%;
}
/* Container principal */
.container {
padding: 20px;
width: 300px;
padding: 1rem;
}
/* Títulos */
h2 {
text-align: center;
}
/* Formulários */
form label {
display: block;
margin-top: 10px;
}
form input[type="text"],
form select,
form textarea {
width: 100%;
padding: 5px;
box-sizing: border-box;
}
form button {
margin-top: 15px;
width: 100%;
padding: 10px;
background-color: #0073b1;
color: #fff;
border: none;
cursor: pointer;
}
form button:hover {
background-color: #005582;
}
/* Estilos para o popup */
.popup {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
}
.popup-content {
background-color: #fff;
padding: 20px;
border-radius: 5px;
text-align: center;
width: 80%;
max-width: 300px;
}
.popup-content p {
font-size: 16px;
margin-bottom: 20px;
}
/* Botões do popup */
.popup-buttons {
display: flex;
justify-content: space-around;
margin-top: 20px;
}
.popup-buttons button {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
}
#cancelButton {
background-color: #ccc;
border: none;
border-radius: 5px;
}
#viewButton {
background-color: #0073b1;
color: #fff;
border: none;
border-radius: 5px;
}
#viewButton:hover {
background-color: #005582;
}
}

27
styles/input.css Normal file
View File

@ -0,0 +1,27 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Estilos base personalizados */
@layer base {
input[type="email"],
input[type="password"],
input[type="text"] {
@apply w-full rounded-lg border-gray-300;
}
button {
@apply transition-colors duration-200;
}
}
/* Componentes personalizados */
@layer components {
.input-icon {
@apply absolute inset-y-0 left-3 flex items-center pointer-events-none text-gray-400;
}
.btn-primary {
@apply w-full bg-indigo-600 text-white py-2 px-4 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 text-lg font-semibold;
}
}

View File

@ -1,10 +1,10 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{html,js,ts,jsx,tsx}",
"./*.html"
],
content: ["./**/*.{html,js}"],
theme: {
extend: {},
},
plugins: [
require('@tailwindcss/forms'),
],
}