-
Notifications
You must be signed in to change notification settings - Fork 2
Integration Guide
Integrate Neiki's Editor into PHP, Vue.js, React, Laravel, and plain AJAX projects.
The editor ships with php/neiki-editor.php β a helper class that handles asset loading, rendering, and HTML sanitization.
<?php require_once 'path/to/php/neiki-editor.php'; ?><head>
<?= NeikiEditor::assets() ?>
</head>This outputs the CSS <link> and JS <script> tags and prevents duplicate includes if called multiple times.
<form method="POST" action="save.php">
<?= NeikiEditor::render('content', $article->body, [
'minHeight' => 400,
'placeholder' => 'Write your article...',
'theme' => 'light'
]) ?>
<button type="submit">Save</button>
</form><?php
// save.php
require_once 'path/to/php/neiki-editor.php';
$cleanHTML = NeikiEditor::sanitize($_POST['content']);
// Save to database
$stmt = $pdo->prepare('UPDATE articles SET body = ? WHERE id = ?');
$stmt->execute([$cleanHTML, $articleId]);Caution
Always sanitize user-submitted HTML server-side before saving to a database. NeikiEditor::sanitize() strips dangerous tags (<script>, <iframe>), event handler attributes (onclick, onerror), and javascript: protocol URLs.
| Method | Description |
|---|---|
NeikiEditor::assets() |
Output CDN CSS + JS tags. Call once per page. |
NeikiEditor::assets(true, '/path/to/dist') |
Use local files instead of CDN. |
NeikiEditor::render($id, $content, $options) |
Render <textarea> with initialization script. |
NeikiEditor::sanitize($html) |
Sanitize HTML β strips dangerous tags and attributes. |
<?= NeikiEditor::assets(true, '/assets/vendor/neiki-editor/dist') ?><head>
<script src="https://cdn.neikiri.dev/neiki-editor/neiki-editor.min.js"></script>
</head>
<body>
<form method="POST" action="save.php">
<textarea id="editor" name="content"><?= htmlspecialchars($article->body) ?></textarea>
<button type="submit">Save</button>
</form>
<script>
const editor = new NeikiEditor('#editor');
</script>
</body>import { useEffect, useRef } from 'react';
function NeikiEditorComponent({ value, onChange, options = {} }) {
const textareaRef = useRef(null);
const editorRef = useRef(null);
const onChangeRef = useRef(onChange);
// Keep callback ref updated without re-initializing
useEffect(() => {
onChangeRef.current = onChange;
}, [onChange]);
useEffect(() => {
if (!textareaRef.current) return;
editorRef.current = new NeikiEditor(textareaRef.current, {
...options,
onChange: (content) => {
onChangeRef.current?.(content);
}
});
if (value) {
editorRef.current.setContent(value);
}
return () => {
editorRef.current?.destroy();
editorRef.current = null;
};
}, []); // Initialize once only
return <textarea ref={textareaRef} defaultValue={value} />;
}
export default NeikiEditorComponent;Usage:
import { useState } from 'react';
import NeikiEditorComponent from './NeikiEditorComponent';
function ArticleForm() {
const [content, setContent] = useState('');
return (
<NeikiEditorComponent
value={content}
onChange={setContent}
options={{ minHeight: 400, theme: 'dark' }}
/>
);
}Caution
Do not include value in the useEffect dependency array. This would destroy and recreate the editor on every keystroke. Use setContent() imperatively if you need to update content externally.
<template>
<textarea ref="editorEl"></textarea>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
const props = defineProps({
modelValue: { type: String, default: '' }
});
const emit = defineEmits(['update:modelValue']);
const editorEl = ref(null);
let editorInstance = null;
onMounted(() => {
editorInstance = new NeikiEditor(editorEl.value, {
onChange: (content) => {
emit('update:modelValue', content);
}
});
if (props.modelValue) {
editorInstance.setContent(props.modelValue);
}
});
onBeforeUnmount(() => {
editorInstance?.destroy();
});
</script>Usage:
<template>
<NeikiEditorComponent v-model="article.body" />
</template><template>
<textarea ref="editor"></textarea>
</template>
<script>
export default {
props: {
value: { type: String, default: '' }
},
mounted() {
this.editor = new NeikiEditor(this.$refs.editor, {
onChange: (content) => {
this.$emit('input', content);
}
});
if (this.value) {
this.editor.setContent(this.value);
}
},
beforeDestroy() {
this.editor?.destroy();
}
}
</script>Note
Always call editor.destroy() in onBeforeUnmount / beforeDestroy to prevent memory leaks.
{{-- resources/views/components/editor.blade.php --}}
@once
@push('scripts')
<script src="https://cdn.neikiri.dev/neiki-editor/neiki-editor.min.js"></script>
@endpush
@endonce
<textarea id="{{ $id }}" name="{{ $name }}">{!! e($value ?? '') !!}</textarea>
@push('scripts')
<script>
new NeikiEditor('#{{ $id }}', @json($options ?? []));
</script>
@endpushUsage in a Blade view:
<form method="POST" action="{{ route('articles.store') }}">
@csrf
<x-editor id="content" name="content" :value="$article->body" :options="['minHeight' => 400]" />
<button type="submit">Save</button>
</form>Save content automatically as the user types:
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const editor = new NeikiEditor('#editor', {
onChange: debounce(function(content) {
fetch('/api/autosave', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content
},
body: JSON.stringify({ content, id: articleId })
})
.then(res => res.json())
.then(data => {
document.getElementById('save-status').textContent = 'Saved ' + new Date().toLocaleTimeString();
})
.catch(() => {
document.getElementById('save-status').textContent = 'Save failed';
});
}, 2000)
});Before going live, verify:
- Assets loaded once β CSS before JS, no duplicates
- Textarea has a unique ID β required for initialization
- Editor initialized after DOM ready β
<script>after textarea or useDOMContentLoaded - Server-side sanitization β never trust raw HTML from clients
-
destroy()on unmount β required in SPA components (Vue, React) - CSRF token included in AJAX save requests
-
imageUploadHandlerconfigured for production (avoids base64 bloat) -
autosaveKeyset if multiple editors on same URL edit different records
| π Getting Started | Install and first steps |
| βοΈ Configuration | All init options |
| π Security | Sanitization, XSS protection details |
| π API Reference |
getContent, setContent, destroy and all methods |
Getting Started
Reference
Extending
Integration
Features & UI
Project