Archived
Merge pull request #28 from TheOneironaut/chat-bidi-rtl-support
Add BiDi support to chat text
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@jmfederico/pi-web": patch
|
||||
---
|
||||
|
||||
Improve chat, prompt, and session text rendering for RTL and mixed-direction content.
|
||||
@@ -55,6 +55,7 @@ export class CodeViewer extends LitElement {
|
||||
EditorView.editable.of(false),
|
||||
EditorView.lineWrapping,
|
||||
viewerTheme,
|
||||
...bidiTextExtensions(this.language),
|
||||
...languageExtensions(this.language),
|
||||
],
|
||||
}),
|
||||
@@ -97,6 +98,19 @@ const viewerTheme = EditorView.theme({
|
||||
},
|
||||
});
|
||||
|
||||
const bidiTextTheme = EditorView.theme({
|
||||
".cm-content": {
|
||||
textAlign: "start",
|
||||
},
|
||||
".cm-line": {
|
||||
unicodeBidi: "plaintext",
|
||||
},
|
||||
});
|
||||
|
||||
function bidiTextExtensions(language: string | undefined): Extension[] {
|
||||
return language === "markdown" ? [EditorView.contentAttributes.of({ dir: "auto" }), bidiTextTheme] : [];
|
||||
}
|
||||
|
||||
function languageExtensions(language: string | undefined): Extension[] {
|
||||
if (language === undefined) return [];
|
||||
switch (language) {
|
||||
|
||||
@@ -9,7 +9,7 @@ export class FormattedText extends LitElement {
|
||||
@property() text = "";
|
||||
|
||||
override render() {
|
||||
return html`<div class="formatted" @click=${this.onFormattedClick}>${unsafeHTML(toSafeMarkdownHtml(this.text))}</div>`;
|
||||
return html`<div class="formatted" dir="auto" @click=${this.onFormattedClick}>${unsafeHTML(toSafeMarkdownHtml(this.text))}</div>`;
|
||||
}
|
||||
|
||||
override updated(): void {
|
||||
|
||||
@@ -500,6 +500,7 @@ const proseInputAssistanceAttributes: Record<string, string> = {
|
||||
autocorrect: "on",
|
||||
autocapitalize: "sentences",
|
||||
writingsuggestions: "true",
|
||||
dir: "auto",
|
||||
};
|
||||
|
||||
const codeLikeInputAssistanceAttributes: Record<string, string> = {
|
||||
@@ -507,6 +508,7 @@ const codeLikeInputAssistanceAttributes: Record<string, string> = {
|
||||
autocorrect: "off",
|
||||
autocapitalize: "off",
|
||||
writingsuggestions: "false",
|
||||
dir: "auto",
|
||||
};
|
||||
|
||||
function inputAssistanceContentAttributes(draftBeforeCursor: string): Record<string, string> {
|
||||
|
||||
@@ -134,7 +134,7 @@ export class SessionList extends LitElement implements KeyboardNavigableSection
|
||||
const selectedTitle = this.selected?.path ?? selectedSummary;
|
||||
return html`
|
||||
<h2>
|
||||
<button class="section-toggle" aria-expanded=${String(!this.collapsed)} @click=${() => { this.onToggleCollapsed?.(); }}><span class="section-title"><span class="section-name">${this.collapsed ? "▸" : "▾"} Sessions</span>${this.collapsed ? html`<small class="section-selected" title=${selectedTitle}>${selectedSummary}</small>` : null}</span></button>
|
||||
<button class="section-toggle" aria-expanded=${String(!this.collapsed)} @click=${() => { this.onToggleCollapsed?.(); }}><span class="section-title"><span class="section-name">${this.collapsed ? "▸" : "▾"} Sessions</span>${this.collapsed ? html`<small class="section-selected" dir="auto" title=${selectedTitle}>${selectedSummary}</small>` : null}</span></button>
|
||||
${this.renderCurrentSelectionButton(currentSessions)}
|
||||
<small class="section-count">${sessionCount}</small>
|
||||
<button ?disabled=${!this.canStart} @click=${(event: MouseEvent) => { event.stopPropagation(); this.onStart?.(); }}>+</button>
|
||||
@@ -213,7 +213,7 @@ export class SessionList extends LitElement implements KeyboardNavigableSection
|
||||
>
|
||||
<div class="action-main ${selectionActive ? "selecting" : ""}">
|
||||
${showsCheckbox ? html`<input class="session-checkbox" type="checkbox" aria-label=${`Select ${sessionLabel(session)}`} .checked=${bulkSelected} @click=${(event: MouseEvent) => { event.stopPropagation(); }} @change=${() => { this.toggleSelected(session.id); }}>` : null}
|
||||
<span class="action-name">${row.depth > 0 ? html`<span class="tree-marker">↳</span>` : null}${sessionLabel(session)}${row.depth > 2 ? html` <span class="badge">depth ${row.depth}</span>` : null}${row.hasMissingParent ? html` <span class="badge">parent unavailable</span>` : null}</span><small>${this.renderSessionMetaPrefix(session)}${String(session.messageCount)} messages</small>
|
||||
<span class="action-name" dir="auto">${row.depth > 0 ? html`<span class="tree-marker">↳</span>` : null}${sessionLabel(session)}${row.depth > 2 ? html` <span class="badge">depth ${row.depth}</span>` : null}${row.hasMissingParent ? html` <span class="badge">parent unavailable</span>` : null}</span><small>${this.renderSessionMetaPrefix(session)}${String(session.messageCount)} messages</small>
|
||||
${this.renderActivity(session)}
|
||||
</div>
|
||||
<div class="action-menu">
|
||||
@@ -375,6 +375,7 @@ export class SessionList extends LitElement implements KeyboardNavigableSection
|
||||
.bulk-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0 0 6px; }
|
||||
.bulk-row button { padding: 5px 7px; font-size: 12px; }
|
||||
.bulk-row small { display: inline; min-width: 0; color: var(--pi-muted); }
|
||||
.action-name, .section-selected { text-align: start; unicode-bidi: plaintext; }
|
||||
.bulk-row .capability-hint { flex: 1 0 100%; color: var(--pi-warning); }
|
||||
.bulk-row.selecting { padding: 6px; border: 1px solid var(--pi-border-muted); border-radius: 8px; background: color-mix(in srgb, var(--pi-surface) 65%, transparent); }
|
||||
button.danger, .action-menu-panel button.danger { color: var(--pi-danger); }
|
||||
|
||||
@@ -345,6 +345,7 @@ export const chatStyles = css`
|
||||
.msg-meta:focus::before, .msg-meta.expanded::before { content: ""; }
|
||||
}
|
||||
formatted-text.part { display: block; }
|
||||
formatted-text.part, .queued-message formatted-text { text-align: start; unicode-bidi: plaintext; }
|
||||
.part { max-width: 100%; min-width: 0; box-sizing: border-box; overflow: visible; }
|
||||
.part + .part { margin-top: 10px; }
|
||||
.tool-line { color: var(--pi-warning); }
|
||||
@@ -355,22 +356,22 @@ export const chatStyles = css`
|
||||
.skill-invocation > summary, .skill-read > strong { color: var(--pi-purple); }
|
||||
.skill-invocation > small, .skill-read > small { display: block; margin: 6px 0 0; color: var(--pi-muted); }
|
||||
summary { cursor: pointer; color: var(--pi-muted); }
|
||||
pre { margin: 6px 0 0; white-space: pre-wrap; overflow-wrap: anywhere; font: inherit; }
|
||||
.shell-output { color: var(--pi-text); font: 13px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; line-height: 1.45; }
|
||||
pre { margin: 6px 0 0; white-space: pre-wrap; overflow-wrap: anywhere; font: inherit; direction: ltr; text-align: left; unicode-bidi: isolate; }
|
||||
.shell-output { color: var(--pi-text); font: 13px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; line-height: 1.45; direction: ltr; text-align: left; unicode-bidi: isolate; }
|
||||
@keyframes pulse { 0%, 100% { transform: scale(.75); opacity: .55; } 50% { transform: scale(1.2); opacity: 1; } }
|
||||
`;
|
||||
|
||||
export const formattedTextStyles = css`
|
||||
:host { display: block; }
|
||||
.formatted { white-space: normal; overflow-wrap: anywhere; line-height: 1.45; }
|
||||
.formatted { white-space: normal; overflow-wrap: anywhere; line-height: 1.45; text-align: start; unicode-bidi: plaintext; }
|
||||
p, ul, ol, pre, blockquote, table, .code-block-wrapper { margin: 0 0 10px; }
|
||||
:is(p, ul, ol, pre, blockquote, table, .code-block-wrapper):last-child { margin-bottom: 0; }
|
||||
ul, ol { padding-left: 22px; }
|
||||
li + li { margin-top: 3px; }
|
||||
code { border: 1px solid var(--pi-border); border-radius: 4px; background: var(--pi-bg); padding: 1px 4px; font: 13px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
||||
code { border: 1px solid var(--pi-border); border-radius: 4px; background: var(--pi-bg); padding: 1px 4px; font: 13px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; direction: ltr; text-align: left; unicode-bidi: isolate; }
|
||||
.code-block-wrapper { position: relative; }
|
||||
.code-block-wrapper pre { margin: 0; padding-right: 40px; }
|
||||
pre { border: 1px solid var(--pi-border); border-radius: 8px; background: var(--pi-bg); padding: 10px; overflow-x: auto; overflow-y: hidden; }
|
||||
pre { border: 1px solid var(--pi-border); border-radius: 8px; background: var(--pi-bg); padding: 10px; overflow-x: auto; overflow-y: hidden; direction: ltr; text-align: left; unicode-bidi: isolate; }
|
||||
pre code { border: 0; padding: 0; background: transparent; }
|
||||
.code-copy-button { position: absolute; top: 6px; right: 6px; z-index: 1; display: inline-grid; place-items: center; width: 24px; height: 24px; border: 1px solid var(--pi-border); border-radius: 6px; background: var(--pi-surface); color: var(--pi-muted); padding: 0; font: 14px system-ui, sans-serif; line-height: 1; cursor: pointer; }
|
||||
.code-copy-button:hover, .code-copy-button:focus { color: var(--pi-text); border-color: var(--pi-accent); }
|
||||
@@ -466,8 +467,8 @@ export const promptEditorStyles = css`
|
||||
textarea, .markdown-editor .cm-editor { box-sizing: border-box; width: 100%; min-height: 54px; max-height: 220px; resize: none; overflow: hidden; border-radius: 8px; border: 1px solid var(--pi-border); background: var(--pi-bg); color: var(--pi-text); font: 16px/1.4 system-ui, sans-serif; }
|
||||
textarea { overflow-y: auto; padding: 8px; }
|
||||
.markdown-editor .cm-scroller { max-height: 220px; overflow-y: auto; font-family: system-ui, sans-serif; line-height: 1.4; }
|
||||
.markdown-editor .cm-content { min-height: 38px; padding: 8px 44px 8px 8px; caret-color: var(--pi-text); }
|
||||
.markdown-editor .cm-line { padding: 0; }
|
||||
.markdown-editor .cm-content { min-height: 38px; padding: 8px 44px 8px 8px; caret-color: var(--pi-text); text-align: start; unicode-bidi: plaintext; }
|
||||
.markdown-editor .cm-line { padding: 0; unicode-bidi: plaintext; }
|
||||
.markdown-editor .cm-placeholder { color: var(--pi-dim); }
|
||||
.markdown-editor .cm-focused { outline: none; }
|
||||
.shell-mode textarea, .shell-mode .markdown-editor .cm-editor { border-color: var(--pi-success); box-shadow: 0 0 0 1px var(--pi-success-ring); }
|
||||
|
||||
Reference in New Issue
Block a user