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><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), andjavascript: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 includevaluein theuseEffectdependency array. This would destroy and recreate the editor on every keystroke. UsesetContent()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 calleditor.destroy()inonBeforeUnmount/beforeDestroyto 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>
@endpush
Usage 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:
<script> after textarea or use DOMContentLoadeddestroy() on unmount — required in SPA components (Vue, React)imageUploadHandler configured for production (avoids base64 bloat)autosaveKey set 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 |