diff --git a/src/parser/KeyNamePlugin.ts b/src/parser/KeyNamePlugin.ts new file mode 100644 index 0000000..7c5fb28 --- /dev/null +++ b/src/parser/KeyNamePlugin.ts @@ -0,0 +1,92 @@ +import { + Decoration, + DecorationSet, + EditorView, + ViewPlugin, + ViewUpdate, + WidgetType, +} from "@codemirror/view"; +import { Extension, RangeSetBuilder, StateField } from "@codemirror/state"; + +export const PO_MSGCTXT_KEY_SEPARATOR = "\u0004"; + +type MsgCtxtRange = { + start: number; + end: number; + msgctxt: string; +}; + +function findMsgCtxt(text: string): MsgCtxtRange | null { + const idx = text.indexOf(PO_MSGCTXT_KEY_SEPARATOR); + if (idx <= 0) return null; + return { start: 0, end: idx + 1, msgctxt: text.slice(0, idx) }; +} + +class MsgCtxtChipWidget extends WidgetType { + constructor(public msgctxt: string) { + super(); + } + + eq(other: WidgetType): boolean { + return other instanceof MsgCtxtChipWidget && other.msgctxt === this.msgctxt; + } + + toDOM(): HTMLElement { + const span = document.createElement("span"); + span.className = "keyname-msgctxt-widget"; + span.textContent = this.msgctxt; + return span; + } + + ignoreEvent(): boolean { + return false; + } +} + +function buildSet(text: string): DecorationSet { + const builder = new RangeSetBuilder(); + const range = findMsgCtxt(text); + if (range) { + builder.add( + range.start, + range.end, + Decoration.replace({ + widget: new MsgCtxtChipWidget(range.msgctxt), + }) + ); + } + return builder.finish(); +} + +export const KeyNamePlugin = (): StateField => { + return StateField.define({ + create() { + return null; + }, + update() { + return null; + }, + provide(): Extension { + return ViewPlugin.fromClass( + class { + decorationSet: DecorationSet; + constructor(view: EditorView) { + this.decorationSet = buildSet(view.state.doc.toString()); + } + update(change: ViewUpdate) { + if (change.docChanged || change.viewportChanged) { + this.decorationSet = buildSet(change.state.doc.toString()); + } + } + }, + { + decorations: (instance) => instance.decorationSet, + provide: (plugin) => + EditorView.atomicRanges.of((view) => { + return view.plugin(plugin)?.decorationSet || Decoration.none; + }), + } + ); + }, + }); +}; diff --git a/src/parser/keyNameStyle.ts b/src/parser/keyNameStyle.ts new file mode 100644 index 0000000..53b116c --- /dev/null +++ b/src/parser/keyNameStyle.ts @@ -0,0 +1,45 @@ +import { StyledComponent } from "@emotion/styled"; + +export type KeyNameChipColors = { + border: string; + background: string; + text: string; +}; + +const DEFAULT_COLORS: KeyNameChipColors = { + border: "#BBC2CB", + background: "#F0F2F4", + text: "#4D5B6E", +}; + +type Props = { + styled: (component: any) => any; + colors?: KeyNameChipColors; + component?: any; +}; + +export const generateKeyNameStyle = ({ + styled, + colors = DEFAULT_COLORS, + component = "div", +}: Props): StyledComponent => { + return styled(component)` + & .keyname-msgctxt-widget { + display: inline-flex; + vertical-align: text-top; + align-items: center; + justify-content: center; + min-width: 15px; + border: 1px solid ${colors.border}; + background-color: ${colors.background}; + color: ${colors.text}; + border-radius: 10px; + padding: 0px 7px; + font-size: 12px; + user-select: none; + margin: 0px 4px 0px 1px; + overflow: hidden; + font-family: monospace; + } + `; +}; diff --git a/src/tolgee-editor.ts b/src/tolgee-editor.ts index 7fafc39..2328f4c 100644 --- a/src/tolgee-editor.ts +++ b/src/tolgee-editor.ts @@ -14,3 +14,8 @@ export { getPluralVariants, getVariantExample } from "./utils/pluralTools"; export { getLanguageDirection } from "./utils/getLanguageDirection"; export { getPluralRules, selectPluralRule } from "./utils/plurals"; export * from "./parser/types"; +export { + KeyNamePlugin, + PO_MSGCTXT_KEY_SEPARATOR, +} from "./parser/KeyNamePlugin"; +export { generateKeyNameStyle } from "./parser/keyNameStyle";