v3.0.0

<bmx-yaml-editor>

Configuration files edited in the page: YAML (1.2, or 1.1), TOML, INI, Java .properties and .env, as text (the library's code editor with colouring, folding and completion) or as a tree (edit values and keys in place, add, move, duplicate, comment out and delete), and compared with another version.

28 properties · 4 events · 18 methods · 10 parts

Example

Try it with your own configuration files in confidence: they are opened by your browser, on your device. Nothing is uploaded, no copy reaches us or anyone else, and nothing is kept once you close this tab.

Checking...

Show markup
<bmx-yaml-editor id="ex-ye" label="Deployment manifest" name="manifest" file-name="harbour-shop.yaml" compare-label="As deployed on Monday" description="Each document is checked against the schema for its kind as you edit. Try replicas: 0, or press Ctrl+Space on a new line under spec to see what can go there."></bmx-yaml-editor>
<p class="note" id="ex-ye-out" role="status" style="margin: 12px 0 0">Checking...</p>

<script type="module">
  await customElements.whenDefined('bmx-yaml-editor');
  const editor = document.getElementById('ex-ye');
  const out = document.getElementById('ex-ye-out');

  // Made-up schemas for a made-up shop's three kinds of document, chosen per document by its `kind`.
  const metadata = {
    type: 'object',
    required: ['name'],
    properties: {
      name: { type: 'string', pattern: '^[a-z0-9-]+$', description: 'Lower-case letters, digits and dashes.' },
      labels: { type: 'object', additionalProperties: { type: 'string' } },
    },
  };
  const deployment = {
    type: 'object',
    required: ['apiVersion', 'kind', 'metadata', 'spec'],
    properties: {
      apiVersion: { const: 'apps/v1' },
      kind: { const: 'Deployment' },
      metadata,
      spec: {
        type: 'object',
        required: ['replicas', 'template'],
        properties: {
          replicas: { type: 'integer', minimum: 1, maximum: 20, description: 'How many copies of the shop run at once.' },
          strategy: { enum: ['RollingUpdate', 'Recreate'], description: 'How new versions replace old ones.' },
          template: {
            type: 'object',
            properties: {
              metadata: { type: 'object', properties: { labels: metadata.properties.labels } },
              spec: {
                type: 'object',
                properties: {
                  containers: {
                    type: 'array',
                    minItems: 1,
                    items: {
                      type: 'object',
                      required: ['name', 'image'],
                      properties: {
                        name: { type: 'string' },
                        image: { type: 'string', pattern: ':[\\w.-]+$', description: 'An image with its version tag.' },
                        ports: { type: 'array', items: { type: 'object', properties: { containerPort: { type: 'integer', minimum: 1, maximum: 65535 } } } },
                        env: { type: 'array', items: { type: 'object', required: ['name'], properties: { name: { type: 'string' }, value: { type: 'string' } } } },
                        resources: { type: 'object', properties: { memory: { type: 'string' }, cpu: { type: 'string' } } },
                      },
                    },
                  },
                },
              },
            },
          },
        },
      },
    },
  };
  const service = {
    type: 'object',
    required: ['kind', 'metadata', 'spec'],
    properties: {
      kind: { const: 'Service' },
      metadata,
      spec: {
        type: 'object',
        properties: {
          type: { enum: ['ClusterIP', 'NodePort', 'LoadBalancer'] },
          ports: { type: 'array', items: { type: 'object', required: ['port'], properties: { port: { type: 'integer', maximum: 65535 }, targetPort: { type: 'integer' } } } },
        },
      },
    },
  };
  const settings = {
    type: 'object',
    properties: { kind: { const: 'ConfigMap' }, metadata, data: { type: 'object', additionalProperties: { type: 'string' } } },
  };

  const deployed = `# Harbour Shop - the web tier and its settings.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: harbour-shop
  labels: &labels
    app: harbour-shop
    tier: web
spec:
  replicas: 2
  strategy: RollingUpdate
  template:
    metadata:
      labels: *labels
    spec:
      containers:
        - name: shop
          image: registry.example.test/harbour-shop:4.1.0
          ports:
            - containerPort: 8080
          env:
            - name: CURRENCY
              value: GBP
            - name: CHECKOUT_TIMEOUT
              value: "30"   # seconds, as text
          resources: &small
            memory: 256Mi
            cpu: 250m
---
apiVersion: v1
kind: Service
metadata:
  name: harbour-shop
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 8080
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: harbour-shop-settings
data:
  theme: harbour
  # YAML 1.1 tools read the next value as false.
  maintenance: off
`;

  editor.schema = [
    { match: { kind: 'Deployment' }, schema: deployment, name: 'Deployment' },
    { match: { kind: 'Service' }, schema: service, name: 'Service' },
    { match: { kind: 'ConfigMap' }, schema: settings, name: 'Settings' },
  ];
  // Now: one more copy running, and the next version of the image.
  editor.value = deployed.replace('replicas: 2', 'replicas: 3').replace('harbour-shop:4.1.0', 'harbour-shop:4.2.0');
  editor.compareWith = deployed;

  const report = ({ ok, valid, problems }) => {
    const errors = problems.filter(p => p.severity === 'error');
    if (!ok) out.textContent = `Not valid YAML: ${errors[0]?.message ?? ''}`;
    else if (!valid) out.textContent = `${errors.length} problem${errors.length === 1 ? '' : 's'} against the schemas: ${errors[0].message}`;
    else out.textContent = `Valid against each document's schema${problems.length ? ` (${problems.length} note${problems.length === 1 ? '' : 's'})` : ''} - ready to deploy.`;
  };
  editor.addEventListener('bmxValidate', event => report(event.detail));
  await editor.validate();
</script>
<bmx-yaml-editor label="Deployment" name="manifest"></bmx-yaml-editor>
<script>
  editor.schema = [
    { match: { kind: 'Deployment' }, schema: deploymentSchema },
    { match: { kind: 'Service' }, schema: serviceSchema },
  ];
  editor.value = manifestText;
</script>

Every mistake in the text is underlined where it is, in plain words, and each document is checked against its JSON Schema - one schema for all, or one chosen per document by the values it holds (kind: Deployment) or the schema it names in a # yaml-language-server: $schema= comment. Completion offers the keys a mapping can still have, required ones first, and the values a key allows. Plain values that YAML 1.1 tools read differently (no, on, 012) are pointed out.

Edits in the tree change only the characters they must, so comments, quoting, anchors and blank lines stay as they were; Format lays a YAML file out again with one indentation step and keeps every comment. Anchors, aliases and merge keys are shown and followed. Every edit can be refused (cancel bmxEdit) and undone. Convert, copy or save as YAML, JSON, TOML, INI, .properties or .env.

Nothing leaves the page: schemas are what you give (schema, or schema-src for the page's own copy) and files are opened from the device. In a form, name receives the text; require-valid keeps the form from submitting until every document is valid.

Tree keyboard: arrows move and open/close, Home/End, Page Up/Down, Enter edits a value (or opens a mapping), F2 renames, Delete deletes, Insert adds, Alt+Up/Down move, Ctrl+D duplicates, Ctrl+C copies the entry as YAML, Shift+F10 or the context-menu key opens actions.

Properties

PropertyAttributeTypeDefaultDescription
checking checking boolean true Check the text as it changes (its own rules always; the schema when there is one).
compareLabel compare-label string — What the original side of the comparison is called.
compareWith compare-with string — Another version of the text, for the Compare view.
description description string — Help text below the editor.
disabled disabled boolean false Disable the editor.
errorText error-text string — An error supplied by the page.
expandDepth expand-depth number 2 How many levels of the tree start open.
fileName file-name string — The file's name: chooses the format when it is auto, and names saved files.
format format BmxYamlEditorFormat 'auto' How the text is read: yaml, toml, ini, properties, env, or auto (from file-name, else the text).
hideLabel hide-label boolean false Hide the label visually while keeping it as the accessible name.
hideStatus hide-status boolean false Hide the status bar.
hideToolbar hide-toolbar boolean false Hide the toolbar.
indent indent number — Spaces per level for new and formatted YAML. Default: what the file uses.
label label string — The field's label: names the whole control.
name name string — The field's name in a form: it receives the text.
readonly readonly boolean false Allow reading, searching and copying, but not changing.
requireValid require-valid boolean false Keep the form from submitting until the text has no mistakes and every document is valid.
schema schema BmxYamlSchemas | string — The JSON Schema for every document, or rules that choose one per document: [{ match: { kind: 'Service' }, schema }, { uri: 'app.json', schema }, { schema }].
schemaMessages schema-messages Record<string, string> | string — Schema messages in place of the English ones, by keyword (required, type, enum, ...), as an object or JSON.
schemaSrc schema-src string — An address of a JSON (or YAML) schema to fetch from the page's own server.
sequenceIndent sequence-indent boolean — Indent lists under their keys (key:\n - a). Default: what the file does.
src src string — An address to load the text from (the page's own server).
strings strings Partial<BmxYamlEditorStrings> | string — Words to show instead of the English ones, as an object or JSON.
value value string '' The text. Two-way.
view view BmxYamlEditorView 'text' The view shown. Two-way.
views views BmxYamlEditorView[] | string — The views offered, as an array, JSON or a comma list. Default: text and tree, and compare when compare-with is set.
yaml11Warnings yaml-1-1-warnings boolean true Point out plain values YAML 1.1 tools read differently (no, on, 012).
yamlVersion yaml-version '1.1' | '1.2' '1.2' The YAML version a document is read with when it does not say: 1.2 or 1.1.

Events

EventDetailDescription
bmxChange BmxYamlChangeDetail Fired after the text changes.
bmxEdit BmxYamlEditDetail Fired before an edit made with the tree or a command. Cancel it to refuse the edit.
bmxSelect BmxYamlSelectDetail Fired when the selected entry changes (in the tree, or the one at the caret).
bmxValidate BmxYamlValidateDetail Fired after each check of the text.

Methods

MethodSignatureDescription
collapseAll collapseAll() => Promise<void>
convert convert(to: BmxYamlFileFormat | "json") => Promise<string> The data written in another format (or JSON), without changing anything.
convertTo convertTo(to: BmxYamlFileFormat) => Promise<boolean> Replace the text with its data written in another format, and read it as that format. Can be undone.
edit edit(edit: BmxYamlFileEdit, document?: number) => Promise<boolean> Make one change by path, as the tree does: { op: 'set', path: ['spec', 'replicas'], value: 3 }. Returns whether the text changed.
expandAll expandAll(depth?: number) => Promise<void>
formatText formatText() => Promise<boolean> Lay a YAML file out again, keeping its comments. Returns whether it changed (false when it has mistakes).
getData getData() => Promise<BmxJsonValue[]> Each document's data (one item for TOML, INI, .properties and .env).
getProblems getProblems() => Promise<BmxYamlProblem[]> The problems found by the last check.
getSelection getSelection() => Promise<BmxYamlSelectDetail | null> The selected entry (in the tree, or at the caret).
getValue getValue() => Promise<string> The text now (including typing not yet reported).
goTo goTo(line: number, column?: number) => Promise<void> Put the caret at a line and column (from 1), in the text view.
redo redo() => Promise<boolean>
save save(fileName?: string) => Promise<void> Download the text as a file (default: the file name, else config.<format>).
select select(path: readonly BmxYamlKey[] | string, document?: number) => Promise<boolean> Select the entry at a path (keys and indexes, a JSON Pointer, or spec.containers[0]) and show it. Returns whether it was found.
setFocus setFocus() => Promise<void>
setValue setValue(text: string, options?: { keepHistory?: boolean; }) => Promise<void> Replace the whole text. With keepHistory, it can be undone.
undo undo() => Promise<boolean>
validate validate() => Promise<BmxYamlProblem[]> Check now: the text's own rules and, with a schema, every document. Returns every problem.

Slots

SlotDescription
description Rich help text, in place of the description property.
label Rich label content, in place of the label property.

CSS shadow parts

PartDescription
description The help text under the field.
label The label element.
menu The actions menu.
name A key in a row.
problems The problems panel.
row A tree row; also row-selected, row-problem.
status The bar under the views.
toolbar The bar of views and commands.
tree The tree.
value A value in a row.

CSS custom properties

PropertyDescription
--bmx-yaml-editor-background The views' background.
--bmx-yaml-editor-date-color TOML dates and times.
--bmx-yaml-editor-height The height of the views' scrolling area.
--bmx-yaml-editor-indent How far each level of the tree is indented.
--bmx-yaml-editor-key-color Keys in the tree.
--bmx-yaml-editor-label-font-size The label's font size.
--bmx-yaml-editor-literal-color true, false and null.
--bmx-yaml-editor-number-color Number values.
--bmx-yaml-editor-radius Corner radius of the frame.
--bmx-yaml-editor-row-height The height of one tree row.
--bmx-yaml-editor-selected-background The selected row.
--bmx-yaml-editor-string-color Text values.
--bmx-yaml-editor-support-font-size Font size of the description and error message.