Atelier Logo

Atelier

Installation

Install Text Atelier from a private registry and configure your `.npmrc` securely.

Installing Text Atelier from a private registry

Private registry (.npmrc) setup

Use the steps below to configure access to the private registry and safely store your access token (PAT).

  1. Create a .npmrc file in your project root (or update your user-level .npmrc) and add the private registry and authentication token entry exactly as shown:
@innosoft:registry=https://code.is.sa/api/v4/projects/1047/packages/npm/
//code.is.sa/api/v4/projects/1047/packages/npm/:_authToken=${PAT}
  1. Choose how to provide the token:

    • For local development, export the token in your shell before running installs: export PAT="your-token-here".
    • For CI, store PAT as a secret in your CI provider and make it available to the job environment — avoid hardcoding credentials in files.
  2. Note: yarn and pnpm respect .npmrc, so the same file works across package managers.

Add your project's .npmrc to .gitignore to avoid committing a file that contains your access token. In CI/CD, prefer using encrypted repository secrets or environment variables rather than embedding tokens in files.

Then install the wrapper for your framework — this is the only package you install. The wrapper brings @innosoft/atelier-editor-core, Tiptap, and everything else with it as normal dependencies, so updating the wrapper updates the whole editor.

# Angular (>= 14)
npm install @innosoft/atelier-editor-angular

# React (18 or 19)
npm install @innosoft/atelier-editor-react

# Vue 3
npm install @innosoft/atelier-editor-vue

# Vanilla JS / TS
npm install @innosoft/atelier-editor-vanilla

Quick start

// editor-page.component.ts
import { Component, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AtelierEditorComponent } from '@innosoft/atelier-editor-angular';

@Component({
  standalone: true,
  selector: 'app-editor-page',
  imports: [FormsModule, AtelierEditorComponent],
  template: `
    <atelier-editor
      [(ngModel)]="content"
      [licenseKey]="licenseKey"
      placeholder="Write something..."
      lang="en"
      (editorReady)="onReady($event)"
    ></atelier-editor>

    <button (click)="save()">Save</button>
    <button (click)="downloadPdf()">Download PDF</button>
  `
})
export class EditorPageComponent {
  @ViewChild(AtelierEditorComponent) editor!: AtelierEditorComponent;

  content = '<p>Hello world</p>'; // always holds the latest HTML via ngModel
  readonly licenseKey = '<your-license-key>';

  save(): void {
    const html = this.editor.getHTML();
    // send `html` to your API
  }

  downloadPdf(): void {
    void this.editor.exportPDF({ filename: 'report' });
  }
}

Feature flags default to false in React (opt-in). Define the feature config at module scope so its identity is stable across renders — see Preventing the infinite update loop.

// EditorPage.tsx
import { useRef, useState, type ComponentRef } from 'react';
import { AtelierEditor } from '@innosoft/atelier-editor-react';

const LICENSE_KEY = '<your-license-key>';

// Module scope — created once, stable identity across every render.
const FEATURES = {
  starterKit: true,
  bold: true,
  italic: true,
  underline: true,
  heading: true,
  bulletList: true,
  orderedList: true,
  table: true,
  exportPdf: true
} as const;

export function EditorPage() {
  const editorRef = useRef<ComponentRef<typeof AtelierEditor>>(null);
  const [content, setContent] = useState('<p>Hello world</p>');

  return (
    <>
      <AtelierEditor
        ref={editorRef}
        {...FEATURES}
        src={content}
        onChange={e => setContent(e.detail)} // e.detail = the HTML string, store it as-is
        licenseKey={LICENSE_KEY}
        placeholder="Write something..."
        lang="en"
      />
      <button onClick={() => console.log(editorRef.current?.getHTML())}>Save</button>
      <button onClick={() => void editorRef.current?.exportPDF({ filename: 'report' })}>
        Download PDF
      </button>
    </>
  );
}

Next.js: add 'use client' at the top of any file that renders <AtelierEditor>, and import it via next/dynamic with { ssr: false } — see SSR.

<script setup lang="ts">
import { ref } from 'vue';
import { AtelierEditor } from '@innosoft/atelier-editor-vue';
import type { Editor } from '@tiptap/core';

const content = ref('<p>Hello world</p>'); // always holds the latest HTML via v-model
const editor = ref<InstanceType<typeof AtelierEditor> | null>(null);

function onReady(tiptap: Editor) {
  // direct Tiptap access — rarely needed
}

function save() {
  const html = editor.value?.getHTML();
  // send `html` to your API
}

function downloadPdf() {
  void editor.value?.exportPDF({ filename: 'report' });
}
</script>

<template>
  <AtelierEditor
    ref="editor"
    v-model="content"
    license-key="<your-license-key>"
    placeholder="Write something..."
    lang="en"
    @editor-ready="onReady"
  />
  <button @click="save">Save</button>
  <button @click="downloadPdf">Download PDF</button>
</template>

Nuxt: the component guards itself with typeof window !== 'undefined' — no <ClientOnly> wrapper needed, though using it avoids hydration mismatches entirely.

import { AtelierEditor } from '@innosoft/atelier-editor-vanilla';

const editor = new AtelierEditor('#editor-mount', {
  content: '<p>Hello world</p>',
  licenseKey: '<your-license-key>',
  placeholder: 'Write something...',
  table: false, // all feature flags default to true; opt out individually
  onChange: html => {
    // Store exactly what onChange gives you. Do not transform the HTML here.
  },
  onReady: tiptapEditor => {
    /* Tiptap Editor instance */
  }
});

// Imperative API
editor.getHTML(); // current content as HTML string
editor.setContent('<p>Replacement</p>'); // replace content
editor.update({ rtl: true, lang: 'ar' }); // change any option at runtime
void editor.exportPDF({ filename: 'report' });
editor.destroy(); // remove from DOM and clean up

On this page