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).
- Create a
.npmrcfile 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}-
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
PATas a secret in your CI provider and make it available to the job environment — avoid hardcoding credentials in files.
- For local development, export the token in your shell before running installs:
-
Note:
yarnandpnpmrespect.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-vanillaQuick 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