Skip to content

Plugin does not work #1

Description

@1amrahulranjan
  1. Can use CapitalizeMyTitle API - https://capitalizemytitle.com/api/ or
  2. Feed CapitalizeMyTitle Dictionary or model to plugin to work or
  3. Can add aA button next to Formatter (A) button in TinyMCE iFrame of Title and Subtitle. Use DOM analyser to find the solution.

Working Knowledge till now is here:

Title Capitalizer Plugin – Developer Handoff Notes

Context

OMP (Open Monograph Press) 3.4.0.9 installation at inkbound.org.

A custom generic plugin titlecapitalizer is meant to auto-capitalize the Title and Subtitle fields in the Publication → Title & Abstract tab of the editorial workflow when an editor types or pastes text.

GitHub repo: https://github.com/OJSpro/title-capitalizer


What the Title & Subtitle Fields Actually Are

Important

These are NOT plain <input> elements. They are TinyMCE rich-text editors running inside <iframe> elements.

Confirmed from live DOM inspection via browser console:

tinymce.get().map(e => e.id)
// Returns:
// [
//   "titleAbstract-title-control-en",      ← Title field
//   "titleAbstract-subtitle-control-en",   ← Subtitle field
//   "titleAbstract-abstract-control-en",   ← Abstract (full TinyMCE with toolbar)
//   "metadata-dataAvailability-control-en"
// ]

The Title and Subtitle use component: "field-rich-text" with size: "oneline" (defined in Vue component state). They appear visually as plain text boxes because TinyMCE in oneline mode hides the toolbar until the field is focused.

TinyMCE Editor IDs (locale-suffixed)

Field Editor ID
Title titleAbstract-title-control-en
Subtitle titleAbstract-subtitle-control-en

Note

The -en suffix is the locale. For multilingual installations it may also be -fr, -de, etc. The regex /-title-|-subtitle-/ on the editor ID correctly matches all locales.


Correct TinyMCE Hook Pattern

The PlainPaste plugin (/plugins/generic/plainPaste/js/plainPaste.js) successfully hooks into these same editors. Its working pattern is:

function init() {
    if (typeof tinymce === 'undefined') return false;

    // 1. Hook into already-initialized editors
    tinymce.get().forEach(function(editor) {
        setupEditor(editor);
    });

    // 2. Hook into future editors (Vue lazy-mounts tabs)
    tinymce.on('AddEditor', function(e) {
        setupEditor(e.editor);
    });

    return true;
}

// 3. Polling retry — TinyMCE may not be ready immediately
if (!init()) {
    var attempts = 0;
    var poll = setInterval(function() {
        attempts++;
        if (init() || attempts >= 20) clearInterval(poll);
    }, 500);
}

Caution

Do NOT use tinymce.editors — that is not a valid public API in the bundled TinyMCE version. Use tinymce.get() instead.


Current Plugin JS (js/titleCapitalizer.js)

(function () {

    var SMALL_WORDS = /^(a|an|and|as|at|but|by|en|for|if|in|nor|of|on|or|per|so|the|to|v\.?|vs\.?|via|yet)$/i;

    function capitalizeTitle(str, style) {
        str = (str || '').replace(/<[^>]+>/g, '').replace(/&[a-z]+;/gi, ' ').replace(/\s+/g, ' ').trim();
        if (!str) return str;
        switch (style) {
            case 'uppercase': return str.toUpperCase();
            case 'lowercase': return str.toLowerCase();
            case 'sentence':  return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
            default: // title case
                return str.replace(/[A-Za-z0-9\u00C0-\u00FF]+[^\s-]*/g, function (word, index, full) {
                    if (index === 0 || index + word.length === full.length)
                        return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
                    if (/[A-Z]{2,}/.test(word)) return word;
                    if (SMALL_WORDS.test(word) && full.charAt(index - 2) !== ':')
                        return word.toLowerCase();
                    return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
                });
        }
    }

    function isTitleEditor(editor) {
        return /-title-|-subtitle-/.test((editor.id || '').toLowerCase());
    }

    function applyToEditor(editor) {
        var style = window.titleCapitalizerStyle || 'chicago';
        var text = editor.getContent({ format: 'text' }).trim();
        var capitalized = capitalizeTitle(text, style);
        if (!capitalized || capitalized === text) return;
        editor.setContent(capitalized);
        editor.fire('change');
    }

    function setupEditor(editor) {
        if (!isTitleEditor(editor)) return;
        if (editor._tcHooked) return;
        editor._tcHooked = true;

        editor.on('blur', function () { applyToEditor(editor); });
        editor.on('PastePreProcess PastePostProcess', function () {
            setTimeout(function () { applyToEditor(editor); }, 200);
        });
    }

    function init() {
        if (typeof tinymce === 'undefined') return false;
        tinymce.get().forEach(setupEditor);
        tinymce.on('AddEditor', function (e) { setupEditor(e.editor); });
        return true;
    }

    if (!init()) {
        var attempts = 0;
        var poll = setInterval(function () {
            attempts++;
            if (init() || attempts >= 20) clearInterval(poll);
        }, 500);
    }

})();

Current Status: Unresolved Issue

The plugin JS loads on the page (confirmed via document.querySelectorAll('script[src*="titleCapitalizer"]').length), and TinyMCE editors exist with the correct IDs.

However, editor._tcHooked remains undefined on the title editor after page load, meaning setupEditor() is not being called — despite tinymce.get() returning the editors in the console.

Suspected cause

There may be a timing race: tinymce.get() may return an empty array when our script first executes (Vue hasn't mounted yet), and tinymce.on('AddEditor', ...) may fire before our listener is registered (because TinyMCE initializes inside a Vue component that mounts on DOMContentLoaded, potentially before our deferred script runs).

What to try next

  1. Console test to confirm the hook works manually:
var ed = tinymce.get('titleAbstract-title-control-en');
ed.on('blur', function() {
    var t = ed.getContent({format:'text'});
    ed.setContent(t.toUpperCase());
});
// Now click inside title field then click outside — should UPPERCASE
  1. If manual hook works, the issue is purely timing of script load vs. TinyMCE init. Solution: load our JS earlier (e.g., hook into a Vue event or use window.pkp.eventBus if available in OMP 3.4).

  2. Check OMP 3.4 Vue event bus for when forms are ready:

// In OMP 3.4, the Vue app may emit events when components mount
window.pkp && window.pkp.eventBus && window.pkp.eventBus.$on('form-mounted', function(id) {
    if (id === 'titleAbstract') {
        tinymce.get().forEach(setupEditor);
    }
});
  1. Alternative approach — use tinymce.PluginManager to register a plugin that runs setup on every editor init:
tinymce.PluginManager.add('titleCapitalizer', function(editor) {
    if (!/-title-|-subtitle-/.test(editor.id)) return;
    editor.on('blur', function() { /* capitalize */ });
});
// Then the plugin name must be added to TinyMCE's plugins list — requires PHP change

PHP Plugin File Location

/plugins/generic/titlecapitalizer/
├── TitleCapitalizerPlugin.php   ← Registers hook, injects JS
├── index.php
├── version.xml
├── js/
│   └── titleCapitalizer.js      ← The JS that needs to work
├── locale/
│   └── en/locale.po
└── templates/
    └── settings.tpl

The PHP uses Hook::add('TemplateManager::display', [$this, 'injectJS']) with contexts => 'backend' and priority => STYLE_SEQUENCE_LAST.

window.titleCapitalizerStyle is set via addHeader before the script runs and contains the selected style ('chicago', 'apa', 'sentence', 'uppercase', 'lowercase').

Metadata

Metadata

Labels

bugSomething isn't working

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions