Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable language switch for text widget #1042

Merged
merged 37 commits into from
Aug 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
dcb99ad
Fix widget
herrvigg Nov 14, 2020
5f6e11e
Fix TinyMCE hooks and init
herrvigg Apr 18, 2021
fb3a7eb
Collect request from widget subtree
herrvigg Apr 28, 2021
99fbbfb
Collect widget POST
herrvigg May 1, 2021
65301bd
Fix refresh hook
herrvigg May 1, 2021
61400eb
Draft custom_html
herrvigg May 2, 2021
993d1b3
Rewrite refresh
herrvigg May 2, 2021
6e4e89b
Restore i18n config
herrvigg May 14, 2021
8d60f0a
Update with synced content
herrvigg May 14, 2021
14d2db2
Store editor ids
herrvigg May 14, 2021
5767640
Handle widget content hook in core
herrvigg May 14, 2021
d86c4b0
Update widget on save
herrvigg May 15, 2021
da7bf45
Fix dirty state
herrvigg May 15, 2021
f961b9e
Rework widget hooks
herrvigg May 16, 2021
60bd4db
Add attach options
herrvigg May 16, 2021
daaf5f5
Fix classlist for refresh
herrvigg May 16, 2021
5f26bda
Fix regression for post collect
herrvigg May 16, 2021
1b451e3
Remove unnecessary php filters
herrvigg May 16, 2021
b9b2006
Fix support generic title
herrvigg May 16, 2021
a64cdf7
Handle title hook on editor init
herrvigg May 19, 2021
1b9fb36
Reuse wpautop state from mce.settings
herrvigg May 19, 2021
3855b2b
Revert unnecessary widget collect
herrvigg May 19, 2021
75869e3
Add attachContentHook
herrvigg May 19, 2021
3612851
Merge master
herrvigg May 20, 2021
cf12734
Merge branch 'master' into fix/widget
herrvigg May 21, 2021
e2e2715
Simplify widgetId, clarify contentId
herrvigg May 22, 2021
99971d7
Fix widget sync on tab switch
herrvigg May 22, 2021
a569bec
Minor rename
herrvigg May 29, 2021
c61aea2
Merge branch 'master' into fix/widget
herrvigg Jun 13, 2021
bf8924e
Cleanup and doc
herrvigg Jun 25, 2021
fe611e9
Dynamic setup LSB
herrvigg Jun 26, 2021
3b0fd36
Cleanup
herrvigg Jun 26, 2021
c44176e
Update dist
herrvigg Jun 26, 2021
1d2e8ed
Refactor removeHook and refreshHook
herrvigg Jun 27, 2021
d4c5b2f
Merge master
herrvigg Jun 27, 2021
2015568
Disable block editor for widgets
herrvigg Aug 4, 2021
eecf1c0
Merge branch 'master' into fix/widget
herrvigg Aug 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 100 additions & 43 deletions admin/js/core/qtranslatex.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ const qTranslateX = function (pg) {
const qtx = this;

/**
* Internal state of the hooks
* Internal state of hooks and languageSwitch, not exposed
*/
const contentHooks = {};
const displayHookNodes = [];
const displayHookAttrs = [];
let languageSwitchInitialized = false;

/**
* Designed as interface for other plugin integration. The documentation is available at
Expand Down Expand Up @@ -89,6 +90,29 @@ const qTranslateX = function (pg) {
return contentHooks[id];
};

/**
* Attach an input field to an existing content hook.
*
* Usually, this function is called internally for an input field edited by the user.
* In some cases (e.g. widgets), the editable fields are different from those containing
* the translatable content. This function allows to attach them to the hook.
* The single content field initially attached is not updated anymore but the hidden fields
* storing each language content are still updated.
* @see addContentHook
* @see attachEditorHook
*
* @param inputField field editable by the user
* @param contentId optional element ID to override the content hook key (default: input ID)
*/
this.attachContentHook = function (inputField, contentId) {
const hook = contentHooks[contentId ? contentId : inputField.id];
if (!hook) {
return;
}
inputField.classList.add('qtranxs-translatable');
hook.contentField = inputField;
}

/**
* Designed as interface for other plugin integration. The documentation is available at
* https://github.com/qtranslate/qtranslate-xt/wiki/Integration-Guide
Expand Down Expand Up @@ -116,14 +140,19 @@ const qTranslateX = function (pg) {
}

if (!fieldName) {
if (!inputField.name) return false;
if (!inputField.name) {
console.error('Missing name in field', inputField);
return false;
}
fieldName = inputField.name;
}

if (inputField.id) {
if (contentHooks[inputField.id]) {
if ($.contains(document, inputField))
return contentHooks[inputField.id];
// otherwise some Java script already removed previously hooked element
console.warn('No input field with id=', inputField.id);
qtx.removeContentHook(inputField);
}
} else if (!contentHooks[fieldName]) {
Expand All @@ -136,16 +165,10 @@ const qTranslateX = function (pg) {
} while (contentHooks[inputField.id]);
}

/**
* Highlighting the translatable fields
* @since 3.2-b3
*/
inputField.classList.add('qtranxs-translatable');

const hook = contentHooks[inputField.id] = {};
hook.name = fieldName;
hook.contentField = inputField;
hook.lang = qTranslateConfig.activeLanguage;
qtx.attachContentHook(inputField);

let qtxPrefix;
if (encode) {
Expand Down Expand Up @@ -192,6 +215,7 @@ const qTranslateX = function (pg) {
contents = qtranxj_split(inputField.value);
// Substitute the current ML content with translated content for the current language
inputField.value = contents[hook.lang];

// Insert translated content for each language before the current field
for (const lang in contents) {
const text = contents[lang];
Expand Down Expand Up @@ -333,8 +357,9 @@ const qTranslateX = function (pg) {
* @since 3.3
*/
this.removeContentHook = function (inputField) {
if (!inputField || !inputField.id)
if (!inputField || !inputField.id) {
return false;
}
const hook = contentHooks[inputField.id];
if (!hook) {
return false;
Expand All @@ -351,6 +376,7 @@ const qTranslateX = function (pg) {
editor.getContainer().classList.remove('qtranxs-translatable');
editor.getElement().classList.remove('qtranxs-translatable');
}
// The current content field may not be the same as the input field, in case it was re-attached (e.g. widgets)
hook.contentField.classList.remove('qtranxs-translatable');
delete contentHooks[inputField.id];
inputField.classList.remove('qtranxs-translatable');
Expand Down Expand Up @@ -533,7 +559,10 @@ const qTranslateX = function (pg) {
// since 3.2.7
hook.contentField.placeholder = '';
}

hook.contentField.value = value;
// Some widgets such as text-widget sync the widget content on 'change' event on the input field
$(hook.contentField).trigger('change');
if (visualMode) {
updateMceEditorContent(hook);
}
Expand Down Expand Up @@ -628,8 +657,7 @@ const qTranslateX = function (pg) {
const className = qTranslateConfig.custom_field_classes[i];
qtx.addContentHooksByClass(className);
}
if (qTranslateConfig.LSB)
qtx.addContentHooksTinyMCE();
qtx.addContentHooksTinyMCE();
};

/**
Expand Down Expand Up @@ -746,22 +774,40 @@ const qTranslateX = function (pg) {
}
};

/** Link a TinyMCE editor with translatable content. The editor should be initialized for TinyMCE. */
const setEditorHooks = function (editor) {
/**
* Link a TinyMCE editor with translatable content.
*
* Usually, this function is called internally for an input field edited by the user.
* In some cases (e.g. widgets), the editable fields are different from those containing
* the translatable content. This function allows to attach them to the hook.
* The single content field initially attached is not updated anymore but the hidden fields
* storing each language content are still updated.
* @see attachContentHook
*
* @param editor tinyMCE editor, should be initialized for TinyMCE
* @param contentId optional element ID to override the content hook key (default: input ID)
* @return hook
*/
this.attachEditorHook = function (editor, contentId) {
if (!editor.id)
return;
const hook = contentHooks[editor.id];
return null;
// The MCE editor can be linked to translatable content having a different ID, e.g. for widgets
if (!contentId) {
contentId = editor.id;
}
const hook = contentHooks[contentId];
if (!hook)
return;
return null;
// The hook may have been created for a different content field, e.g. for widgets
// The main content field should always match the editor ID so that its value is synced on tab switch
if (contentId !== editor.id) {
hook.contentField = document.getElementById(editor.id);
}
if (hook.mce) {
return; // already initialized for qTranslate
return hook; // already initialized for qTranslate
}
hook.mce = editor;

/**
* Highlighting the translatable fields
* @since 3.2-b3
*/
editor.getContainer().classList.add('qtranxs-translatable');
editor.getElement().classList.add('qtranxs-translatable');

Expand All @@ -772,7 +818,7 @@ const qTranslateX = function (pg) {
* Sets hooks on HTML-loaded TinyMCE editors via tinyMCEPreInit.mceInit.
*/
this.addContentHooksTinyMCE = function () {
if (!window.tinyMCEPreInit) {
if (!window.tinyMCEPreInit || qTranslateConfig.RAW) {
return;
}
for (const key in contentHooks) {
Expand All @@ -781,7 +827,7 @@ const qTranslateX = function (pg) {
continue;
hook.mceInit = tinyMCEPreInit.mceInit[key];
tinyMCEPreInit.mceInit[key].init_instance_callback = function (editor) {
setEditorHooks(editor);
qtx.attachEditorHook(editor);
}
}
};
Expand All @@ -792,7 +838,7 @@ const qTranslateX = function (pg) {
this.loadAdditionalTinyMceHooks = function () {
if (window.tinyMCE) {
tinyMCE.get().forEach(function (editor) {
setEditorHooks(editor);
qtx.attachEditorHook(editor);
});
}
};
Expand Down Expand Up @@ -1131,6 +1177,33 @@ const qTranslateX = function (pg) {
}
};

/**
* Setup the language switching buttons, meta box and listeners.
*
* Usually, this is called internally after the display and content hooks have been added.
* However some pages may initialize the hooks later on events (e.g. widget-added).
* Switching buttons should only be created if there is at least one hook, so this offers
* the possibility to setup the language switch dynamically later.
*/
this.setupLanguageSwitch = function () {
if (languageSwitchInitialized || !qTranslateConfig.LSB) {
return;
}
if (!displayHookNodes.length && !displayHookAttrs.length && !Object.keys(contentHooks).length) {
return;
}

setupMetaBoxLSB();
setupAnchorsLSB();
// Synchronization of multiple sets of Language Switching Buttons
qtx.addLanguageSwitchListener(onTabSwitch);
if (pg.onTabSwitch) {
qtx.addLanguageSwitchListener(pg.onTabSwitch);
}

languageSwitchInitialized = true;
}

const initialize = function () {
if (qTranslateConfig.LSB) {
qTranslateConfig.activeLanguage = getStoredEditLanguage();
Expand Down Expand Up @@ -1164,25 +1237,9 @@ const qTranslateX = function (pg) {

addMultilingualHooks();

if (!displayHookNodes.length && !displayHookAttrs.length && !Object.keys(contentHooks).length) {
return;
}

if (!qTranslateConfig.RAW) {
qtx.addContentHooksTinyMCE();
}
qtx.addContentHooksTinyMCE();

if (qTranslateConfig.LSB) {
setupMetaBoxLSB();
setupAnchorsLSB();
/**
* @since 3.2.4 Synchronization of multiple sets of Language Switching Buttons
*/
qtx.addLanguageSwitchListener(onTabSwitch);
if (pg.onTabSwitch) {
qtx.addLanguageSwitchListener(pg.onTabSwitch);
}
}
qtx.setupLanguageSwitch();
};

initialize();
Expand Down
67 changes: 57 additions & 10 deletions admin/js/pages/widgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,72 @@
/wp-admin/widgets.php
*/
'use strict';

const $ = jQuery;

$(document).on('qtxLoadAdmin:widgets', (event, qtx) => {
if (!window.wpWidgets)
return;

const onWidgetUpdate = function (evt, widget) {
widget.find('span.in-widget-title').each(function (i, e) {
qtx.addDisplayHook(e);
});
widget.find("input[id^='widget-'][id$='-title']").each(function (i, e) {
qtx.refreshContentHook(e);
});
widget.find("textarea[id^='widget-text-'][id$='-text']").each(function (i, e) {
qtx.refreshContentHook(e);
jQuery(document).on('tinymce-editor-init', (event, editor) => {
const widget = $(editor.settings.selector).parents('.widget');
const widgetId = widget.find('.widget-id').val();
// The title is not dependent on TinyMCE
// But the widget input fields are created dynamically by WP when the area is shown
const titleContentId = 'widget-' + widgetId + '-title';
widget.find(".text-widget-fields input[id$='_title']").each(function (i, e) {
qtx.attachContentHook(e, titleContentId);
});
const textContentId = 'widget-' + widgetId + '-text';
qtx.attachEditorHook(editor, textContentId);
});

const onWidgetUpdate = function (evt, widget) {
const widgetBase = widget.find('.id_base').val();
switch (widgetBase) {
case 'text':
const widgetId = widget.find('.widget-id').val();
const fieldTitle = widget.find(".text-widget-fields input[id$='_title']");
widget.find(".widget-content input[id^='widget-text-'][id$='-title']").each(function (i, e) {
qtx.refreshContentHook(e);
qtx.attachContentHook(fieldTitle[0], e.id);
});

const fieldText = widget.find(".text-widget-fields textarea[id$='_text']");
const editor = window.tinyMCE.get(fieldText[0].id);
widget.find(".widget-content textarea[id^='widget-text-'][id$='-text']").each(function (i, e) {
qtx.refreshContentHook(e);
if (editor) {
qtx.attachEditorHook(editor, e.id);
// The text field has not been synced after translation yet.
// Because the text field has not been updated by wp.widgets when in Visual Mode,
// it still has the translated content before saving the widget.
// To allow updateField to change the MCE content, change the value of the text field.
const syncInput = widget.find('.sync-input.text');
fieldText.val(syncInput.val() + '*');
}
});
if (widgetId in wp.textWidgets.widgetControls) {
wp.textWidgets.widgetControls[widgetId].updateFields();
}
break;
default:
widget.find(".widget-content input[id^='widget-'][id$='-title']").each(function (i, e) {
qtx.refreshContentHook(e);
});
break;
}
wpWidgets.appendTitle(widget);
};

const onWidgetAdded = function (evt, widget) {
// Rely on refreshContent to create hooks
onWidgetUpdate(evt, widget);
// The LSB may not be initialized yet if all widget areas were empty on page load
qtx.setupLanguageSwitch();
};

$(document).on('widget-added', onWidgetUpdate);
$(document).on('widget-added', onWidgetAdded);
$(document).on('widget-updated', onWidgetUpdate);

const onLanguageSwitchAfter = function () {
Expand Down
4 changes: 4 additions & 0 deletions admin/qtx_admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,10 @@ function qtranxf_admin_load() {
if ( version_compare( $wp_version, '5.0' ) >= 0 ) {
require_once( QTRANSLATE_DIR . '/admin/qtx_admin_gutenberg.php' );
}

// Disable the block editor from managing widgets, including the Gutenberg plugin
add_filter( 'gutenberg_use_widgets_block_editor', '__return_false', 99 );
add_filter( 'use_widgets_block_editor', '__return_false', 99 );
}

qtranxf_admin_load();
Loading