Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ jobs:
- run: pnpm install --frozen-lockfile

- name: Install Playwright browsers
run: pnpm dlx playwright install --with-deps chromium
# Use the project-pinned Playwright (pnpm exec), not `pnpm dlx` which
# fetches the latest Playwright and installs mismatched browser
# revisions, leaving the pinned version's binary missing.
run: pnpm exec playwright install --with-deps chromium

- name: Run tests
run: pnpm test
Expand Down
22 changes: 13 additions & 9 deletions src/EpButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export class EpButton extends LitElement {
line-height: 1.5;
border: none;
outline: none;
/* Reset the UA button background so it doesn't leak through variants
that set their own (default/ghost/icon were showing the browser's
light ButtonFace, which looked broken in dark themes). */
background: transparent;
display: inline-flex;
width: 100%;
height: 100%;
Expand Down Expand Up @@ -51,21 +55,21 @@ export class EpButton extends LitElement {
background: var(--bg-soft-color, #f2f3f4);
}

/* Primary */
/* Primary — matches Etherpad colibris .btn-primary: primary-coloured
background with bg-coloured (white) text. */
:host([variant="primary"]) button {
background: var(--text-color, #586a69);
color: var(--primary-color, #64d29b);
background: var(--primary-color, #64d29b);
color: var(--bg-color, #ffffff);
border: none;
transition: .2s background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
transition: filter 0.15s ease, opacity 0.15s ease;
}

:host([variant="primary"]) button:active {
box-shadow: var(--primary-button-active,inset 0 1px 12px rgba(0, 0, 0, 0.9));
background: var(--primary-button-active, #444);
:host([variant="primary"]) button:hover {
filter: brightness(0.94);
}

:host([variant="primary"]) button:hover {
background: var(--dark-color, #4a5d5c);
:host([variant="primary"]) button:active {
filter: brightness(0.88);
}

/* Ghost */
Expand Down
35 changes: 12 additions & 23 deletions src/EpChatMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,39 @@ import { customElement, property } from 'lit/decorators.js';
@customElement('ep-chat-message')
export class EpChatMessage extends LitElement {
static styles = css`
/* Matches Etherpad colibris chat: a plain message line (#chattext p),
padding 4px 10px, bold author, muted inline time, then the text. No
bubbles — Etherpad does not style own vs other messages differently. */
:host {
--ep-font: var(--main-font-family, Quicksand, Cantarell, "Open Sans", "Helvetica Neue", sans-serif);
display: block;
font-family: var(--ep-font);
font-size: 14px;
line-height: 1.5;
color: var(--text-color, #485365);
padding: 6px 10px;
padding: 4px 10px;
word-wrap: break-word;
}

:host(:first-child) { padding-top: 10px; }
:host(:last-child) { padding-bottom: 10px; }

.header {
display: flex;
align-items: baseline;
gap: 8px;
margin-bottom: 2px;
}

.author {
font-weight: 700;
font-size: 13px;
font-weight: bold;
}

.time {
font-size: 11px;
color: var(--text-soft-color, #576273);
margin: 0 4px 0 6px;
}

.body {
line-height: 1.5;
word-wrap: break-word;
}
.body { display: inline; }

/* authorColors mode: Etherpad tints the whole message with the author
colour. Opt-in via the own flag to keep the default view plain. */
:host([own]) {
background: var(--bg-soft-color, #f2f3f4);
border-radius: 4px;
margin: 2px 0;
}
`;

Expand All @@ -52,13 +47,7 @@ export class EpChatMessage extends LitElement {

render() {
return html`
<div class="header">
<span class="author" style="${this.authorColor ? `color: ${this.authorColor}` : ''}">${this.author}</span>
${this.time ? html`<span class="time">${this.time}</span>` : ''}
</div>
<div class="body">
<slot></slot>
</div>
<b class="author" style="${this.authorColor ? `color: ${this.authorColor}` : ''}">${this.author}</b>${this.time ? html`<span class="time">${this.time}</span>` : html`<span class="time"></span>`}<span class="body"><slot></slot></span>
`;
}
}
Expand Down
25 changes: 18 additions & 7 deletions src/EpCheckbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,17 @@ export class EpCheckbox extends LitElement {
pointer-events: none;
}

/* Etherpad colibris toggle: a light, outlined track (bg-soft fill with a
text-soft border) that switches to a primary-coloured border when
checked. */
.track {
position: relative;
background: var(--middle-color, #d2d2d2);
box-sizing: border-box;
background: var(--bg-soft-color, #f2f3f4);
border: 2px solid var(--text-soft-color, #576273);
border-radius: 10px;
transition: background 0.2s ease;
opacity: 0.7;
transition: border-color 0.2s ease, opacity 0.2s ease;
flex-shrink: 0;
}

Expand All @@ -41,20 +47,25 @@ export class EpCheckbox extends LitElement {
}

:host([checked]) .track {
background: var(--text-color, #64d29b);
background: transparent;
border-color: var(--primary-color, #64d29b);
opacity: 1;
}

.thumb {
position: absolute;
top: 50%;
width: 16px;
height: 16px;
border-radius: 50%;
background: white;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
transition: transform 0.2s ease;
background: var(--text-soft-color, #576273);
transition: transform 0.2s ease, background 0.2s ease;
transform: translateY(-50%);
}

:host([checked]) .thumb {
background: var(--primary-color, #64d29b);
}

:host([variant="default"]) .thumb {
left: 2px;
Expand Down
7 changes: 5 additions & 2 deletions src/EpColorPicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ const isLightColor = (color: string): boolean => {
}
};

// Etherpad's author colour palette (the pastel tints from
// AuthorManager.getColorPalette) — the same swatches Etherpad offers when
// picking your author colour.
const DEFAULT_COLORS = [
'black', 'red', 'green', 'blue', 'yellow', 'orange',
'purple', 'pink', 'brown', 'gray', 'white', 'cyan',
'#ffc7c7', '#fff1c7', '#e3ffc7', '#c7ffd5', '#c7ffff', '#c7d5ff', '#e3c7ff', '#ffc7f1',
'#ffa8a8', '#ffe699', '#cfff9e', '#99ffb3', '#a3ffff', '#99b3ff', '#cc99ff', '#ff99e5',
];

@customElement('ep-color-picker')
Expand Down
101 changes: 45 additions & 56 deletions src/EpEditor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LitElement, html, css } from 'lit';
import { LitElement, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { AceEditor } from './editor/AceEditor.js';
import type AttributePool from './editor/AttributePool.js';
Expand All @@ -16,60 +16,14 @@ import type AttributePool from './editor/AttributePool.js';
*/
@customElement('ep-editor')
export class EpEditor extends LitElement {
static styles = css`
:host {
display: block;
position: relative;
min-height: 100px;
}

.ep-editor-container {
width: 100%;
height: 100%;
min-height: inherit;
overflow: auto;
font-family: var(--ep-editor-font, monospace);
font-size: var(--ep-editor-font-size, 14px);
line-height: var(--ep-editor-line-height, 1.6);
color: var(--ep-editor-color, #333);
background: var(--ep-editor-bg, #fff);
padding: var(--ep-editor-padding, 8px 12px);
box-sizing: border-box;
outline: none;
white-space: pre-wrap;
word-wrap: break-word;
}

.ep-editor-container:focus {
outline: none;
}

.ep-editor-container .list-bullet1 { list-style-type: disc; }
.ep-editor-container .list-bullet2 { list-style-type: circle; }
.ep-editor-container .list-bullet3 { list-style-type: square; }
.ep-editor-container .list-bullet4 { list-style-type: disc; }
.ep-editor-container .list-number1 { list-style-type: decimal; }
.ep-editor-container .list-number2 { list-style-type: lower-alpha; }
.ep-editor-container .list-number3 { list-style-type: lower-roman; }
.ep-editor-container .list-number4 { list-style-type: decimal; }

.ep-editor-container ul, .ep-editor-container ol {
padding-left: 1.5em;
margin: 0;
}

.ep-editor-container .tag\\:b, .ep-editor-container b { font-weight: bold; }
.ep-editor-container .tag\\:i, .ep-editor-container i { font-style: italic; }
.ep-editor-container .tag\\:u, .ep-editor-container u { text-decoration: underline; }
.ep-editor-container .tag\\:s, .ep-editor-container s { text-decoration: line-through; }

.ep-editor-container a { color: var(--ep-editor-link-color, #0366d6); text-decoration: underline; }

:host([readonly]) .ep-editor-container {
opacity: 0.85;
cursor: default;
}
`;
// Render into the light DOM, NOT a shadow root. Etherpad's Ace engine reads
// and restores the caret via the document Selection API, which does not work
// reliably across a shadow boundary (the caret retargets to the host and
// every keystroke inserts at offset 0, reversing the text). In light DOM the
// engine uses the standard, well-tested selection path.
protected createRenderRoot() {
return this;
}

/** Initial text content for the editor. */
@property({ type: String }) content = '';
Expand All @@ -95,7 +49,7 @@ export class EpEditor extends LitElement {
// ── Lifecycle ──────────────────────────────────────────────

protected firstUpdated() {
const container = this.shadowRoot!.querySelector('.ep-editor-container') as HTMLElement;
const container = this.renderRoot.querySelector('.ep-editor-container') as HTMLElement;
if (!container) return;

this._editor = new AceEditor(container);
Expand Down Expand Up @@ -294,6 +248,41 @@ export class EpEditor extends LitElement {

protected render() {
return html`
<style>
ep-editor { display: block; position: relative; min-height: 100px; }
ep-editor .ep-editor-container {
width: 100%;
height: 100%;
min-height: inherit;
overflow: auto;
font-family: var(--ep-editor-font, var(--main-font-family, Quicksand, Cantarell, "Open Sans", "Helvetica Neue", sans-serif));
font-size: var(--ep-editor-font-size, 14px);
line-height: var(--ep-editor-line-height, 1.6);
color: var(--ep-editor-color, var(--text-color, #485365));
background: var(--ep-editor-bg, var(--bg-color, #fff));
padding: var(--ep-editor-padding, 8px 12px);
box-sizing: border-box;
outline: none;
white-space: pre-wrap;
word-wrap: break-word;
}
ep-editor .ep-editor-container:focus { outline: none; }
ep-editor .ep-editor-container .list-bullet1 { list-style-type: disc; }
ep-editor .ep-editor-container .list-bullet2 { list-style-type: circle; }
ep-editor .ep-editor-container .list-bullet3 { list-style-type: square; }
ep-editor .ep-editor-container .list-bullet4 { list-style-type: disc; }
ep-editor .ep-editor-container .list-number1 { list-style-type: decimal; }
ep-editor .ep-editor-container .list-number2 { list-style-type: lower-alpha; }
ep-editor .ep-editor-container .list-number3 { list-style-type: lower-roman; }
ep-editor .ep-editor-container .list-number4 { list-style-type: decimal; }
ep-editor .ep-editor-container ul, ep-editor .ep-editor-container ol { padding-left: 1.5em; margin: 0; }
ep-editor .ep-editor-container .tag\\:b, ep-editor .ep-editor-container b { font-weight: bold; }
ep-editor .ep-editor-container .tag\\:i, ep-editor .ep-editor-container i { font-style: italic; }
ep-editor .ep-editor-container .tag\\:u, ep-editor .ep-editor-container u { text-decoration: underline; }
ep-editor .ep-editor-container .tag\\:s, ep-editor .ep-editor-container s { text-decoration: line-through; }
ep-editor .ep-editor-container a { color: var(--ep-editor-link-color, #2e96f3); text-decoration: underline; }
ep-editor[readonly] .ep-editor-container { opacity: 0.85; cursor: default; }
</style>
<div
class="ep-editor-container"
contenteditable="${this.readonly ? 'false' : 'true'}"
Expand Down
4 changes: 2 additions & 2 deletions src/EpInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ export class EpInput extends LitElement {

:host([error]) input,
:host([error]) textarea {
border-color: #d9534f;
border-color: #d1242f;
}

.error-text {
font-family: var(--ep-font);
font-size: 12px;
color: #d9534f;
color: #d1242f;
margin-top: 4px;
}
`;
Expand Down
4 changes: 3 additions & 1 deletion src/EpModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ export class EpModal extends LitElement {
:host([open]) { display: flex; }
.overlay {
position: fixed; inset: 0;
background: rgba(72, 83, 101, 0.3);
/* Matches Etherpad's dialog backdrop (#settings-dialog::backdrop). */
background: rgba(0, 0, 0, 0.45);
backdrop-filter: blur(2px);
animation: ep-modal-fade-in 0.15s ease;
}
.dialog {
Expand Down
4 changes: 2 additions & 2 deletions src/EpNotification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export class EpNotification extends LitElement {
}

:host([type="error"]) .notification {
border-left-color: #d9534f;
border-left-color: #d1242f;
}

:host([type="info"]) .notification {
Expand All @@ -105,7 +105,7 @@ export class EpNotification extends LitElement {
}

:host([type="success"]) .icon { color: var(--primary-color, #64d29b); }
:host([type="error"]) .icon { color: #d9534f; }
:host([type="error"]) .icon { color: #d1242f; }
:host([type="info"]) .icon { color: var(--dark-color, #576273); }

.body { flex: 1; min-width: 0; word-wrap: break-word; }
Expand Down
4 changes: 2 additions & 2 deletions src/EpToast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface ToastOptions {

const toastIconSvg: Record<string, string> = {
success: `<svg class="icon" viewBox="0 0 16 16" fill="var(--primary-color, #64d29b)"><path fill-rule="evenodd" d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"/></svg>`,
error: `<svg class="icon" viewBox="0 0 16 16" fill="#d9534f"><path fill-rule="evenodd" d="M8 15A7 7 0 108 1a7 7 0 000 14zm.75-9.25a.75.75 0 00-1.5 0v4.5a.75.75 0 001.5 0v-4.5zM8 11a1 1 0 100 2 1 1 0 000-2z"/></svg>`,
error: `<svg class="icon" viewBox="0 0 16 16" fill="#d1242f"><path fill-rule="evenodd" d="M8 15A7 7 0 108 1a7 7 0 000 14zm.75-9.25a.75.75 0 00-1.5 0v4.5a.75.75 0 001.5 0v-4.5zM8 11a1 1 0 100 2 1 1 0 000-2z"/></svg>`,
info: `<svg class="icon" viewBox="0 0 16 16" fill="var(--dark-color, #576273)"><path fill-rule="evenodd" d="M8 15A7 7 0 108 1a7 7 0 000 14zm.75-9.25a.75.75 0 00-1.5 0v4.5a.75.75 0 001.5 0v-4.5zM8 11a1 1 0 100 2 1 1 0 000-2z"/></svg>`,
};

Expand Down Expand Up @@ -61,7 +61,7 @@ export class EpToastItem extends LitElement {
}

:host([type="success"]) .toast { border-left-color: var(--primary-color, #64d29b); }
:host([type="error"]) .toast { border-left-color: #d9534f; }
:host([type="error"]) .toast { border-left-color: #d1242f; }
:host([type="info"]) .toast { border-left-color: var(--dark-color, #576273); }

.icon { flex-shrink: 0; width: 16px; height: 16px; margin-top: 2px; }
Expand Down
Loading
Loading