Download the PHP package vipertecpro/wysiwyg-editor without Composer

On this page you can find all versions of the php package vipertecpro/wysiwyg-editor. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package wysiwyg-editor

WysiwygEditor — a native WYSIWYG rich text editor for NativePHP Mobile

Packagist Version Total Downloads PHP Version

Open a fully native rich text editor from PHP — the user writes with a real native keyboard and a formatting toolbar (bold, italic, headings, lists, links, colors…), then you get clean HTML back. Written against UITextView / NSAttributedString (iOS) and EditText / Spannable (Android), with one simple PHP API and zero third-party native libraries. No webview.

Think Quill / TipTap / CKEditor — but as a real native mobile screen instead of a JavaScript editor in a browser.

See it running

Every configuration below is the same editor with a different options array. The demo application shows each one as a working screen with screenshots and the code behind it — a plain-text composer with no toolbar, mentions the host answers, a few words held on a colour, a / palette, tables, checklists, and an editor with no Save button at all.

Start there if you are deciding whether this fits; the options each screen passes are the whole of what makes them different.

Features

What it does not do

Worth knowing before you adopt it rather than after:

Requirements

If you are on NativePHP 4.0.0 exactly

Nothing to do on 4.0.1 or newer — skip this.

On 4.0.0 only, a build stops with "Please provide a valid cache path". That release staged a copy of your app to run composer install in, excluded storage/framework from the copy, and then booted Laravel inside it; the framework's default compiled path wraps realpath(), which answers false for a directory that is not there. Publishing config/view.php and handing the path over as a plain string works around it:

4.0.1 recreates those directories after the exclusions run, so the workaround is no longer needed and the published config can go. Nothing to do with this plugin either way — it bit any app on 4.0.0 — but it is written down because it is the first thing you would have hit.

Platform support

Both platforms have the same features. One PHP API, one document model, one HTML/JSON contract — and a parity harness that asserts the coder produces byte-identical output on each.

Everything is implemented and device-verified on iOS and Android: the text engine and every mark and block; the toolbar, its presets and bottom-sheet menus; the media strip with per-thumbnail remove, description and edit; the inline poll card with its option cap and duration; host accessory rows; the countdown ring; the soft length cap with the overrun shaded; the filled save pill; the full-screen media viewer; re-opening from saved JSON; typography and spacing; theming; localization; validation; counts; haptics; the auto-save seam; embeds with provider detection; and HTML / plain text / JSON / Markdown export.

Where the two differ is only where the platforms do: iOS uses UITextView and NSAttributedString, Android EditText and Spannable; each draws with its own vector API from one shared set of icon paths. A document written on one opens identically on the other.

Installation

Requiring with Composer is not enough — an unregistered plugin does nothing. Always run native:plugin:register and confirm with native:plugin:list.

Usage

Call the facade from a NativeComponent, then handle the result event.

On NativePHP Mobile v3, the #[On] attribute isn't available — listen for the events with #[OnNative(...)] (from Native\Mobile\Attributes\OnNative) instead. Everything else is identical; the plugin itself is unchanged across v3 and v4.

Options

WysiwygEditor::open(string $html = '', array $options = []): void

All options are optional:

Key Type Default Meaning
preset string full Built-in toolbar: full, basic, comment, note
toolbar list — Explicit ordered tool list; overrides preset
title string '' Heading shown in the editor's top bar
placeholder string '' Shown while the editor is empty
maxLength int 0 Max plain-text length; 0 = unlimited. Shows a live counter
counts list [] Live readouts: characters, words, readingTime (200 wpm, min 1)
validation array [] Save-time rules: minWords, maxWords, requiredBlocks, maxImages
strings array English Translations for every user-visible string — see Localization
changeDebounce int 0 Emit ContentChanged this many ms after typing stops; 0 = off
haptics bool true Light haptic tap on toolbar buttons
typography array — fontFamily, fontSize (base, 10–32), lineHeight (1.0–2.0). Headings scale from the base
spacing string comfortable Editing density: compact, comfortable, roomy
menu string toolbar toolbar = one scrolling bar; sheet = compact bar + Format/Insert bottom sheets
mediaLayout string blocks blocks = media sits in the text flow; strip = a thumbnail row under it
maxMedia int 4 Attachments allowed; 0 = no limit
countStyle string text text readout, or a ring counting down to maxLength
maxLengthMode string hard hard refuses the keystroke; soft allows the overrun, shades it and blocks save
avatar string '' The author's picture — a url or a local path
avatarPlacement string text text beside the writing, header beside Close and Save, none
toolbarAlign string leading trailing parks a short bar in the corner instead of the left edge
customTools list [] Extra toolbar buttons: id, icon, optional label, optional sheet. Tapping emits ToolTapped — unless it names a sheet, which the editor presents instead
accessories list [] Your own controls — see Your own rows in the composer
sheets array [] Sheets the editor presents on your behalf — see Your own sheets
triggers array|false @ → mention, # → hashtag Characters that start a lookup; false turns it off entirely
backgrounds array [] Colours a short post can be written ON — see Post backgrounds
backgroundMaxLength int 130 Past this many characters a background is dropped
cancelMode string discard discard asks to throw the edits away; draft offers to keep them and emits DraftRequested
cancelStyle string text text shows "Cancel"; icon shows a ✕
saveStyle string text text button, a filled pill that dims until there is something to save, or none — no Save button at all, for an editor that saves as you type. The close control commits instead
history bool true Show undo / redo ahead of the tools
pollOptionMaxLength int 25 Longest a single poll answer may be
theme array [] Hex colors: background, text, accent (Save button), highlight (active states). Omitted keys fall back to the host app's theme, then to system-adaptive defaults
id string null Echoed back on the result event, to correlate concurrent editors

Available tools for toolbar (this order is the full preset): bold, italic, underline, strikethrough, h1, h2, h3, bulletList, orderedList, checklist, blockquote, link, code, textColor, highlight, image, camera, video, file, poll, table, divider, embed, clearFormat. Undo/redo are always present and are not toolbar keys.

You are meant to take only what you need. An explicit empty list draws no bar at all, and everything that costs something is opt-in: name no media tool and the picker, the uploader and MediaRequested never come into play; pass triggers => false and nothing watches your keystrokes. An editor opened with no options carries no media, no polls, no colours, no host controls and no lookups — which is what makes it usable as a plain text field.

Toolbar presets:

Theming

The editor adopts the host application's theme automatically. When nativephp/native-ui is present the plugin reads its tokens (Theme::all()) and derives its four surfaces per colour scheme, so the editor follows your app into dark mode with no configuration:

Editor surface Host token (first match wins)
background background, surface
text on-background, on-surface
accent (Save) primary, accent
highlight (active tools) primary, accent, secondary

Precedence is explicit theme option → host tokens → the plugin's system-adaptive defaults, so passing colours still overrides everything and an app without NativeUI behaves exactly as before.

Override it explicitly to match a specific look:

Events

Event Payload When
…\Events\ContentSaved string $html, string $text, string $json, ?string $id User taps Save
…\Events\EditCancelled ?string $id User cancels / backs out (a discard confirm guards unsaved changes)
…\Events\ContentChanged string $html, string $text, string $json, ?string $id Typing settled, when changeDebounce is set — the auto-save seam
…\Events\MediaRequested string $kind, ?string $id User tapped image / video / file. Answer with insertMedia()
…\Events\MediaEditRequested string $kind, string $uploadId, string $source, ?string $id User tapped edit on an attachment. Re-open your own picker
…\Events\DraftRequested string $html, string $text, string $json, ?string $id User backed out of a half-written document and chose to keep it (cancelMode => 'draft')
…\Events\ToolTapped string $tool, ?string $id User tapped one of your own toolbar buttons
…\Events\AccessoryTapped string $accessory, ?string $id User tapped one of your own controls. Answer with setAccessory()
…\Events\SuggestionRequested string $kind, string $trigger, string $query, ?string $id User typed a trigger character. Answer with suggestions()
…\Events\SheetOptionPicked string $sheet, string $option, ?string $id User chose something from one of your declared sheets

All events live under Vipertecpro\WysiwygEditor\Events\.

What you actually receive

The same document arrives three ways. They are not alternatives to pick between — each answers a different question, and a real client usually sends more than one. A post with some words, a photo still uploading, a photo already uploaded and a poll produces exactly this:

$html — to RENDER. Safe to publish as-is:

Note what is not in there: the photo still uploading has no src at all. A device path must never leak into published markup, so the editor omits it and marks the figure data-pending instead.

$text — to SEARCH and to excerpt. Marks stripped, one line per block.

$json — CANONICAL. The only form carrying device paths, poll option ids, upload state and the background a post was written on:

Store $json if the document should ever be editable again — re-opening from $html alone comes back without any photo whose upload had not finished, and without the colour the post was written on.

Saving text and attachments separately

Most servers want the prose in one table and the files in another. The editor uploads nothing — the endpoint is yours, so the upload is too — and you should not have to learn the document format to find the files:

Exactly one of path and url is ever filled, so which to do next is never ambiguous: a device path means it still needs uploading, a url means it is already yours. Polls, dividers and embeds are not files and are not returned — they travel in the document itself.

Validation

Checked natively when the user taps Save — a failing document never makes the round-trip to PHP just to be rejected.

Localization

The plugin ships no locale files, on purpose: your app already knows the user's language and has its own translation workflow. Pass the strings you want changed and the rest keep their English defaults.

{n}, {max} and {type} are substituted natively, so the translation controls word order — important in languages where the number does not come first. Unknown keys are dropped rather than silently ignored, so a typo shows up immediately. See WysiwygEditor::STRINGS for the full key list.

Auto-save

The editor does not own drafts or persistence — it tells you the document changed and settled, and you decide what that means:

Off by default, so apps that don't want it pay nothing.

HTML contract

Both platforms parse and serialize exactly this tag set, so a document round-trips identically on iOS and Android. This section is normative — the Swift and Kotlin implementations are written against it.

Blocks (serialized in document order)

Block Serialized as
Paragraph <p>…</p>
Empty paragraph (blank line) <p><br></p>
Heading 1–3 <h1>…</h1> / <h2>…</h2> / <h3>…</h3>
Bullet list <ul><li>…</li><li>…</li></ul> (consecutive items in ONE <ul>)
Ordered list <ol><li>…</li></ol> (consecutive items in ONE <ol>)
Blockquote <blockquote>…</blockquote> (inline content only)

No attributes on block tags. No nested lists in v1. Blocks are concatenated with no whitespace between them.

Inline marks (nesting order, outermost → innermost)

<a href="…"> → <span style="color:#RRGGBB"> → <mark style="background-color:#RRGGBB"> → <strong> → <em> → <u> → <s> → <code>

Adjacent runs with identical marks are merged. Text nodes escape & < > (and " inside attribute values). Colors always serialize as 6-digit uppercase hex.

Parser tolerance (input only)

Parsing accepts aliases and normalises them: <b>→strong, <i>→em, <del>/<strike>→s, <div>→p, <h4>–<h6>→h3, <mark> without a style → default highlight. <br> inside a block splits it into two blocks. Unknown tags are ignored but their text content is kept. Unknown attributes are dropped. javascript: and other non-http(s)/mailto/tel link schemes are dropped (the text is kept, the link removed).

Entities &amp; &lt; &gt; &quot; &#39; &nbsp; are decoded. &nbsp; becomes a real non-breaking space (U+00A0) and is emitted raw on the way out, not re-encoded — runs of ordinary whitespace collapse to a single space, U+00A0 does not. Text between blocks that is pure whitespace is dropped; any other loose text opens an implicit <p>.

Verified examples

These round-trip identically on both platforms (input → $html, $text), and are checked by the parity harnesses in tests/native/ios and tests/native/android:

Input $html $text
<p>Hello <strong>wor</strong>ld</p> unchanged Hello world
<div>a<br>b</div> <p>a</p><p>b</p> a\nb
<b>x</b> <p><strong>x</strong></p> x
<h4>deep</h4> <h3>deep</h3> deep
<p><a href="javascript:alert(1)">x</a></p> <p>x</p> x
<p><span style="color:#f00">r</span></p> <p><span style="color:#FF0000">r</span></p> r
<p><mark>h</mark></p> <p><mark style="background-color:#FDE68A">h</mark></p> h
<p><br></p> (empty) |
<ol><li>x</li><li>y</li></ol> unchanged 1. x\n2. y

Marks that share a level nest into ONE tag rather than repeating it — <strong>a<em>b</em></strong>, never <strong>a</strong><strong><em>b</em></strong>.

Toolbar icons

The toolbar glyphs are hand-drawn vector paths defined once (a 24×24 viewBox, a tiny M/L/C/Z subset) and duplicated verbatim in the Swift and Kotlin files, then stroked with each platform's vector API. SF Symbols and Material icons share no common subset, so drawing the same vectors is the only way the two toolbars can genuinely match. Edit both copies together.

Plain-text rendition ($text)

One line per block, \n-joined. List items are prefixed - (bullet) or 1., 2.… (ordered). All marks stripped.

Editor UI

Top bar: Cancel · title · Save (accent color). The content area fills the screen; the formatting toolbar is a horizontally scrollable icon row pinned above the keyboard, with undo/redo first, then the configured tools. Active formats show in the highlight color. link opens a small URL dialog (pre-filled when editing an existing link); textColor / highlight open a fixed 6-color palette (plus "none"). Cancelling with unsaved changes asks for confirmation. The editor follows the system light/dark theme unless overridden by theme.

Inserting media

The editor ships no picker and no uploader, on purpose. It asks, and your app answers with whatever it already uses — so permissions, auth and upload infrastructure stay in one place instead of being duplicated inside an editor.

The block appears the moment it is inserted, rendering from localPath, so the user never waits on a network round-trip. Until an upload completes the block exports as <figure data-pending="…"> with no src — the device path is never written into published HTML.

Where attachments sit

mediaLayout decides whether media lives in the text or beside it:

maxMedia caps attachments (default 4 — what the common grids are built to lay out); 0 means no limit.

Tapping edit on a thumbnail emits MediaEditRequested with the block's uploadId and current source. The editor does not crop, rotate or filter — re-open your own picker and call insertMedia() again with the same uploadId to replace it.

Showing media full-screen

Opens a zoomable image viewer, or a player for video. It lives in the plugin because the editor already decodes images and plays video for its own cards, and because NativePHP ships no video element for a host to build a viewer out of.

Your own buttons in the toolbar

The same idea as accessory rows, one level up. The editor cannot know what a GIF picker, a location tagger or a scheduler should do — those are your features, backed by your services — so it draws the button and reports the tap.

icon names one of the editor's own glyphs, so your buttons are drawn by the same vector code as everything else and match on both platforms. camera is a first-class tool rather than a custom one, because a photo you TAKE and a photo you PICK are different screens — it emits MediaRequested with kind of camera.

avatar puts the author's picture beside what they are writing, the way social composers arrange it. A url or a local path; it is decoded the same way media is, so an app that has not uploaded one yet still shows something.

Your own rows in the composer

The editor owns the whole screen, so without this an app could not put its own controls beside the text — and most composers have some: Tag people, Add location, an audience picker.

None of that belongs in an editor: they are your features, backed by your data. So the editor draws the row and gets out of the way.

Each control takes id (reported back on tap), label, and optionally:

Key Meaning
icon One of the editor's own glyph names
value Trailing text — the choice that was made
placement row under the media (default), or header beside Close and Save
style row full width, chip label + disclosure, icon a bare glyph. A header control defaults to chip
sheet Names a declared sheet to present instead of merely reporting the tap

A control without an id could never report a tap and one without a label would draw as a blank tappable strip — both are dropped rather than shown.

header is for the controls that belong beside the button that sends the post — an audience picker decides who sees it, so it belongs next to Save, not below the fold. An icon control draws its value beside the glyph once it has one, so a schedule button can say when rather than looking identical before and after you use it.

Mentions and hashtags

The editor watches for a trigger character and reports what follows it. It has no directory of people and should not grow one — who is mentionable depends entirely on who is asking, which only your app knows.

Picking one writes a link, not styled text, so the saved post says WHICH person was named rather than merely that some words are blue:

The href is {kind}:{id} — the kind you declared and the id you returned. Render it however you like; the editor only guarantees it survives the round trip, including back into an edit.

Turn it off entirely with 'triggers' => false, and nothing watches keystrokes at all.

Slash commands

A command is a suggestion row that names a tool. Same pipeline as a mention — the editor spots the trigger, you answer with what matches — and the only difference is what a pick does:

Picking one deletes the trigger and everything typed after it — /h1 is an instruction, not text you meant to keep — and then:

For that last case, insertText() writes at the caret as if the user had typed it — inheriting the formatting there, and landing in the undo stack:

A date format is a product decision, so the editor has no opinion about it.

Which commands exist is your decision, the same way the directory behind @ is. The editor supplies the mechanism, never the list.

Post backgrounds

A few words held large and centred on a colour, the way a social composer does it — the post stops being a paragraph and becomes a card.

from alone is a flat colour; adding to makes it a gradient. textColor defaults to white. Which colours exist is your decision — the editor ships no palette, because that is a brand decision.

The swatches are offered only while the post is still short and has no media: a paragraph set in 28pt white on orange is unreadable, and a photo already IS the card.

A background belongs to the DOCUMENT, not to a run inside it, so it round-trips in both forms:

It is in the HTML as well as the JSON on purpose: for a post like this the background IS the post, and a host rendering saved markup would otherwise show a few words in plain black on white.

Your own sheets

The editor owns the screen — on iOS it owns its own window — so a sheet you drew would open behind it. That is not something an app should have to work around, so you declare the options and the editor presents them, then reports the pick.

style is list (rows with a tick) or grid (circular tiles). What the options MEAN stays yours; the editor draws them and gets out of the way.

Offer nothing you cannot honour. A tile that reports a tap nobody acts on is a dead control, and it looks exactly like a bug.

Running one of the editor's own tools

A composer whose toolbar is a sheet of your own still needs to say "insert a poll":

Only tools the editor actually owns; anything else is ignored, so a typo does nothing rather than something surprising. Formatting marks are not available this way — they apply to a selection, and a sheet has no idea what is selected.

Polls

poll inserts a poll the user writes in the editor — a question and its options — with no host round-trip, because there is nothing to pick and nothing to upload. Tapping an existing poll re-opens the composer to edit it.

The editor authors polls; it does not run them. Voting belongs to whatever renders your saved content, which is also where the votes have to be stored. The block round-trips through HTML as <figure data-poll="…"> and through JSON with its option ids intact, so your renderer has everything it needs.

Tables

A grid of plain-text cells, inserted with the table tool and edited where it sits — a row of small fields with controls to grow or shrink it.

Key Type Default Meaning
tableDefaultRows int 2 Rows a fresh table starts with
tableDefaultColumns int 2 Columns a fresh table starts with
tableMinRows int 1 Never shrinks below this
tableMaxRows int 20 Never grows past this
tableMinColumns int 1
tableMaxColumns int 5 What a phone can actually show

It saves as a real <table>, not an opaque blob — so anything rendering your saved markup gets a table:

The first row uses <th> when the header is on. In JSON the cells are a plain 2-D array:

Cells hold plain text, not marks. A cell on a phone is a word or two, and carrying bold and links into every one of them would multiply the coders and the editing surface for something the layout has no room to show. Text pasted into a cell arrives as text.

Reading a table back in is tolerant: a ragged row is padded rather than dropped, because a grid with a short row cannot be laid out and throwing the row away loses what it held.

A document never ends in a card. Whatever the last block is — a table, a picture, a divider — the editor keeps an empty paragraph below it so there is somewhere to carry on writing. It is an editing affordance only: an empty trailing paragraph is dropped on the way out, so it never appears in the HTML or the JSON you save.

Exporting

ContentSaved hands you the document three ways:

Format Payload Lossless?
HTML $html Text is exact. Block ids, upload state and poll options have nowhere to live in HTML
Plain text $text No, by definition — it is the rendition used for counts and previews
JSON $json Yes — this is the canonical form

Markdown is derived from the JSON, not the HTML, so it never inherits a loss that already happened:

Markdown is a narrower format than the document model, so some things cannot survive and are dropped rather than approximated — underline, text colour and highlight have no Markdown spelling, and emitting raw HTML for them would produce a file that is Markdown in name only. Video and file blocks become links, an embed becomes its bare URL, and a poll becomes its question plus a list: the content crosses over, the interactivity does not. Store the JSON if you need everything back.

Typography and spacing

The editor takes the host application's font the same way it takes its colours — if the NativeUI theme names one (fonts.default or font-family), the editor uses it with no configuration. A font the app never bundled falls back to the system face rather than rendering nothing.

Size and density are set explicitly:

The heading ramp is derived from fontSize with fixed multipliers (1.75 / 1.375 / 1.125), so one number moves the whole scale and the proportions hold. The default base of 16 gives exactly the 28 / 22 / 18 ramp the editor has always used, so nothing shifts unless you ask it to.

spacing values are points on iOS and dp on Android, so both platforms lay out the same. Unlike colours and the font this is a choice you make rather than something adopted — NativeUI has no spacing token to read, and guessing one would be inventing a schema.

Scale Horizontal Vertical Paragraph
compact 12 8 4
comfortable 16 12 6
roomy 20 18 10

Toolbar or bottom sheets

menu decides how the tools are reached:

The sheets are built from the SAME toolbar list, so they stay modular: a tool you did not enable does not appear, and a section with nothing in it is not drawn. Every row is localizable through strings like everything else — see WysiwygEditor::TOOL_LABEL_KEYS for which key labels which tool.

Body is always offered in the Format sheet even though it is not a tool, because without it there is no way back to plain text once a heading has been applied.

Developing on a device

Two caching traps will make you think your changes did nothing. Both bit us — and they are about your PHP, not about this plugin's native code.

Native code refreshes on its own from 4.0.0. Earlier versions kept a stale copy of a plugin's Swift/Kotlin in the generated project, so editing the plugin and re-running built the OLD code and you had to copy it across by hand. 4.0.0 cleans the generated copy before compiling. If you are upgrading, run php artisan native:install both --force once — the fix only takes effect on a project regenerated by 4.x.

Check the extracted copy, not the zip — the zip being fresh proves nothing:

Native (Swift/Kotlin) code is compiled into the app and does NOT suffer from this — only the PHP bundle does.

Testing your integration

NativePHP 4.0 lets a plugin teach its own assertions to the test bridge, so a test of YOUR screen reads in terms of the editor rather than of bridge method strings:

Registered for you when the app is running its tests. Available:

Assertion Passes when
assertEditorOpened(?$option, $value) The editor was opened — optionally with a given option set
assertEditorNotOpened() It was not
assertSuggestionsOffered(?array $labels) You answered a lookup — optionally with exactly these rows
assertNoSuggestionsOffered() You did not
assertMediaInserted(?string $kind) A picked file was handed back
assertToolRun(string $tool) One of the editor's tools was asked for by name
assertTextInserted(?string $text) A host command wrote at the caret
assertAccessorySet($id, ?$value) A host control was updated to show a choice

Needs nativephp/mobile 4.0.0 or newer. Below that they are simply absent, which costs nothing.

License

MIT — see LICENSE.


All versions of wysiwyg-editor with dependencies

PHP Build Version
Package Version
No informations.
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package vipertecpro/wysiwyg-editor contains the following files

Loading the files please wait ...