Publication IA: - 4 templates (magazine, brief, essay, simple) avec CSS riche - Rewrite IA (article/exercises/tutorial/reference/mixed) - Modération avec timeout 12s + fallback safe - Quotas publish_enhance par tier (basic=2, pro=15, business=100) - Détection contenu stale (hash) - Migration DB publishedContent/publishedTemplate/publishedSourceHash Fixes: - cheerio v1.2: Element -> AnyNode (domhandler), decodeEntities cast - _isShared ajouté au type Note (champ virtuel serveur) - callout colors PDF export: extraction fonction pure testable - admin/published: guard note.userId null - Cmd+S fonctionne en mode dialog (pas seulement fullPage) i18n: - 23 clés publish* traduites dans les 15 locales - Extension Web Clipper: 13 locales mise à jour Tests: - callout-colors.test.ts (6 tests) - note-visible-in-view.test.ts (5 tests) - entitlements.test.ts + byok-entitlements.test.ts: mock usageLog + unstubAllEnvs - 199/199 tests passent Tracker: user-stories.md sync avec sprint-status.yaml
287 lines
8.1 KiB
TypeScript
287 lines
8.1 KiB
TypeScript
/** CSS KaTeX commun aux pages publiées */
|
||
export const KATEX_PUBLISH_CSS = `
|
||
.r-math-display {
|
||
margin: 1.75em 0;
|
||
text-align: center;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
padding: 0.25em 0;
|
||
}
|
||
.r-math-inline { display: inline; }
|
||
.r-math-inline .katex { font-size: 1.05em; }
|
||
.r-math-fallback {
|
||
font-family: 'SF Mono', Menlo, monospace;
|
||
font-size: 0.9em;
|
||
opacity: 0.85;
|
||
}
|
||
.pub-rewrite-body .katex-display { margin: 0; }
|
||
.katex { font-size: 1.1em; }
|
||
.katex-display { margin: 0; overflow-x: auto; overflow-y: hidden; }
|
||
`
|
||
|
||
export const REWRITE_SHARED_CSS = `
|
||
${KATEX_PUBLISH_CSS}
|
||
/* ─── Résumé introductif ─────────────────────────────── */
|
||
.pub-rewrite-summary {
|
||
font-size: 1.2em;
|
||
line-height: 1.7;
|
||
font-style: italic;
|
||
color: var(--pub-summary-color, #444);
|
||
margin-bottom: 2em;
|
||
padding-bottom: 1.5em;
|
||
border-bottom: 2px solid var(--pub-accent, #A47148);
|
||
}
|
||
|
||
/* ─── Exercices ──────────────────────────────────────── */
|
||
.pub-rewrite-body .pub-exercise {
|
||
border: 2px solid var(--pub-exercise-border, #e0d4c3);
|
||
border-radius: 12px;
|
||
margin: 1.75em 0;
|
||
overflow: hidden;
|
||
}
|
||
.pub-rewrite-body .pub-exercise-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 18px;
|
||
background: var(--pub-exercise-header-bg, #f6f1ea);
|
||
border-bottom: 1px solid var(--pub-exercise-border, #e0d4c3);
|
||
}
|
||
.pub-rewrite-body .pub-exercise-num {
|
||
font-weight: 700;
|
||
font-size: 0.82em;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
color: var(--pub-accent, #A47148);
|
||
}
|
||
.pub-rewrite-body .pub-exercise-meta {
|
||
margin-left: auto;
|
||
font-size: 0.82em;
|
||
font-weight: 600;
|
||
color: #666;
|
||
}
|
||
.pub-rewrite-body .pub-exercise-body {
|
||
padding: 16px 20px;
|
||
color: #1a1a1a;
|
||
background: #fff;
|
||
}
|
||
.pub-rewrite-body .pub-exercise-body p:first-child { margin-top: 0; }
|
||
.pub-rewrite-body .pub-exercise-body p:last-child { margin-bottom: 0; }
|
||
|
||
/* Corps réécrit : texte toujours sombre sur fond clair */
|
||
.pub-rewrite-body {
|
||
color: #1a1a1a;
|
||
}
|
||
.pub-rewrite-body p,
|
||
.pub-rewrite-body li,
|
||
.pub-rewrite-body td,
|
||
.pub-rewrite-body th {
|
||
color: inherit;
|
||
}
|
||
|
||
/* Solution collapsible */
|
||
.pub-rewrite-body .pub-solution {
|
||
border-top: 1px dashed var(--pub-exercise-border, #e0d4c3);
|
||
}
|
||
.pub-rewrite-body .pub-solution > summary {
|
||
list-style: none;
|
||
cursor: pointer;
|
||
padding: 10px 20px;
|
||
font-size: 0.85em;
|
||
font-weight: 600;
|
||
color: var(--pub-accent, #A47148);
|
||
user-select: none;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.pub-rewrite-body .pub-solution > summary::before { content: '▶'; font-size: 10px; transition: transform 0.2s; }
|
||
.pub-rewrite-body .pub-solution[open] > summary::before { transform: rotate(90deg); }
|
||
.pub-rewrite-body .pub-solution-body {
|
||
padding: 14px 20px 18px;
|
||
background: var(--pub-solution-bg, rgba(164,113,72,0.04));
|
||
border-top: 1px solid var(--pub-exercise-border, #e0d4c3);
|
||
color: #1a1a1a;
|
||
}
|
||
|
||
/* ─── Étapes tutoriel ────────────────────────────────── */
|
||
.pub-rewrite-body .pub-step {
|
||
display: grid;
|
||
grid-template-columns: 36px 1fr;
|
||
gap: 14px;
|
||
align-items: start;
|
||
margin: 1.5em 0;
|
||
}
|
||
.pub-rewrite-body .pub-step-num {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 50%;
|
||
background: var(--pub-accent, #A47148);
|
||
color: #fff;
|
||
font-weight: 700;
|
||
font-size: 14px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
margin-top: 2px;
|
||
}
|
||
.pub-rewrite-body .pub-step-body p:first-child { margin-top: 0; }
|
||
|
||
/* ─── Définitions / Concepts ─────────────────────────── */
|
||
.pub-rewrite-body .pub-definition {
|
||
margin: 1.5em 0;
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
border: 1px solid var(--pub-definition-border, #ddd);
|
||
}
|
||
.pub-rewrite-body .pub-definition-term {
|
||
padding: 8px 16px;
|
||
background: var(--pub-accent, #A47148);
|
||
color: #fff;
|
||
font-weight: 700;
|
||
font-size: 0.88em;
|
||
letter-spacing: 0.04em;
|
||
}
|
||
.pub-rewrite-body .pub-definition-body {
|
||
padding: 12px 16px;
|
||
background: var(--pub-definition-bg, #fafaf8);
|
||
color: #1a1a1a;
|
||
}
|
||
.pub-rewrite-body .pub-definition-body p { margin: 0.4em 0; color: #1a1a1a; }
|
||
.pub-rewrite-body .pub-definition-body p:first-child { margin-top: 0; }
|
||
|
||
/* ─── Toggle / Collapsible ───────────────────────────── */
|
||
.pub-rewrite-body .pub-toggle {
|
||
border: 1px solid var(--pub-toggle-border, #e5e5e5);
|
||
border-radius: 10px;
|
||
margin: 1.2em 0;
|
||
overflow: hidden;
|
||
}
|
||
.pub-rewrite-body .pub-toggle > .pub-toggle-trigger {
|
||
list-style: none;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 12px 16px;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
font-size: 0.92em;
|
||
background: var(--pub-toggle-header-bg, #f7f7f5);
|
||
user-select: none;
|
||
}
|
||
.pub-rewrite-body .pub-toggle > .pub-toggle-trigger::before {
|
||
content: '▶';
|
||
font-size: 10px;
|
||
color: var(--pub-accent, #A47148);
|
||
transition: transform 0.2s;
|
||
flex-shrink: 0;
|
||
}
|
||
.pub-rewrite-body .pub-toggle[open] > .pub-toggle-trigger::before { transform: rotate(90deg); }
|
||
.pub-rewrite-body .pub-toggle-body {
|
||
padding: 14px 18px;
|
||
border-top: 1px solid var(--pub-toggle-border, #e5e5e5);
|
||
color: #1a1a1a;
|
||
background: #fff;
|
||
}
|
||
.pub-rewrite-body .pub-toggle-body p:first-child { margin-top: 0; }
|
||
|
||
/* ─── Mise en avant ──────────────────────────────────── */
|
||
.pub-rewrite-body .pub-highlight {
|
||
margin: 1.5em 0;
|
||
padding: 16px 20px;
|
||
background: var(--pub-highlight-bg, #fffbf5);
|
||
border-left: 4px solid var(--pub-accent, #A47148);
|
||
border-radius: 0 10px 10px 0;
|
||
font-weight: 600;
|
||
color: #1a1a1a;
|
||
}
|
||
.pub-rewrite-body .pub-highlight p { margin: 0; color: #1a1a1a; }
|
||
|
||
/* ─── Callouts ───────────────────────────────────────── */
|
||
.pub-rewrite-body .pub-callout {
|
||
margin: 1.4em 0;
|
||
padding: 14px 18px 14px 52px;
|
||
border-radius: 10px;
|
||
position: relative;
|
||
font-size: 0.93em;
|
||
line-height: 1.65;
|
||
}
|
||
.pub-rewrite-body .pub-callout::before {
|
||
position: absolute;
|
||
left: 16px;
|
||
top: 14px;
|
||
font-size: 18px;
|
||
}
|
||
.pub-rewrite-body .pub-callout strong {
|
||
display: block;
|
||
font-weight: 700;
|
||
margin-bottom: 4px;
|
||
font-size: 0.9em;
|
||
letter-spacing: 0.04em;
|
||
text-transform: uppercase;
|
||
color: inherit;
|
||
}
|
||
.pub-rewrite-body .pub-callout p { margin: 0.3em 0; color: inherit; }
|
||
.pub-rewrite-body .pub-callout p:first-of-type { margin-top: 0; }
|
||
|
||
.pub-rewrite-body .pub-callout-info {
|
||
background: #eff6ff;
|
||
border: 1px solid #93c5fd;
|
||
color: #1e3a8a;
|
||
}
|
||
.pub-rewrite-body .pub-callout-info::before { content: 'ℹ️'; }
|
||
|
||
.pub-rewrite-body .pub-callout-tip {
|
||
background: #ecfdf5;
|
||
border: 1px solid #6ee7b7;
|
||
color: #065f46;
|
||
}
|
||
.pub-rewrite-body .pub-callout-tip::before { content: '💡'; }
|
||
|
||
.pub-rewrite-body .pub-callout-warning {
|
||
background: #fffbeb;
|
||
border: 1px solid #fbbf24;
|
||
color: #78350f;
|
||
}
|
||
.pub-rewrite-body .pub-callout-warning::before { content: '⚠️'; }
|
||
|
||
/* ─── Checklist ──────────────────────────────────────── */
|
||
.pub-rewrite-body .pub-checklist {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 1em 0;
|
||
}
|
||
.pub-rewrite-body .pub-checklist li {
|
||
padding: 8px 12px 8px 36px;
|
||
position: relative;
|
||
border-radius: 6px;
|
||
margin: 4px 0;
|
||
background: var(--pub-checklist-bg, rgba(0,0,0,0.02));
|
||
color: #1a1a1a;
|
||
}
|
||
.pub-rewrite-body .pub-checklist li::before {
|
||
content: '✓';
|
||
position: absolute;
|
||
left: 10px;
|
||
top: 8px;
|
||
font-weight: 700;
|
||
color: var(--pub-accent, #A47148);
|
||
}
|
||
|
||
/* ─── Code ───────────────────────────────────────────── */
|
||
.pub-rewrite-body .pub-code {
|
||
margin: 1.5em 0;
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
}
|
||
.pub-rewrite-body .pub-code code {
|
||
display: block;
|
||
padding: 20px 22px;
|
||
font-family: 'SF Mono', 'Fira Code', Menlo, Consolas, monospace;
|
||
font-size: 13.5px;
|
||
line-height: 1.65;
|
||
overflow-x: auto;
|
||
}
|
||
`
|