Codync
A free, open-source 1:1 alternative to Grok Bot and Muse. Message Claude Code, Codex, Cursor and 40+ coding agents as persistent bots from your iPhone, Mac or Linux desktop. Group chats, threads, appr
Transforma qualquer textarea em um editor completo. Seguro, acessível e com um único arquivo.
Translation pending
Developer toolsJavaScript · PHPGitHub @edersonfrOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Editor HTML WYSIWYG leve para jQuery. Transforma qualquer <textarea> num editor completo, com um único arquivo JavaScript, sem build no seu projeto e sem depender de Tailwind.
<textarea>: o conteúdo continua indo no envio do formulário, sem código extra.Tab entre células.O editor é um único arquivo, que depende só do jQuery (testado com a versão 3.6). Ícones e estilos são carregados automaticamente.
Baixando o arquivo: copie dist/weaver-editor.min.js para o seu projeto.
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="/js/weaver-editor.min.js"></script>
Pelo CDN do jsDelivr, com a versão fixada no tag:
<script src="https://cdn.jsdelivr.net/gh/edersonfr/[email protected]/dist/weaver-editor.min.js"
integrity="sha384-2O2pulBNAw6/bO1u5dlhjHNDQo3FZiwZuX8LPd1ZQKf9eu8LkLo12tO29HbXyUuH"
crossorigin="anonymous"></script>
O integrity (SRI) faz o navegador recusar o arquivo se o conteúdo servido pelo CDN não for exatamente o da versão 1.1.1. Cada versão tem o seu hash, publicado na Release correspondente.
Use sempre um tag (@v1.1.1), nunca @main: a main muda a cada commit, e o editor mudaria junto em produção. As versões e o que mudou em cada uma estão no CHANGELOG e nas Releases, que também trazem o weaver-editor.min.js para baixar. Em tempo de execução, a versão carregada fica em WysiwygEditor.VERSION.
Funciona nas versões atuais do Chrome, Edge, Firefox e Safari. O Internet Explorer não é suportado.
<form action="/salvar" method="POST">
<label for="artigo">Artigo</label>
<textarea id="artigo" name="conteudo_html"></textarea>
<button type="submit">Salvar</button>
</form>
<script>
$(function () {
$('#artigo').weaver({
height: 400,
placeholder: 'Comece a escrever...'
});
});
</script>
O HTML do editor é copiado para o textarea a cada alteração e no envio do formulário. O conteúdo que já estiver no textarea é carregado ao iniciar.
A barra é uma lista de grupos, cada um com seus botões. Os grupos aparecem separados por uma divisória:
$('#artigo').weaver({
toolbar: [
['history', ['undo', 'redo']],
['style', ['style', 'fontName', 'fontSize']],
['font', ['bold', 'italic', 'underline', 'strikethrough', 'clear']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['insert', ['link', 'table', 'picture', 'video', 'hr', 'ai']],
['view', ['preview', 'fullscreen', 'showBlocks', 'codeview']]
]
});
A lista que você passar substitui a padrão por inteiro. Um grupo sem lista, como ['color'], usa o nome do grupo como botão.
Botões disponíveis:
| Botão | Função |
|---|---|
undo, redo | Desfazer e refazer |
formatBlock | Parágrafo, títulos H1 a H6, citação e bloco de código |
fontName, fontSize | Fonte e tamanho do texto |
bold, italic, underline, strikethrough | Negrito, itálico, sublinhado e tachado |
subscript, superscript | Subscrito e sobrescrito |
removeFormat | Remove a formatação do trecho selecionado |
foreColor, backColor | Cor do texto e cor de destaque |
ul, ol | Lista com marcadores e lista numerada |
paragraphGroup | Alinhamentos e recuo, num menu |
link, table, image, video, hr | Inserir link, tabela, imagem, vídeo e linha horizontal |
ai | Assistente de IA |
preview, fullscreen, showBlocks, codeview | Pré-visualizar, tela cheia, mostrar blocos e código-fonte |
Apelidos (compatíveis com o Summernote): style = formatBlock, clear = removeFormat, picture = image, paragraph = paragraphGroup, color = foreColor + backColor.
Os recursos de link, tabela, imagem, vídeo, código-fonte, pré-visualização, tela cheia e IA só são ativados quando o botão correspondente está na barra.
| Opção | Padrão | Descrição |
|---|---|---|
toolbar | barra completa | Grupos e botões da barra (veja acima) |
height | 300 | Altura mínima da área de edição, em pixels |
placeholder | o do textarea | Texto exibido com o editor vazio |
theme | — | 'light', 'dark' ou 'auto' (segue o sistema); sem valor, segue a classe .dark da página (veja Tema e aparência) |
fontNames | lista padrão | Fontes do menu de fontes |
googleFonts | true | Carrega do Google as fontes do menu e as usadas no conteúdo, sob demanda (editor e pré-visualização) |
fontSearch | 'auto' | Campo de busca no menu de fontes: true, false ou 'auto' (com mais de 20 fontes) |
onFontApply | — | function (fontName, editor) chamada ao aplicar uma fonte, para carregá-la também na loja |
previewCss | — | Folhas de estilo do seu site, aplicadas na pré-visualização |
uploadImage | — | Função de upload de imagens (veja Imagens) |
maxImageSize | 5242880 | Tamanho máximo da imagem, em bytes (5 MB) |
imageMaxWidth | 1600 | Largura máxima ao embutir a imagem em base64 |
aiEndpoint | 'ai-api.php' | URL do backend do assistente de IA |
onAiGenerate | — |
A instância fica guardada no elemento:
var editor = $('#artigo').data('weaver');
editor.getContent(); // HTML atual (já sanitizado)
editor.setContent('<p>Novo texto</p>'); // substitui o conteúdo (sanitiza antes)
editor.isPristine(); // true se nada foi editado desde o carregamento
editor.saveSelection(); // guarda o cursor antes de abrir um modal
editor.insertHTML('<p>Oi</p>'); // insere (sanitizado) no cursor salvo, com desfazer
editor.insertImage(url, { alt: 'Foto' }); // valida a URL e insere a imagem no cursor salvo
editor.restoreSelection(); // foca o editor e volta o cursor salvo
editor.focus(); // foca a área de edição
editor.loadFont('Lobster'); // carrega uma fonte do Google Fonts no editor
editor.getSelectedImage(); // <img> selecionada no editor, ou null
editor.replaceImage(img, url, attrs); // troca o src mantendo estilo e alinhamento
editor.exec('bold'); // executa um comando da barra
editor.sanitize(html); // sanitiza um HTML com as regras do editor
editor.notify('Salvo!'); // mostra um aviso dentro do editor
editor.setTheme('dark'); // 'light', 'dark', 'auto' ou null (segue .dark da página)
$('#artigo').weaver('destroy'); // remove o editor e devolve o textarea
A cada alteração, o elemento original dispara change com o HTML:
$('#artigo').on('change', function (event, html) {
console.log('Novo conteúdo:', html);
});
Use destroy ao trocar de tela numa SPA: ele remove todos os eventos e devolve o textarea com o conteúdo atual.
Fontes. O menu aceita o catálogo inteiro do Google Fonts (fontNames com ~1.500 nomes): os itens só são montados ao abrir o menu, cada fonte só é baixada quando aparece na lista, e a busca filtra pelo nome. Ao escolher uma fonte, ou ao carregar um conteúdo que já usa uma, o editor pede ao Google só aquela família. Fontes do sistema (Arial, Georgia...) não são pedidas. O conteúdo salvo guarda apenas o font-family; para a fonte aparecer na loja, carregue-a lá, por exemplo em onFontApply.
HTML original preservado. Enquanto nada for editado, getContent() (e o envio do formulário) devolve exatamente o HTML carregado, sem mudar aspas, entidades, ordem de atributos ou a formatação do CSS. Isso vale quando a sanitização não removeu nada do conteúdo; se removeu, o editor devolve a versão sanitizada.
O editor fica escuro quando a página tem a classe .dark num ancestral, como <html class="dark"> (o padrão do Tailwind). A opção theme muda isso:
theme | Comportamento |
|---|---|
| sem valor | Segue a classe .dark da página |
'auto' | Segue o modo escuro do sistema (prefers-color-scheme), além da classe .dark |
'light' | Sempre claro, mesmo com .dark na página |
'dark' | Sempre escuro |
As cores ficam em variáveis CSS na raiz .wysiwyg-editor. Para ajustar, redefina as variáveis nos seus estilos, nos dois temas:
.wysiwyg-editor { --weaver-accent: #0f766e; }
.dark .wysiwyg-editor:not([data-weaver-theme="light"]), .wysiwyg-editor[data-weaver-theme="dark"] {
--weaver-accent: #14b8a6;
--weaver-content-bg: #fff; /* área de texto clara, como na loja */
--weaver-content-fg: #1f2937;
}
| Variável | Uso |
|---|---|
--weaver-bg, --weaver-fg | Fundo e texto do editor |
--weaver-content-bg, --weaver-content-fg | Fundo e texto da área de edição (padrão: os dois acima) |
--weaver-toolbar-bg | Fundo da barra de ferramentas |
--weaver-menu-bg, --weaver-input-bg | Botões, menus e janelas; campos de texto |
--weaver-border, --weaver-border-soft | Bordas de botões e campos; divisórias |
--weaver-hover, --weaver-active | Botão sob o mouse; botão ativo |
--weaver-accent, --weaver-link | Cor de destaque (foco, seleção, botão Salvar); links no conteúdo |
--weaver-code-bg, --weaver-code-fg, --weaver-code-tag | Code view: fundo, texto e tags |
A lista completa está em src/core/base-styles.js.
Tailwind na página. A interface usa nomes de classe do Tailwind (bg-white, border-gray-300...), mas o CSS do editor, com escopo em .wysiwyg-editor, tem prioridade sobre as utilitárias da página. Por isso, uma paleta gray diferente não muda o editor. A área de edição não usa prose, e o editor restaura listas com marcador e tamanhos de título, que o preflight do Tailwind zera. Só um Tailwind com important: true passa por cima dessas regras.
Defina botões na opção buttons e use o nome deles na toolbar. O action recebe a instância do editor e o botão. Antes de chamá-lo, o editor guarda o cursor, então insertHTML e insertImage inserem no lugar certo mesmo depois de um modal tirar o foco:
$('#descricao').weaver({
buttons: {
imageManager: {
icon: 'images', // ícone do Lucide; ou `html` com qualquer HTML; ou `text`
title: 'Gerenciar imagens',
action: function (editor, $button) {
abrirGerenciador(function (urls) {
urls.forEach(function (url) { editor.insertImage(url, { alt: '' }); });
});
},
isActive: function (editor) { return false; } // opcional: botão pressionado
},
modelo: {
type: 'dropdown',
text: 'Modelos',
options: ['Garantia', { label: 'Frete grátis', value: 'frete' }],
action: function (editor, value) { editor.insertHTML(modelos[value]); }
}
},
toolbar: [['insert', ['link', 'imageManager', 'modelo', 'video']]]
});
Para registrar um botão para todos os editores da página, use WysiwygEditor.registerButton(nome, definicao). Um nome na toolbar que não existe gera um aviso no console.
Sem configuração, a imagem é embutida no HTML em base64, reduzida para no máximo imageMaxWidth pixels de largura. Para guardar as imagens no seu servidor, use uploadImage e chame done(url) no sucesso ou done(null, 'mensagem') no erro. A mensagem aparece dentro do editor.
$('#artigo').weaver({
maxImageSize: 5 * 1024 * 1024,
uploadImage: function (file, done) {
var data = new FormData();
data.append('imagem', file);
fetch('/upload.php', { method: 'POST', body: data })
.then(function (r) { return r.json(); })
.then(function (json) { json.url ? done(json.url) : done(null, json.error || 'Falha no envio.'); })
.catch(function () { done(null, 'Não foi possível enviar a imagem.'); });
}
});
Formatos aceitos: PNG, JPG, GIF, WebP e AVIF. No servidor, confira o tipo e o tamanho de novo antes de salvar.
O done aceita atributos para a imagem: done(url, { alt: 'Foto', width: 600 }).
Colar e arrastar: um print colado com Ctrl+V ou uma imagem arrastada passa pelo mesmo caminho (com uploadImage, sobe para o servidor). Ao colar do Word ou do Excel, que mandam um PNG junto com o HTML, vale o HTML. Com uploadPastedImages: true, as imagens em base64 dentro de um HTML colado ou solto também sobem pelo uploadImage, e o src delas é trocado pela URL devolvida.
Com uploadImage ou imageToolbar configurados, os recursos de imagem (colar, redimensionar, alinhar) ficam ativos mesmo sem o botão image na barra.
Barra da imagem: clique numa imagem para redimensionar, alinhar, editar o texto alternativo ou remover. Para acrescentar botões ou tirar os padrões (w100, w50, w25, original, floatLeft, center, floatRight, noFloat, alt, remove):
$('#descricao').weaver({
imageToolbar: {
add: [{
icon: 'replace',
title: 'Trocar imagem',
action: function (editor, img) {
abrirGerenciador(function (url) { editor.replaceImage(img, url); });
}
}],
remove: ['w25']
}
});
Adicione o botão ai à barra. Ele envia a instrução para o ai-api.php (ou o endereço em aiEndpoint), que responde em HTML usando Google Gemini ou OpenAI. Sem chave configurada, o backend responde em modo demonstração.
Configure o backend por variáveis de ambiente:
| Variável | Descrição |
|---|---|
GEMINI_API_KEY / OPENAI_API_KEY | Chave de ao menos um provedor |
GEMINI_MODEL / OPENAI_MODEL | Modelo usado (padrões no topo do arquivo) |
WEAVER_AI_SESSION_KEY | Chave em $_SESSION que indica o usuário logado (padrão: user_id) |
WEAVER_AI_ALLOWED_ORIGINS | Origens aceitas, separadas por vírgula (padrão: o próprio domínio) |
WEAVER_AI_RATE_LIMIT / WEAVER_AI_RATE_WINDOW | Limite de chamadas por usuário (padrão: 20 por hora) |
WEAVER_AI_MAX_PROMPT | Tamanho máximo da instrução (padrão: 2000 caracteres) |
Com uma chave real configurada, o endpoint exige usuário logado, para ninguém gastar a sua cota de fora do painel.
Para usar o seu próprio backend, passe onAiGenerate:
$('#artigo').weaver({
onAiGenerate: function (prompt, done) {
minhaApi.gerar(prompt)
.then(function (html) { done(html); })
.catch(function (erro) { done(null, erro.message); });
}
});
A resposta da IA sempre passa pelo sanitizador antes de entrar no texto.
| Atalho | Ação |
|---|---|
Ctrl+B / Ctrl+I / Ctrl+U | Negrito / itálico / sublinhado |
Ctrl+K | Inserir ou editar link |
Ctrl+Shift+V | Colar sem formatação |
Ctrl+Z / Ctrl+Y (ou Ctrl+Shift+Z) | Desfazer / refazer |
Tab / Shift+Tab em lista | Aumentar / diminuir o nível do item |
Tab / Shift+Tab em tabela | Próxima / célula anterior (no fim, cria uma linha) |
Alt+F10 | Ir para a barra de ferramentas |
No Mac, use Cmd no lugar de Ctrl.
Esc volta ao texto. Menus, grade de tabela, menu da tabela (tecla de menu ou Shift+F10) e popovers também funcionam só pelo teclado.Esc e devolvem o foco ao botão.aria-pressed, e erros são anunciados sem alert().<label for="id-do-textarea"> na página vira o nome da área de edição.O editor sanitiza todo HTML que entra: conteúdo inicial, colar, arrastar, código-fonte, IA e desfazer/refazer. Scripts, eventos (onclick, onerror...), links javascript: e iframes de domínios desconhecidos são removidos.
Sanitize também no servidor. O editor protege quem está editando, mas o envio do formulário pode ser forjado. Antes de salvar ou exibir o HTML, passe-o por uma biblioteca de servidor, como o HTML Purifier (PHP) ou o DOMPurify (Node).
Tags bloqueadas: script, style, svg, math, object, embed, form e similares saem mesmo se estiverem em allowedTags. A lista pode ser trocada em forbiddenTags (veja Modo confiável).
iframes: só players do YouTube (youtube-nocookie.com) e do Vimeo, sempre com sandbox. Para liberar outros, use allowedIframeSources.
CSS inline: só propriedades de formatação. position e url() são removidos.
Pré-visualização: roda num iframe com sandbox e sem scripts.
Ícones: vêm de uma versão fixa do Lucide. Para o navegador recusar um arquivo adulterado no CDN, informe o hash em iconsIntegrity:
curl -s https://unpkg.com/[email protected]/dist/umd/lucide.min.js | openssl dgst -sha384 -binary | openssl base64 -A
Para não depender de CDN, sirva o arquivo do seu domínio com iconsUrl: '/js/lucide.min.js'. Se os ícones não carregarem, os botões mostram o nome.
Google Fonts e LGPD: as fontes do menu vêm do Google, que recebe o IP do visitante. Use googleFonts: false para desligar.
Quando quem edita é o dono do conteúdo (por exemplo, o lojista editando a própria loja), a sanitização pode ser afrouxada para não alterar o HTML existente:
$('#descricao').weaver({
forbiddenTags: [], // ou ['script'] para manter só scripts de fora
allowedTags: '*',
allowedAttributes: { '*': '*' }, // ou ['class', 'style', /^data-[\w-]+$/, /^aria-[\w-]+$/]
allowedStyles: '*',
allowCssUrls: true,
keepComments: true,
iframeAttributes: false,
allowedIframeSources: '*'
});
Para desligar tudo, use sanitize: false, ou passe a sua própria função em sanitizer.
Mesmo nesses modos, nada executa dentro do editor:
<script>, <style>, <link>, <meta>, <form>, <input> e afins aparecem como um marcador cinza não editável. O CSS não vaza para a página, e os campos não vão junto no envio do formulário. No getContent() e no código-fonte, eles voltam como eram.onclick, onerror, srcdoc, href="javascript:...") ficam desativados no editor e voltam na saída.expression(), behavior e escapes CSS fora de aspas continuam bloqueados com allowedStyles: '*', e url() só passa com allowCssUrls.Com o modo confiável, a validação do HTML passa a ser responsabilidade do servidor. O editor deixa de remover o que o servidor precisa barrar antes de exibir o conteúdo.
O código-fonte fica em src/ (ES5, sem transpilação). O Gulp junta e minifica os arquivos em dist/.
npm install # dependências do build
npx gulp # gera dist/weaver-editor.js e dist/weaver-editor.min.js
Os testes abrem o editor num Chromium real. tests/security.test.js reproduz ataques de XSS conhecidos, tests/functional.test.js cobre formatação, listas, tabelas, imagens, colar, histórico, teclado e acessibilidade, e tests/site.test.js verifica a página de demonstração.
npm install --no-save playwright [email protected]
npx playwright install chromium
npm test
No GitHub:
main.main, o workflow Build regenera o dist/ com o Gulp e faz o commit. Não é preciso commitar o dist/ nos PRs.weaver-editor/
├── src/
│ ├── core/ # Sanitizador, seleção, histórico, estilos base
│ ├── plugins/ # Barra, formatação, link, tabela, imagem, vídeo, IA...
│ └── editor.js # Núcleo: opções, ciclo de vida, API
├── dist/ # Arquivos prontos para uso (gerados pelo Gulp)
├── docs/ # Página de demonstração (GitHub Pages)
├── examples/ # Exemplo de uso em HTML
├── tests/ # Testes com Playwright
├── ai-api.php # Backend de exemplo do assistente de IA
├── .github/workflows/ # CI e build automático
└── gulpfile.js # Build
MIT © 2026 Ederson F. Ribeiro
| Função própria para gerar o conteúdo da IA |
iconsUrl | Lucide 0.525.0 (unpkg) | Arquivo de ícones; false usa o Lucide já carregado na página |
iconsIntegrity | — | Hash SRI do arquivo de ícones |
allowedTags | lista segura | Tags aceitas no conteúdo; '*' aceita qualquer tag HTML |
allowedAttributes | lista segura | Atributos aceitos, por tag ('*' vale para todas). Aceita RegExp (/^data-[\w-]+$/) e '*' como lista |
allowedStyles | lista segura | Propriedades CSS aceitas no atributo style; '*' aceita todas |
allowedIframeSources | YouTube e Vimeo | Lista de RegExp com os endereços de iframe aceitos; '*' aceita qualquer http(s) |
forbiddenTags | script, style, svg... | Tags removidas com todo o conteúdo, mesmo se liberadas em allowedTags |
allowCssUrls | false | Aceita url() no CSS inline, com endereço http(s), relativo ou imagem data: |
keepComments | false | Mantém os comentários HTML |
iframeAttributes | true | Acrescenta sandbox, referrerpolicy e loading aos iframes |
sanitize | true | false desliga a sanitização (veja Modo confiável) |
sanitizer | — | Função (html) => html que substitui a sanitização |
normalize | true | Limpa tags repetidas e vazias ao sair do editor; false desliga |
buttons | — | Botões próprios da barra (veja Botões próprios) |
imageToolbar | — | Botões extras (ou removidos) na barra da imagem selecionada (veja Imagens) |
uploadPastedImages | false | Envia pelo uploadImage as imagens em base64 de HTML colado ou solto |
imageDefaults | — | Atributos aplicados a toda imagem inserida, como { style: 'width:100%;height:auto' } |
extraPlugins | — | Plugins somados aos automáticos, sem desligar link, tabela, imagem etc. |
plugins | automático | Lista de plugins, para montar o editor à mão (substitui os automáticos) |