Codync
免费开源的 1:1 Grok Bot 与 Muse 替代方案。可在 iPhone、Mac 或 Linux 桌面上将 Claude 代码、Codex、Cursor 以及 40 多个编码代理作为持久机器人使用。支持群组聊天、线程、应用。
AI 翻译
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 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) |