Corrigindo Popup e Menu
This commit is contained in:
parent
439e26b0a5
commit
4d2609c646
66
.dockerignore
Normal file
66
.dockerignore
Normal 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
55
.gitignore
vendored
Normal 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.*
|
||||||
30
README.md
30
README.md
@ -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
|
||||||
@ -177,5 +177,56 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|||||||
sendResponse({ success: true });
|
sendResponse({ success: true });
|
||||||
});
|
});
|
||||||
return 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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
75
content.css
75
content.css
@ -1,60 +1,45 @@
|
|||||||
/* Estilos do botão */
|
/* Estilos do botão */
|
||||||
.launchr-save-button {
|
.launchr-save-button {
|
||||||
display: inline-flex;
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 8px 16px;
|
transition: all 0.2s;
|
||||||
|
margin-right: 8px;
|
||||||
border-radius: 4px;
|
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) {
|
.launchr-save-button:hover {
|
||||||
background-color: #4338CA;
|
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;
|
opacity: 0.7;
|
||||||
cursor: not-allowed;
|
cursor: wait;
|
||||||
}
|
}
|
||||||
|
|
||||||
.launchr-save-button.loading {
|
.launchr-save-button.saved {
|
||||||
background-color: #6B7280;
|
background-color: rgba(10, 102, 194, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.launchr-save-button.success {
|
/* Ajustes para o container do botão */
|
||||||
background-color: #10B981;
|
.feed-shared-control-menu .display-flex,
|
||||||
}
|
.feed-shared-update-v2__control-menu .display-flex {
|
||||||
|
align-items: center;
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
167
content.js
167
content.js
@ -1,74 +1,109 @@
|
|||||||
// Função para extrair dados do post atual
|
function createSaveButton() {
|
||||||
function extractPostData() {
|
const button = document.createElement('button');
|
||||||
// Selecionar o post atual
|
button.className = 'launchr-save-button';
|
||||||
const post = document.querySelector('.occludable-update');
|
button.title = 'Salvar no Launchr'; // Tooltip ao passar o mouse
|
||||||
|
|
||||||
if (!post) {
|
// Adicionar ícone do Launchr
|
||||||
console.error('Post não encontrado.');
|
const icon = document.createElement('img');
|
||||||
return null;
|
icon.src = chrome.runtime.getURL('images/logo.png');
|
||||||
}
|
icon.alt = 'Launchr';
|
||||||
|
icon.className = 'launchr-icon';
|
||||||
// Extrair informações do post
|
|
||||||
const contentElement = post.querySelector('.feed-shared-update-v2__description, .break-words, [data-test-id="feed-update-message"]');
|
button.appendChild(icon);
|
||||||
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');
|
return button;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adicionar um botão na página para salvar o post
|
function extractPostData(postElement) {
|
||||||
function addSavePostButton() {
|
// Encontrar o autor
|
||||||
const button = document.createElement('button');
|
const authorElement = postElement.querySelector('.feed-shared-actor__name, .update-components-actor__name');
|
||||||
button.innerText = 'Salvar Post';
|
const author = authorElement?.textContent?.trim() || '';
|
||||||
button.id = 'savePostButton';
|
|
||||||
button.style.position = 'fixed';
|
|
||||||
button.style.top = '10px';
|
|
||||||
button.style.right = '10px';
|
|
||||||
button.style.zIndex = '9999';
|
|
||||||
|
|
||||||
button.addEventListener('click', () => {
|
// Encontrar o link do perfil do autor
|
||||||
const postData = extractPostData();
|
const authorProfileLink = authorElement?.closest('a')?.href || '';
|
||||||
|
|
||||||
if (postData) {
|
// Encontrar o link do post
|
||||||
// Enviar os dados do post para o background script
|
const postLinkElement = postElement.querySelector('a[href*="/feed/update/"], a[data-control-name="share_link"]');
|
||||||
chrome.runtime.sendMessage({ action: 'storePostData', data: postData }, (response) => {
|
const postLink = postLinkElement?.href || window.location.href;
|
||||||
if (response && response.success) {
|
|
||||||
console.log('Dados do post enviados para o background script.');
|
// Encontrar o conteúdo do post
|
||||||
alert('Dados do post prontos para serem salvos. Clique no ícone da extensão para prosseguir.');
|
const contentElement = postElement.querySelector('.feed-shared-update-v2__description-wrapper, .feed-shared-update-v2__description, .feed-shared-text');
|
||||||
} else {
|
const content = contentElement?.textContent?.trim() || '';
|
||||||
console.error('Erro ao enviar dados do post:', response.error);
|
|
||||||
}
|
return {
|
||||||
});
|
author,
|
||||||
} else {
|
authorProfileLink,
|
||||||
alert('Não foi possível extrair os dados do post.');
|
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
|
// Observer para detectar novos posts
|
||||||
addSavePostButton();
|
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
6
lib/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 3,
|
||||||
"name": "Sua Extensão",
|
"name": "Sua Extensão",
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"description": "Extensão para salvar posts do LinkedIn e gerar mensagens personalizadas.",
|
"description": "Extensão para salvar posts do LinkedIn e gerar mensagens personalizadas.",
|
||||||
@ -7,15 +7,19 @@
|
|||||||
"activeTab",
|
"activeTab",
|
||||||
"storage",
|
"storage",
|
||||||
"contextMenus",
|
"contextMenus",
|
||||||
"tabs",
|
"scripting",
|
||||||
|
"windows"
|
||||||
|
],
|
||||||
|
"host_permissions": [
|
||||||
"https://launchr.com.br/*",
|
"https://launchr.com.br/*",
|
||||||
"https://api.openai.com/*"
|
"https://api.openai.com/*",
|
||||||
|
"*://www.linkedin.com/*"
|
||||||
],
|
],
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": ["background.js"],
|
"service_worker": "background.js",
|
||||||
"persistent": false
|
"type": "module"
|
||||||
},
|
},
|
||||||
"browser_action": {
|
"action": {
|
||||||
"default_popup": "popup.html",
|
"default_popup": "popup.html",
|
||||||
"default_title": "Minha Extensão",
|
"default_title": "Minha Extensão",
|
||||||
"default_icon": {
|
"default_icon": {
|
||||||
@ -27,6 +31,7 @@
|
|||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"matches": ["*://www.linkedin.com/*"],
|
"matches": ["*://www.linkedin.com/*"],
|
||||||
|
"css": ["content.css"],
|
||||||
"js": ["content.js"]
|
"js": ["content.js"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -34,5 +39,12 @@
|
|||||||
"16": "icon16.png",
|
"16": "icon16.png",
|
||||||
"48": "icon48.png",
|
"48": "icon48.png",
|
||||||
"128": "icon128.png"
|
"128": "icon128.png"
|
||||||
}
|
},
|
||||||
|
"web_accessible_resources": [{
|
||||||
|
"resources": [
|
||||||
|
"images/*",
|
||||||
|
"lib/*"
|
||||||
|
],
|
||||||
|
"matches": ["<all_urls>"]
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
160
popup.html
160
popup.html
@ -2,52 +2,144 @@
|
|||||||
<html lang="pt-BR">
|
<html lang="pt-BR">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Salvar Post</title>
|
<title>Launchr</title>
|
||||||
<link rel="stylesheet" href="styles.css">
|
<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>
|
</head>
|
||||||
<body>
|
<body class="bg-light" style="width: 400px; min-height: 500px;">
|
||||||
<div class="container">
|
<!-- Área de Login -->
|
||||||
<h2>Salvar Post</h2>
|
<div id="loginSection" class="container p-4">
|
||||||
<form id="postForm">
|
<!-- Logo -->
|
||||||
<label for="author">Autor:</label>
|
<div class="d-flex align-items-center mb-4">
|
||||||
<input type="text" id="author" name="author" readonly>
|
<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>
|
<h1 class="display-6 text-primary mb-4">Let's get started</h1>
|
||||||
<input type="text" id="authorProfileLink" name="authorProfileLink" readonly>
|
|
||||||
|
<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>
|
<div class="mb-3">
|
||||||
<input type="text" id="postLink" name="postLink" readonly>
|
<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>
|
<button type="submit" class="btn btn-primary w-100 py-2 mt-3">LOGIN</button>
|
||||||
<input type="text" id="date" name="date" readonly>
|
|
||||||
|
|
||||||
<label for="content">Conteúdo:</label>
|
<div class="text-center mt-4">
|
||||||
<textarea id="content" name="content" rows="4" readonly></textarea>
|
<span class="text-muted">New here? </span>
|
||||||
|
<a href="#" class="text-decoration-none">Create your account</a>
|
||||||
<label for="category">Categoria:</label>
|
</div>
|
||||||
<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>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Popup de confirmação -->
|
<!-- Área Logada -->
|
||||||
<div id="successPopup" class="popup" style="display: none;">
|
<div id="loggedInSection" class="d-none">
|
||||||
<div class="popup-content">
|
<!-- Navbar -->
|
||||||
<p>O post foi enviado com sucesso!</p>
|
<nav class="navbar navbar-expand navbar-light bg-white border-bottom px-3">
|
||||||
<div class="popup-buttons">
|
<div class="container-fluid px-0">
|
||||||
<button id="cancelButton">Fechar</button>
|
<!-- Logo -->
|
||||||
<button id="viewButton">Visualizar Swipe File</button>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script src="lib/bootstrap.bundle.min.js"></script>
|
||||||
<script src="popup.js"></script>
|
<script src="popup.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
349
popup.js
349
popup.js
@ -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', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Recuperar os dados do post do background script
|
// Inicializar o dropdown do usuário
|
||||||
chrome.runtime.sendMessage({ action: 'getPostData' }, (response) => {
|
const userDropdown = document.getElementById('userDropdown');
|
||||||
if (response && response.data) {
|
if (userDropdown) {
|
||||||
const postData = response.data;
|
new bootstrap.Dropdown(userDropdown);
|
||||||
|
|
||||||
// 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';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evento para o botão de cancelar e fechar o popup
|
// Inicializar todos os dropdowns do Bootstrap
|
||||||
cancelButton.addEventListener('click', () => {
|
const dropdownElementList = [].slice.call(document.querySelectorAll('[data-bs-toggle="dropdown"]'));
|
||||||
successPopup.style.display = 'none';
|
const dropdownList = dropdownElementList.map(function (dropdownToggleEl) {
|
||||||
// Fechar o popup principal
|
return new bootstrap.Dropdown(dropdownToggleEl);
|
||||||
window.close();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Evento para o botão de visualizar o Swipe File
|
const loginSection = document.getElementById('loginSection');
|
||||||
viewButton.addEventListener('click', () => {
|
const loggedInSection = document.getElementById('loggedInSection');
|
||||||
if (negocios_nome && negocios_id) {
|
|
||||||
// Construir o link para visualizar o Swipe File
|
// Verificar estado de autenticação
|
||||||
const baseUrl = 'https://launchr.com.br';
|
chrome.storage.local.get(['authToken', 'negocios_id', 'negocios_nome'], (result) => {
|
||||||
const encodedNegociosNome = encodeURIComponent(negocios_nome);
|
if (result.authToken) {
|
||||||
const swipeFileUrl = `${baseUrl}/posts/${encodedNegociosNome}-${negocios_id}?tab=Swipe%20File`;
|
showLoggedInSection();
|
||||||
|
} else {
|
||||||
// Abrir o link em uma nova aba
|
showLoginSection();
|
||||||
chrome.tabs.create({ url: swipeFileUrl }, () => {
|
}
|
||||||
// Fechar o popup após abrir a nova aba
|
});
|
||||||
window.close();
|
|
||||||
|
// 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
|
document.getElementById('navNewPost')?.addEventListener('click', () => {
|
||||||
const saveButton = document.getElementById('saveButton');
|
setActiveNav('navNewPost');
|
||||||
if (saveButton) {
|
});
|
||||||
saveButton.addEventListener('click', () => {
|
|
||||||
try {
|
// Toggle de senha
|
||||||
// Obter dados atualizados do formulário
|
const togglePasswordBtn = document.getElementById('togglePasswordBtn');
|
||||||
const updatedData = {
|
togglePasswordBtn?.addEventListener('click', () => {
|
||||||
author: document.getElementById('author')?.value || '',
|
const passwordInput = document.getElementById('password');
|
||||||
authorProfileLink: document.getElementById('authorProfileLink')?.value || '',
|
const type = passwordInput.type === 'password' ? 'text' : 'password';
|
||||||
postLink: document.getElementById('postLink')?.value || '',
|
passwordInput.type = type;
|
||||||
date: document.getElementById('date')?.value || '',
|
|
||||||
content: document.getElementById('content')?.value || '',
|
const icon = togglePasswordBtn.querySelector('i');
|
||||||
category: document.getElementById('category')?.value || ''
|
icon.classList.toggle('bi-eye');
|
||||||
};
|
icon.classList.toggle('bi-eye-slash');
|
||||||
|
});
|
||||||
if (!updatedData.category) {
|
|
||||||
alert('Por favor, selecione uma categoria.');
|
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;
|
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 {
|
chrome.runtime.sendMessage({
|
||||||
console.error('Elemento saveButton não encontrado.');
|
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');
|
const logoutButton = document.getElementById('logoutButton');
|
||||||
if (logoutButton) {
|
logoutButton?.addEventListener('click', () => {
|
||||||
logoutButton.addEventListener('click', () => {
|
chrome.runtime.sendMessage({ action: 'logout' }, (response) => {
|
||||||
if (confirm('Você realmente deseja fazer logout?')) {
|
if (response.success) {
|
||||||
chrome.runtime.sendMessage({ action: 'logout' }, (response) => {
|
showLoginSection();
|
||||||
if (response && response.success) {
|
|
||||||
window.close();
|
|
||||||
} else {
|
|
||||||
alert('Erro ao realizar logout.');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/editor.js
Normal file
16
src/editor.js
Normal 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'
|
||||||
|
});
|
||||||
|
}
|
||||||
345
styles.css
345
styles.css
@ -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 {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
width: 400px;
|
||||||
margin: 0;
|
min-height: 500px;
|
||||||
padding: 0;
|
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 {
|
.container {
|
||||||
padding: 20px;
|
padding: 1rem;
|
||||||
width: 300px;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/* 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
27
styles/input.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,10 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: [
|
content: ["./**/*.{html,js}"],
|
||||||
"./src/**/*.{html,js,ts,jsx,tsx}",
|
|
||||||
"./*.html"
|
|
||||||
],
|
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
|
plugins: [
|
||||||
|
require('@tailwindcss/forms'),
|
||||||
|
],
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user