Menu

Integration Guide

neikiri

🔗 Integration Guide

Integrate Neiki's Editor into PHP, Vue.js, React, Laravel, and plain AJAX projects.


🐘 PHP Integration

The editor ships with php/neiki-editor.php — a helper class that handles asset loading, rendering, and HTML sanitization.

Step 1 — Include the helper

<?php require_once 'path/to/php/neiki-editor.php'; ?>

Step 2 — Output assets in <head>

<head>
    <?= NeikiEditor::assets() ?>
</head>

This outputs the CSS <link> and JS <script> tags and prevents duplicate includes if called multiple times.

Step 3 — Render the editor

<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>

Step 4 — Sanitize on save

<?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.


PHP Helper API Reference

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.

Using Local Assets

<?= NeikiEditor::assets(true, '/assets/vendor/neiki-editor/dist') ?>

Manual PHP Integration (Without Helper)

<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>

⚛️ React Integration

Functional Component with Hooks

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.


💚 Vue.js Integration

Vue 3 (Composition API)

<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>

Vue 2 (Options API)

<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.


🟠 Laravel Blade

{{-- 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>

📡 AJAX Auto-Save

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)
});

📋 Integration Checklist

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 use DOMContentLoaded
  • 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
  • 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