pluton-html-editor
v0.0.2
Published
Editor HTML híbrido para Angular com modo visual (EJS) e modo código (Monaco)
Maintainers
Readme
pluton-html-editor
Editor HTML híbrido para Angular com suporte a templates EJS: modo Visual (contenteditable com chips) e modo Código (Monaco + preview ao vivo).
Demo online: StackBlitz
Recursos principais
- Variáveis EJS (
<%= ... %>) e lógica de template (<% if (...) { %>,<% } %>, loops, etc.) como chips no modo visual - Toolbar sincronizada com a seleção (negrito, itálico, sublinhado, tachado, alinhamento)
- Formatação em variáveis — negrito, itálico, cores e tamanho acumulam no
styledo chip - Tamanho de fonte personalizado (ex.:
27px) além da lista fixa - Layout de página — Automático (largura máxima, padrão), A4/A3/Letter, retrato/paisagem, múltiplas folhas e quebra de página
- Config global + local com defaults exportáveis e botões customizados na toolbar
- Modo código com altura dobrada: editor Monaco e preview em 50% / 50%
Instalação
A biblioteca é compatível com npm e pnpm. js-beautify já vem como dependência da lib (não precisa instalar à parte).
npm
npm install pluton-html-editor monaco-editor ngx-monaco-editor-v2pnpm
pnpm add pluton-html-editor monaco-editor ngx-monaco-editor-v2Pacote local (desenvolvimento / tarball)
# no repositório da lib
npm run build:lib
npm run pack:lib
# gera pluton-html-editor-0.0.1.tgz em editor-workspace/
# no app Angular consumidor
npm install /caminho/para/pluton-html-editor-0.0.1.tgz
# ou
npm install ../editor-workspace/dist/pluton-html-editornpm 7+ instala
peerDependenciesautomaticamente. Em versões antigas, instale Angular, Monaco engx-monaco-editor-v2manualmente.
Configuração do Monaco (obrigatório)
Em angular.json, adicione os assets do Monaco ao app consumidor:
{
"glob": "**/*",
"input": "node_modules/monaco-editor/min",
"output": "assets/monaco"
}Em app.config.ts (loader.js fica em assets/monaco/vs/):
import { provideMonacoEditor } from 'ngx-monaco-editor-v2';
export const appConfig: ApplicationConfig = {
providers: [provideMonacoEditor({ baseUrl: 'assets/monaco/vs' })],
};Estilos dos blocos (cards, headers, chips EJS)
Importe o SCSS no styles.scss do app:
@use 'pluton-html-editor/styles/editor-blocks';Sem esse import, cards, chips EJS e estilos de debug não aparecem corretamente no modo visual.
Uso mínimo
import { PlutonHtmlEditorComponent } from 'pluton-html-editor';
@Component({
imports: [PlutonHtmlEditorComponent],
template: `<pluton-html-editor [content]="html" (contentChange)="html = $event" />`,
})
export class MyPage {
html = '<p>Olá <%= user.name %></p>';
}Chips EJS no modo visual
| Tipo | Sintaxe | Classe CSS | Aparência |
|------|---------|------------|-----------|
| Variável / saída | <%= data.name %> | pluton-editor-var | Chip cinza |
| Lógica de template | <% if (x) { %>, <% } %>, <% for (...) { %> | pluton-editor-template | Chip violeta |
Edição
- Duplo clique no chip abre edição inline da expressão ou do bloco de lógica
- Enter confirma, Esc cancela
Formatação na seleção
- Selecione o chip (ou texto dentro dele) e use B, I, U, S, cores ou tamanho
- Vários estilos acumulam no atributo
styledo chip (ex.: negrito + itálico +27px) - Clicar de novo no mesmo botão remove só aquele estilo (toggle)
- A toolbar não tira o foco da seleção ao clicar nos botões de formatação
Digitação no editor
- Tags EJS digitadas como texto (
<%= ... %>ou<% ... %>) são promovidas a chips automaticamente
Padrões regex (configuráveis)
import {
DEFAULT_VARIABLE_PATTERN,
DEFAULT_TEMPLATE_LOGIC_PATTERN,
} from 'pluton-html-editor';
// Padrão: <%= expressão %>
DEFAULT_VARIABLE_PATTERN; // /<%=\s*([\s\S]*?)\s*%>/g
// Padrão: <% lógica %> (exceto <%=)
DEFAULT_TEMPLATE_LOGIC_PATTERN; // /<%(?!=)\s*([\s\S]*?)\s*%>/gModos Visual e Código
Modo visual
- Canvas com folhas; padrão Automático (largura 100% do editor) ou tamanho fixo (A4, A3, etc.)
- Toolbar fixa; scroll apenas no corpo do editor
- Painel Debug CSS destaca blocos, seções, páginas e chips
Modo código
- Altura total do componente dobra em relação ao modo visual (
editorHeight× 2) - Monaco (metade superior) e Preview (metade inferior), cada um com altura próxima ao canvas visual
- Botão flutuante F formata o HTML com
js-beautify(sefeatures.formatCodeButton)
Configuração global, local e defaults da lib
A configuração é mesclada nesta ordem (o último vence em chaves de primeiro nível; objetos aninhados são mesclados em profundidade):
DEFAULT_PLUTON_HTML_EDITOR_CONFIG— defaults exportados pela bibliotecaPLUTON_HTML_EDITOR_CONFIG— token global (app)[config]no<pluton-html-editor>— config local da instância
Importar defaults da lib
import {
DEFAULT_PLUTON_HTML_EDITOR_CONFIG,
createDefaultPlutonHtmlEditorConfig,
createPlutonHtmlEditorConfig,
mergePlutonHtmlEditorConfig,
PLUTON_HTML_EDITOR_CONFIG,
PlutonHtmlEditorComponent,
} from 'pluton-html-editor';
const base = createDefaultPlutonHtmlEditorConfig();
const custom = createPlutonHtmlEditorConfig({
theme: 'vs',
features: { insertImage: false },
});
const merged = mergePlutonHtmlEditorConfig(
{ theme: 'vs-dark', defaults: { typography: { fontSize: '16px' } } },
{ defaults: { typography: { fontFamily: 'Georgia, serif' } } },
);Exemplo: global + local
// app.config.ts
import { PLUTON_HTML_EDITOR_CONFIG } from 'pluton-html-editor';
export const appConfig: ApplicationConfig = {
providers: [
{
provide: PLUTON_HTML_EDITOR_CONFIG,
useValue: {
theme: 'vs-dark',
minifyOnEmit: false,
defaultViewMode: 'visual',
features: {
insertPageBreak: true,
debugCss: true,
},
defaults: {
typography: {
fontSize: '16px',
fontFamily: 'Arial, sans-serif',
foreColor: '#1e293b',
},
pageLayout: {
pageSize: { preset: 'A4', orientation: 'portrait' },
margin: { top: '16px', right: '16px', bottom: '16px', left: '16px' },
},
},
},
},
],
};<pluton-html-editor
[content]="html"
[height]="720"
[config]="{
theme: 'vs',
features: { insertImage: true },
defaults: { typography: { fontSize: '18px' } }
}"
(contentChange)="html = $event"
/>PlutonHtmlEditorConfig
| Campo | Descrição |
|-------|-----------|
| theme | 'vs' | 'vs-dark' — tema do Monaco |
| minifyOnEmit | Minifica HTML no contentChange |
| variablePattern | Regex para saída/variáveis (<%= ... %>, padrão exportado) |
| templateLogicPattern | Regex para lógica (<% ... %> exceto <%=, padrão exportado) |
| visualDebounceMs | Debounce do sync no modo visual (padrão 300) |
| defaultViewMode | 'visual' | 'code' |
| editorHeight | Altura no modo visual. Número = px; string = CSS (560px, min(85vh, 720px)). Padrão: min(85vh, 720px) |
| editorMinHeight | Altura mínima (padrão 420 px). No modo código, valor efetivo × 2 |
| features | Liga/desliga grupos da toolbar |
| defaults | Valores iniciais dos painéis e seletores |
| lists | Listas customizadas (fontes, tamanhos, margens, etc.) |
| customToolbarButtons | Botões extras no header |
features (todos true por padrão)
| Chave | Toolbar |
|-------|---------|
| undoRedo | Desfazer / Refazer |
| textFormatting | Negrito, itálico, sublinhado, tachado |
| lists | Listas numerada e com marcadores |
| alignment | Alinhamento |
| typography | Caixa, tamanho, Px personalizado, fonte, entrelinhas |
| colors | Cor do texto e destaque |
| collapseDoubleSpaces | Botão 1⎵ |
| removeFormat | Limpar formatação |
| moveCursorToEnd | Ir ao final do documento |
| insertImage | Imagem |
| insertColumns | Colunas (grid 12) |
| insertMargin | Margem do bloco |
| insertCard | Card |
| insertLine | Linha horizontal |
| insertLink | Link |
| insertTable | Tabela |
| insertPageBreak | Quebra de página (nova folha) |
| insertSection | Seção (agrupamento + margem) |
| insertPageLayout | Página (tamanho, orientação, fundo, padding) |
| debugCss | Debug CSS |
| viewModeSwitch | Visual / Código |
| formatCodeButton | Botão flutuante F (modo código) |
Tipografia na toolbar
| Controle | Uso |
|----------|-----|
| Caixa | text-transform (normal, maiúsculas, minúsculas) |
| Tamanho | Lista pré-definida (12px … 32px) |
| Px + OK | Tamanho personalizado — digite 27 e confirme (aplica 27px na seleção) |
| Fonte | font-family |
| Entre | line-height do bloco |
| A / ▢ | Cor do texto e destaque |
Os botões B / I / U / S ficam destacados quando a seleção já possui aquele estilo (incluindo chips de variável).
defaults
| Caminho | Uso |
|---------|-----|
| defaults.typography | Valores iniciais dos selects e color pickers |
| defaults.card | Painel Card |
| defaults.margin | Painel Margin |
| defaults.sectionMargin | Painel Seção |
| defaults.pageLayout | Painel Página: A4/A3/Letter/custom, orientação, fundo, margens |
| defaults.table | Linhas/colunas/cabeçalho |
| defaults.image.maxFileBytes | Limite de upload (padrão 2 MB) |
| defaults.columns | maxColumns, customSpans |
lists
Substitui FONT_FAMILIES, FONT_SIZES, LINE_HEIGHT_OPTIONS, MARGIN_PRESETS, TEXT_TRANSFORM_OPTIONS.
customToolbarButtons
Botões extras no header. Mescla por id (local sobrescreve global).
customToolbarButtons: [
{
id: 'delete-word',
label: 'Remover palavra',
icon: 'trash',
row: 'format',
order: 10,
visualOnly: true,
action: { type: 'deleteWord' },
},
{
id: 'paste-model',
label: 'Modelo',
icon: 'template',
action: {
type: 'insertHtml',
html: '<% if (order.show) { %><p><%= order.id %></p><% } %>',
modes: ['visual', 'code'],
},
},
{
id: 'custom-log',
label: 'Log',
icon: 'clipboard',
},
],| action.type | Comportamento |
|---------------|----------------|
| insertHtml | Cola HTML na seleção/cursor |
| deleteSelection | Apaga a seleção |
| deleteWord | Apaga seleção ou palavra sob o cursor |
<pluton-html-editor (customToolbarClick)="onCustom($event)" />onCustom(e: PlutonCustomToolbarClickEvent) {
console.log(e.buttonId, e.selectedText, e.viewMode);
}
// ViewChild
editor.insertHtmlAtCursor('<span>ok</span>');
editor.deleteSelectionOrWord();
editor.getToolbarSelection();iconSvg aceita SVG inline. showLabel: true exibe texto ao lado do ícone.
Página, folhas e quebra
No painel Página:
- Tamanho: Automático (largura máxima — padrão), A4, A3, Carta ou personalizado (mm)
- Retrato / Paisagem
- Cor de fundo e padding (margens internas da folha)
Aplicar layout atualiza todas as folhas [data-pluton-page].
Quebra (modo visual): insere data-pluton-page-break, cria nova folha e move o cursor para ela.
defaults: {
pageLayout: {
pageSize: { preset: 'auto', orientation: 'portrait' },
margin: { top: '20px', right: '20px', bottom: '20px', left: '20px' },
backgroundColor: '#ffffff',
},
},Arquitetura e fluxo de dados
flowchart LR
subgraph visual [Modo Visual]
V[contenteditable]
Chips[chips EJS]
V --> Chips
end
subgraph code [Modo Código]
M[Monaco]
P[Preview HTML]
end
Raw[rawContent string]
V -->|HTML do surface| Raw
Raw -->|codeToVisual| V
M --> Raw
Raw --> P- Fonte da verdade: string HTML com tags EJS e estilos inline
- Modo visual →
contentChange: emite o HTML do surface (chips permanecem como no editor) - Modo código → visual: EJS vira chips para edição (
codeToVisual) - Modo código → visual: regex configuráveis geram chips; Monaco e preview compartilham o mesmo
rawContent - Exportação: o
contentChangeemite o mesmo HTML do modo visual (sem converter chips em EJS nem alterar colunas existentes);minifyOnEmitopcional
PDF / Puppeteer / html2pdf (opcional, fora do editor)
Somente colunas inseridas pelo botão Colunas da toolbar usam <table> (melhor em PDF). HTML que você colar ou editar com display:flex não é alterado pelo editor.
Se precisar converter flex legado só na geração do PDF (no seu backend), use de forma explícita:
import { normalizeFlexColumnsInHtml } from 'pluton-html-editor';
const html = normalizeFlexColumnsInHtml(ejs.render('template', data));API do componente
| Input / Output | Tipo | Descrição |
|----------------|------|-----------|
| content | string | HTML inicial |
| contentChange | string | HTML emitido (inline + EJS) |
| config | Partial<PlutonHtmlEditorConfig> | Config local |
| height | string \| number | Sobrescreve editorHeight nesta instância |
| defaultViewMode | 'code' \| 'visual' | Modo inicial |
| viewModeChange | 'code' \| 'visual' | Troca de modo |
| customToolbarClick | PlutonCustomToolbarClickEvent | Botão customizado sem action embutida |
Métodos públicos (ViewChild)
| Método | Descrição |
|--------|-----------|
| insertHtmlAtCursor(html) | Insere HTML no cursor (visual ou código) |
| deleteSelectionOrWord() | Remove seleção ou palavra |
| getToolbarSelection() | { selectedText, hasSelection } |
Exports úteis
import {
normalizeFlexColumnsInHtml,
PlutonHtmlEditorComponent,
PLUTON_HTML_EDITOR_CONFIG,
DEFAULT_PLUTON_HTML_EDITOR_CONFIG,
DEFAULT_VARIABLE_PATTERN,
DEFAULT_TEMPLATE_LOGIC_PATTERN,
PLUTON_EDITOR_VAR_CLASS,
PLUTON_EDITOR_TEMPLATE_CLASS,
mergePlutonHtmlEditorConfig,
scaleCssSize,
} from 'pluton-html-editor';Desenvolvimento do repositório
O workspace aceita npm ou pnpm (sem packageManager fixo no package.json raiz).
cd editor-workspace
# npm
npm install
npm start
npm run build:lib
npm run test:lib
# pnpm (opcional)
pnpm install
pnpm start
pnpm run build:lib
pnpm run test:libPlayground
Demo no navegador: StackBlitz
Após npm install ou pnpm install:
npm start
# ou: pnpm startO demo inclui variáveis, lógica <% if %>, tipografia, blocos, colunas, tabela, seção e quebra de página.
Testes da biblioteca
npm run test:lib
# ou: pnpm run test:lib