收发文
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
{
|
||||
'name': 'TinyMCE Editor',
|
||||
'summary': 'TinyMCE Editor for Html Field',
|
||||
'author': 'Hoang Minh Hieu',
|
||||
'version': '17.0.1.0.0',
|
||||
'license': 'OPL-1',
|
||||
'support ': 'hieuhoangminh1996@gmail.com',
|
||||
'depends': ['web'],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'web_editor_tinymce/static/src/field_tinymce/field_tinymce.scss',
|
||||
'web_editor_tinymce/static/src/field_tinymce/field_tinymce.xml',
|
||||
'web_editor_tinymce/static/src/field_tinymce/field_tinymce.js',
|
||||
],
|
||||
},
|
||||
'images': ['static/description/main_screenshot.png'],
|
||||
'price': 35,
|
||||
'currency': 'USD',
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>This module replaces Odoo html editor with TinyMCE editor.</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<section class="mb64">
|
||||
<h2 class="text-info">
|
||||
This module replaces Odoo html editor with TinyMCE editor.
|
||||
</h2>
|
||||
</section>
|
||||
<section class="mb64">
|
||||
<h3>Screenshot</h3>
|
||||
<img src="main_screenshot.png" class="w-75">
|
||||
|
||||
<h3>Usage</h3>
|
||||
<img src="usage.png" class="w-50">
|
||||
</section>
|
||||
<section class="mb64">
|
||||
<h2 class="">
|
||||
Contact me
|
||||
</h2>
|
||||
<p>
|
||||
Skype: <a href="skype:live:hieuhoangminh1996">live:hieuhoangminh1996</a>
|
||||
<br>
|
||||
Email: <a href="mailto:hieuhoangminh1996@gmail.com">hieuhoangminh1996@gmail.com</a>
|
||||
<br>
|
||||
<br>
|
||||
Email subject should begin with
|
||||
<span class="text-info font-weight-bold">[Odoo Module]</span>
|
||||
</p>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
@@ -0,0 +1,130 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import {_t} from '@web/core/l10n/translation';
|
||||
import {Component, onWillStart, onMounted, onPatched, onWillUnmount, useEffect} from '@odoo/owl';
|
||||
import {standardFieldProps} from '@web/views/fields/standard_field_props';
|
||||
import {registry} from '@web/core/registry';
|
||||
import {TranslationButton} from '@web/views/fields/translation_button';
|
||||
import {loadJS} from '@web/core/assets';
|
||||
import {cookie} from '@web/core/browser/cookie';
|
||||
|
||||
export class FieldTinyMCE extends Component {
|
||||
static template = 'web_editor_tinymce.TinyMCE';
|
||||
static components = {
|
||||
TranslationButton,
|
||||
};
|
||||
static props = {
|
||||
...standardFieldProps,
|
||||
placeholder: {type: String, optional: true},
|
||||
dynamicPlaceholder: {type: Boolean, optional: true},
|
||||
dynamicPlaceholderModelReferenceField: {type: String, optional: true},
|
||||
};
|
||||
static defaultProps = {
|
||||
dynamicPlaceholder: false,
|
||||
};
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.editorId = `tinymce_${this.props.name}`
|
||||
const useDarkMode = this.theme === 'dark';
|
||||
this.defaultTinymceConfig = {
|
||||
selector: `#${this.editorId}`,
|
||||
plugins: 'autoresize preview importcss searchreplace autolink directionality code visualblocks visualchars fullscreen image link media codesample table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount help charmap quickbars emoticons accordion',
|
||||
editimage_cors_hosts: ['picsum.photos'],
|
||||
image_advtab: true,
|
||||
importcss_append: true,
|
||||
height: 'auto',
|
||||
image_caption: true,
|
||||
quickbars_selection_toolbar: 'bold italic | quicklink h2 h3 blockquote quickimage quicktable',
|
||||
noneditable_class: 'mceNonEditable',
|
||||
toolbar_mode: 'sliding',
|
||||
contextmenu: 'link image table',
|
||||
skin: useDarkMode ? 'oxide-dark' : 'oxide',
|
||||
content_css: useDarkMode ? 'dark' : 'default',
|
||||
content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:16px }',
|
||||
placeholder: this.props.placeholder,
|
||||
}
|
||||
onWillStart(async () => {
|
||||
await loadJS('web_editor_tinymce/static/lib/tinymce/tinymce.min.js');
|
||||
});
|
||||
onMounted(async () => {
|
||||
await this.initEditor();
|
||||
})
|
||||
useEffect(() => {
|
||||
this.updateEditor();
|
||||
})
|
||||
onWillUnmount(async () => {
|
||||
this.removeEditor()
|
||||
})
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.props.record.data[this.props.name] || '';
|
||||
}
|
||||
|
||||
get theme() {
|
||||
return cookie.get('color_scheme');
|
||||
}
|
||||
|
||||
get isTranslatable() {
|
||||
return this.props.record.fields[this.props.name].translate;
|
||||
}
|
||||
|
||||
get tinymceConfig() {
|
||||
return {
|
||||
...this.defaultTinymceConfig,
|
||||
menubar: !this.props.readonly && 'file edit view insert format tools table help',
|
||||
toolbar: !this.props.readonly && 'undo redo | accordion accordionremove | blocks fontfamily fontsize | bold italic underline strikethrough | align numlist bullist | link image | table media | lineheight outdent indent| forecolor backcolor removeformat | charmap emoticons | code fullscreen preview | save print | pagebreak anchor codesample | ltr rtl',
|
||||
readonly: this.props.readonly,
|
||||
setup: this.setupEditor.bind(this),
|
||||
init_instance_callback: this.initCallback.bind(this),
|
||||
}
|
||||
}
|
||||
|
||||
setupEditor(editor) {
|
||||
editor.on('change', e => {
|
||||
const value = tinymce.activeEditor.getContent();
|
||||
if (value !== this.value) {
|
||||
this.props.record.update({[this.props.name]: value})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeEditor() {
|
||||
if (this.editor) {
|
||||
this.editor.remove()
|
||||
}
|
||||
}
|
||||
|
||||
async initEditor() {
|
||||
this.removeEditor()
|
||||
const config = this.tinymceConfig;
|
||||
this.editor = (await tinymce.init(config))[0];
|
||||
}
|
||||
|
||||
initCallback(editor) {
|
||||
editor.setContent(this.value);
|
||||
}
|
||||
|
||||
updateEditor() {
|
||||
if (!this.editor || this.props.record.dirty) {
|
||||
return
|
||||
}
|
||||
this.editor.setContent(this.value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const fieldTinyMCE = {
|
||||
component: FieldTinyMCE,
|
||||
displayName: _t('TinyMCE'),
|
||||
supportedTypes: ['html', 'text'],
|
||||
extractProps: ({attrs, options}) => ({
|
||||
placeholder: attrs.placeholder,
|
||||
dynamicPlaceholder: options?.dynamic_placeholder || false,
|
||||
dynamicPlaceholderModelReferenceField:
|
||||
options?.dynamic_placeholder_model_reference_field || '',
|
||||
}),
|
||||
}
|
||||
|
||||
registry.category('fields').add('tinymce', fieldTinyMCE);
|
||||
@@ -0,0 +1,3 @@
|
||||
.o_field_tinymce {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<template xml:space="preserve">
|
||||
|
||||
<t t-name="web_editor_tinymce.TinyMCE" owl="1">
|
||||
<div class="tinymce_editor_container w-100 position-relative"
|
||||
t-att-class="{'o_field_translate': isTranslatable}"
|
||||
>
|
||||
<textarea t-att-id="editorId" t-ref="input"/>
|
||||
<span t-if="isTranslatable and !props.readonly"
|
||||
class="position-absolute top-0 end-0 p-2"
|
||||
style="z-index: 2;"
|
||||
>
|
||||
<TranslationButton
|
||||
fieldName="props.name"
|
||||
record="props.record"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</template>
|
||||
Reference in New Issue
Block a user