-
Notifications
You must be signed in to change notification settings - Fork 2
Integration Guide
Learn how to integrate Neiki Editor into your existing projects β PHP, Vue.js, React, or plain AJAX.
Neiki Editor ships with a PHP helper class in php/neiki-editor.php that handles asset loading, rendering, and HTML sanitization.
Tip
The PHP helper is the easiest way to integrate the editor into any PHP project β CMS, blog, admin panel, or custom application.
<?php require_once 'path/to/php/neiki-editor.php'; ?><head>
<?= NeikiEditor::assets() ?>
</head>This outputs both the CSS <link> and JS <script> tags. It 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 before saving to a database. The NeikiEditor::sanitize() method strips dangerous tags (<script>, <iframe>, etc.), event handler attributes (onclick, onerror), and javascript: protocol URLs.
| Method | Description |
|---|---|
NeikiEditor::assets() |
Output CSS & JS tags from CDN. Call once per page. |
NeikiEditor::assets(true, '/path/to/dist') |
Use local files instead of CDN. |
NeikiEditor::render($id, $content, $options) |
Render a <textarea> with auto-initialization script. |
NeikiEditor::sanitize($html) |
Sanitize HTML β strips dangerous tags and attributes. |
If you're hosting the editor files yourself instead of using CDN:
<?= NeikiEditor::assets(true, '/assets/vendor/neiki-editor/dist') ?>If you prefer not to use the helper class:
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.0.0/dist/neiki-editor.css">
<script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.0.0/dist/neiki-editor.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>Important
When outputting user content into a <textarea>, always use htmlspecialchars() to prevent XSS. The PHP helper handles this automatically.
Save content asynchronously without a full page refresh:
const editor = new NeikiEditor('#editor', {
onChange: debounce(function(content) {
fetch('/api/articles/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({ content })
})
.then(response => response.json())
.then(data => {
console.log('Saved:', data);
})
.catch(error => {
console.error('Save failed:', error);
});
}, 2000)
});
// Simple debounce utility
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}Tip
A 2-second debounce prevents excessive API calls while the user is typing. Adjust the delay based on your needs.
<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
Make sure to call editor.destroy() in your component's unmount/destroy lifecycle hook to prevent memory leaks.
import { useEffect, useRef, useCallback } from 'react';
function NeikiEditorComponent({ value, onChange, options = {} }) {
const textareaRef = useRef(null);
const editorRef = useRef(null);
const onChangeRef = useRef(onChange);
// Keep callback ref updated
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
return <textarea ref={textareaRef} defaultValue={value} />;
}
export default NeikiEditorComponent;Usage:
import NeikiEditorComponent from './NeikiEditorComponent';
function ArticleForm() {
const [content, setContent] = useState('');
return (
<NeikiEditorComponent
value={content}
onChange={setContent}
options={{ minHeight: 400, theme: 'dark' }}
/>
);
}Caution
Do not pass value as a dependency to the useEffect that creates the editor. This would destroy and recreate the editor on every content change. Use setContent() to update content imperatively if needed.
{{-- resources/views/components/editor.blade.php --}}
@once
@push('styles')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.0.0/dist/neiki-editor.css">
@endpush
@push('scripts')
<script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.0.0/dist/neiki-editor.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>Use this checklist to ensure a complete integration:
- Assets included β CSS before JS, loaded once per page
- Textarea has unique ID β required for initialization
- Editor initialized after DOM ready β place
<script>after the textarea or useDOMContentLoaded - Content sanitized server-side β never trust raw HTML from the client
- Destroy on unmount β call
editor.destroy()in SPA component cleanup - CSRF token included β for AJAX save requests (if applicable)
- π Getting Started β Installation and first steps
- βοΈ Configuration β All configuration options
- π Plugin API β Extend with custom plugins
Getting Started
Reference
Extending
Integration
Features & UI
Project