第14讲、Odoo 18 实现一个Markdown Widget模块
目录
- 模块概述
- 安装与配置
- 前端实现详解
- 依赖库分析
- 使用示例与最佳实践
- 技术架构与设计模式分析
- 总结
模块概述
模块地址https://apps.odoo.com/apps/modules/18.0/web_widget_markdown
Odoo 18 Markdown Widget 是一个为 Odoo 18.0 社区版开发的全局通用模块,它允许在 Odoo 表单中为 HTML 和 Text 类型字段添加 Markdown 编辑和预览功能。该模块通过自定义字段小部件 bootstrap_markdown
实现,使用户能够以 Markdown 格式编写内容,并实时预览渲染后的 HTML 效果。
模块结构
模块采用标准的 Odoo 插件结构组织,主要包含以下部分:
-
模块定义文件:
__init__.py
:模块初始化文件__manifest__.py
:模块清单文件,定义模块的基本信息和依赖
-
前端资源:
static/src/js/web_widget_text_markdown.js
:核心 JavaScript 实现static/src/css/web_widget_text_markdown.css
:样式定义static/src/lib/
:依赖库文件目录
-
依赖库:
showdown.js
:Markdown 转 HTML 的核心库bootstrap-markdown.js
:基于 Bootstrap 的 Markdown 编辑器- 其他辅助库:如
showdown-toc.js
、showdown-table.js
等扩展功能
实现细节分析
模块清单文件 (__manifest__.py
)
模块清单定义了模块的基本信息、依赖和资产:
{"name": "Widget Text Markdown","version": "18.0","author": "melon, ","description": "Markdown格式插件","category": "Tools","license": 'LGPL-3',"website": "https://hxmelon.com","summary": "Widget adds markdown support","images": ["static/description/img2.png"],"depends": ["web", "base"],"data": [],"installable": True,"auto_install": False,"application": True,"assets": {"web.assets_backend": [# CSS"web_widget_markdown/static/src/css/web_widget_text_markdown.css",# Component"web_widget_markdown/static/src/js/web_widget_text_markdown.js",# Dependencies"/web/static/src/views/fields/text/text_field.js",],},
}
关键点:
- 模块依赖于
web
和base
模块 - 资产定义使用 Odoo 18 的新资产管理系统
- 没有定义
data
文件,说明不需要额外的 XML 数据文件 - 模块被标记为可安装、非自动安装的应用程序
集成与使用方式
根据 README.md 和代码分析,该模块的使用非常简单:
- 安装模块
- 在 XML 视图定义中,为 HTML 或 Text 类型字段添加
widget="bootstrap_markdown"
属性 - 完整示例:
<field name="ai_response" widget="bootstrap_markdown"/>
这种简单的集成方式使得该模块可以轻松应用于任何需要 Markdown 编辑功能的场景。
安装与配置
安装要求
在安装 Odoo 18 Markdown Widget 模块前,请确保您的环境满足以下要求:
- Odoo 版本:Odoo 18.0 社区版
- 依赖模块:
web
:Odoo 的 Web 客户端基础模块base
:Odoo 的基础模块
安装步骤
方法一:通过 ZIP 文件安装
- 下载模块 ZIP 文件
- 登录 Odoo 管理后台
- 进入 应用 菜单
- 点击顶部的 更新应用列表 按钮
- 点击顶部的 安装应用 按钮
- 在搜索框中输入 “Markdown”
- 找到 “Widget Text Markdown” 模块并点击 安装 按钮
方法二:通过文件系统安装
- 解压模块 ZIP 文件
- 将解压后的
web_widget_markdown
目录复制到 Odoo 的 addons 目录中cp -r web_widget_markdown /path/to/odoo/addons/
- 重启 Odoo 服务
service odoo restart # 或 systemctl restart odoo
- 登录 Odoo 管理后台
- 进入 应用 菜单
- 点击顶部的 更新应用列表 按钮
- 在搜索框中输入 “Markdown”
- 找到 “Widget Text Markdown” 模块并点击 安装 按钮
配置与使用
安装完成后,无需额外配置即可使用。该模块是一个全局通用模块,可以在任何表单视图中为 HTML 和 Text 类型的字段添加 Markdown 编辑功能。
基本用法
在 XML 视图定义中,为需要支持 Markdown 的字段添加 widget="bootstrap_markdown"
属性:
<field name="description" widget="bootstrap_markdown"/>
适用字段类型
该小部件支持以下字段类型:
text
:文本字段html
:HTML 字段
示例配置
以下是在一个自定义模块中使用 Markdown 小部件的完整示例:
<?xml version="1.0" encoding="utf-8"?>
<odoo><record id="view_my_model_form" model="ir.ui.view"><field name="name">my.model.form</field><field name="model">my.model</field><field name="arch" type="xml"><form string="My Model"><sheet><group><field name="name"/><field name="description" widget="bootstrap_markdown"/></group></sheet></form></field></record>
</odoo>
在上面的例子中,description
字段将使用 Markdown 编辑器,允许用户输入 Markdown 格式的内容并实时预览渲染效果。
验证安装
安装完成后,可以通过以下步骤验证模块是否正常工作:
- 创建或编辑一个包含 HTML 或 Text 类型字段的表单视图
- 为该字段添加
widget="bootstrap_markdown"
属性 - 保存视图并刷新页面
- 打开表单,检查字段是否显示为 Markdown 编辑器
- 尝试输入一些 Markdown 格式的文本(如
# 标题
、**粗体**
等) - 点击 “预览” 按钮,验证 Markdown 是否正确渲染为 HTML
如果以上步骤都正常工作,则说明模块安装成功并可以正常使用。
前端实现详解
前端架构概述
Odoo 18 Markdown Widget 的前端实现基于 Odoo 18 的 OWL (Odoo Web Library) 框架,采用组件化的开发方式。核心实现位于 web_widget_text_markdown.js
文件中,定义了一个名为 MarkdownField
的 OWL 组件,并将其注册为字段小部件。
OWL 组件实现
组件定义与注册
/** @odoo-module **/import { registry } from "@web/core/registry";
import { _t } from "@web/core/l10n/translation";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
import { Component, markup, xml, useState } from "@odoo/owl";
import { useRecordObserver } from "@web/model/relational_model/utils";
import { Mutex } from "@web/core/utils/concurrency";export class MarkdownField extends Component {// 组件实现...
}// 注册字段组件
export const markdownField = {component: MarkdownField,displayName: _t("Markdown"),supportedTypes: ["text", "html"],extractProps({ attrs }) {return {placeholder: attrs.placeholder,};},
};registry.category("fields").add("bootstrap_markdown", markdownField);
这段代码展示了组件的基本结构和注册过程:
- 导入必要的 OWL 和 Odoo 框架组件
- 定义
MarkdownField
组件类 - 创建
markdownField
配置对象,指定支持的字段类型和属性提取方法 - 通过
registry.category("fields").add()
将组件注册为名为 “bootstrap_markdown” 的字段小部件
组件模板
static template = xml`<div class="o_field_markdown" t-att-class="{'o_field_readonly': props.readonly}"><div class="d-flex justify-content-end mb-2" t-if="!props.readonly"><button class="btn btn-sm btn-secondary toggle-preview-btn" t-on-click="togglePreview"><t t-esc="state.isPreview ? '编辑' : '预览'"/></button></div><div t-if="state.isPreview || props.readonly" class="markdown-preview border p-2 rounded"><div class="markdown-content" t-out="state.htmlContent"/></div><textarea t-else=""class="o_field_text_markdown_editor o_input" t-ref="input"t-att-name="props.name"t-on-input="onInput"t-on-blur="onBlur"rows="10"t-model="state.value"/></div>
`;
模板定义了组件的 UI 结构:
- 外层容器
div.o_field_markdown
,根据只读状态添加不同的类 - 编辑/预览切换按钮,仅在非只读模式下显示
- Markdown 预览区域,在预览模式或只读模式下显示
- 文本编辑区域
textarea
,在编辑模式下显示
模板使用 OWL 的指令:
t-att-class
:动态设置类名t-if
/t-else
:条件渲染t-on-click
/t-on-input
/t-on-blur
:事件绑定t-esc
:转义输出文本t-out
:非转义输出 HTMLt-model
:双向数据绑定
组件属性
static props = {...standardFieldProps,placeholder: { type: String, optional: true },
};
组件属性继承了标准字段属性 standardFieldProps
,并添加了一个可选的 placeholder
属性。
组件初始化与状态管理
setup() {this.mutex = new Mutex();this.isDirty = false;this.state = useState({isPreview: false,value: this.props.record.data[this.props.name] || "",htmlContent: markup(this.convertToHtml(this.props.record.data[this.props.name] || "")),});useRecordObserver((record) => {if (!this.isDirty) {const newValue = record.data[this.props.name] || "";if (this.lastValue !== newValue) {this.state.value = newValue;this.state.htmlContent = markup(this.convertToHtml(newValue));this.lastValue = newValue;}}});// 监听模型的保存事件const { model } = this.props.record;this.env.bus.addEventListener("WILL_SAVE_URGENTLY", () => this.commitChanges({ urgent: true }));model.bus.addEventListener("NEED_LOCAL_CHANGES", ({ detail }) => {detail.proms.push(this.commitChanges());});
}
setup
方法在组件初始化时执行,完成以下工作:
- 创建互斥锁
mutex
,用于控制异步操作的顺序 - 初始化
isDirty
标志,用于跟踪编辑状态 - 使用
useState
创建响应式状态对象,包含:isPreview
:是否处于预览模式value
:当前文本值htmlContent
:转换后的 HTML 内容
- 使用
useRecordObserver
监听记录变化,当记录数据更新时更新组件状态 - 监听保存事件,确保在保存前提交更改
编辑与预览切换
togglePreview() {if (this.props.readonly) {return;}this.state.isPreview = !this.state.isPreview;if (this.state.isPreview) {this.state.htmlContent = markup(this.convertToHtml(this.state.value));}
}
togglePreview
方法实现编辑和预览模式的切换:
- 如果字段是只读的,则不执行任何操作
- 切换
isPreview
状态 - 如果切换到预览模式,则将当前文本转换为 HTML 并更新
htmlContent
数据同步与提交
onInput(ev) {if (this.props.readonly) {return;}this.state.value = ev.target.value;this.isDirty = true;this.props.record.model.bus.trigger("FIELD_IS_DIRTY", true);
}async onBlur() {return this.commitChanges();
}async commitChanges({ urgent } = {}) {if (urgent) {this._commitChanges({ urgent });} else {return this.mutex.exec(() => this._commitChanges({ urgent }));}
}async _commitChanges({ urgent }) {if (this.isDirty) {this.lastValue = this.state.value;this.isDirty = false;await this.props.record.update({ [this.props.name]: this.state.value }).catch(() => {this.isDirty = true;});this.props.record.model.bus.trigger("FIELD_IS_DIRTY", this.isDirty);}
}
这些方法处理数据同步和提交:
onInput
:处理输入事件,更新状态并标记为脏onBlur
:处理失焦事件,提交更改commitChanges
:提交更改的外部接口,使用互斥锁控制并发_commitChanges
:内部提交方法,将更改同步到记录模型
Markdown 转换实现
convertToHtml(text) {if (!text) return "";// 简单的 Markdown 转换规则const html = text// 标题.replace(/^# (.*$)/gm, '<h1>$1</h1>').replace(/^## (.*$)/gm, '<h2>$1</h2>').replace(/^### (.*$)/gm, '<h3>$1</h3>')// 斜体和粗体.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>').replace(/\*(.*?)\*/g, '<em>$1</em>')// 链接.replace(/\[([^\[]+)\]\(([^\)]+)\)/g, '<a href="$2">$1</a>')// 列表.replace(/^\s*\n\*/gm, '<ul>\n*').replace(/^(\*.+)\s*\n([^\*])/gm, '$1\n</ul>\n\n$2').replace(/^\*(.+)/gm, '<li>$1</li>')// 代码块.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>')// 内联代码.replace(/`(.*?)`/g, '<code>$1</code>')// 引用.replace(/^\> (.*$)/gm, '<blockquote>$1</blockquote>');return html;
}
convertToHtml
方法实现了 Markdown 到 HTML 的转换:
- 使用一系列正则表达式替换规则
- 支持基本的 Markdown 语法,包括标题、粗体、斜体、链接、列表、代码块和引用
- 返回转换后的 HTML 字符串
值得注意的是,虽然模块包含了 Showdown.js 等第三方 Markdown 转换库,但实际实现中使用了自定义的转换函数,这可能是为了简化实现或减少依赖。
CSS 样式实现
CSS 样式定义在 web_widget_text_markdown.css
文件中,为编辑器和预览区域提供了样式支持:
编辑器样式
.o_field_text_markdown_editor {width: 100%;min-height: 120px;border: 1px solid #ccc;border-radius: 3px;padding: 8px;font-family: monospace;
}
编辑器样式设置了宽度、最小高度、边框、圆角和内边距,并使用等宽字体以便于编辑 Markdown。
预览区域样式
.markdown-preview {min-height: 100px;background-color: #f8f9fa;
}
预览区域设置了最小高度和背景色,确保内容显示清晰。
Markdown 元素样式
.markdown-content h1 { font-size: 2.5rem; }
.markdown-content h2 { font-size: 2rem; }
.markdown-content h3 { font-size: 1.75rem; }
/* 更多样式... */
为各种 Markdown 元素(标题、列表、代码块等)定义了样式,确保渲染后的 HTML 美观一致。
资产管理与加载
模块使用 Odoo 18 的资产管理系统加载 CSS 和 JavaScript 文件:
"assets": {"web.assets_backend": [# CSS"web_widget_markdown/static/src/css/web_widget_text_markdown.css",# Component"web_widget_markdown/static/src/js/web_widget_text_markdown.js",# Dependencies"/web/static/src/views/fields/text/text_field.js",],
},
这段配置将 CSS 和 JavaScript 文件添加到 Odoo 的后端资产包中,确保它们在后端界面加载时被正确引入。
前端实现总结
Odoo 18 Markdown Widget 的前端实现采用了现代化的组件开发方式,基于 OWL 框架构建,具有以下特点:
- 组件化架构:使用 OWL 组件封装 UI 和逻辑,便于维护和扩展
- 响应式状态管理:使用
useState
管理组件状态,实现 UI 与数据的自动同步 - 声明式模板:使用 XML 模板声明 UI 结构,提高代码可读性
- 事件驱动:通过事件处理用户交互和数据同步
- 自定义 Markdown 转换:实现简单高效的 Markdown 到 HTML 转换
- 样式隔离:通过 CSS 类名前缀确保样式不会影响其他组件
这种实现方式符合 Odoo 18 的前端开发最佳实践,提供了良好的用户体验和开发者友好性。
依赖库分析
依赖库概述
Odoo 18 Markdown Widget 模块包含了多个第三方库,这些库为 Markdown 编辑和渲染提供了支持。虽然在核心实现中使用了自定义的 Markdown 转换函数,但这些库仍然被包含在模块中,可能是为了提供更丰富的功能或作为备选方案。本章将详细分析这些依赖库的功能、版本和集成方式。
Showdown.js
基本信息
- 文件路径:
/static/src/lib/showdown.js
- 版本:2.0.0-alpha1
- 官方网站:https://showdownjs.com/
- 许可证:MIT
功能介绍
Showdown.js 是一个功能强大的 JavaScript Markdown 解析器,可以将 Markdown 文本转换为 HTML。它是 John Gruber 的原始 Markdown 规范的 JavaScript 实现,支持标准 Markdown 语法以及多种扩展。
主要特性
- 标准 Markdown 支持:完整支持 John Gruber 的 Markdown 规范
- 扩展系统:通过扩展机制支持额外的语法和功能
- 配置选项:提供丰富的配置选项,可以自定义转换行为
- 浏览器和 Node.js 兼容:可以在浏览器和 Node.js 环境中使用
- 双向转换:不仅支持 Markdown 到 HTML 的转换,还支持 HTML 到 Markdown 的转换
在模块中的集成
虽然 Showdown.js 被包含在模块中,但在核心 JavaScript 实现中并没有直接使用它。模块使用了自定义的 convertToHtml
方法进行 Markdown 转换。这可能是出于以下考虑:
- 简化实现:自定义转换函数可能更简单,更容易维护
- 减少依赖:避免对第三方库的依赖,减少潜在的兼容性问题
- 性能优化:针对特定需求优化的转换函数可能比通用库更高效
- 备选方案:保留 Showdown.js 作为备选方案,以便在需要更复杂功能时使用
Bootstrap-Markdown.js
基本信息
- 文件路径:
/static/src/lib/bootstrap-markdown.js
- 版本:2.10.0
- 官方仓库:https://github.com/toopay/bootstrap-markdown
- 许可证:Apache License 2.0
功能介绍
Bootstrap-Markdown.js 是一个基于 Bootstrap 的 Markdown 编辑器插件,提供了一个用户友好的界面,用于编辑 Markdown 文本。它包含工具栏按钮,用于插入常见的 Markdown 元素,如标题、列表、链接等。
主要特性
- 工具栏:提供常用 Markdown 元素的按钮
- 实时预览:支持实时预览 Markdown 渲染结果
- 快捷键:支持键盘快捷键
- 自定义按钮:允许添加自定义工具栏按钮
- 事件系统:提供丰富的事件钩子
- 国际化:支持多语言界面
在模块中的集成
与 Showdown.js 类似,Bootstrap-Markdown.js 也被包含在模块中,但在核心实现中并未直接使用。模块使用了自定义的 UI 组件,而非 Bootstrap-Markdown.js 提供的编辑器。这可能是因为:
- OWL 框架集成:使用 OWL 组件可以更好地集成到 Odoo 的前端框架中
- UI 一致性:自定义组件可以保持与 Odoo 界面的一致性
- 简化依赖:减少对第三方库的依赖
- 备选方案:保留 Bootstrap-Markdown.js 作为备选方案
Showdown 扩展库
基本信息
模块包含了多个 Showdown.js 的扩展库:
- showdown-toc.js:为 Showdown 添加目录生成功能
- showdown-table.js:为 Showdown 添加表格支持
- showdown-footnotes.js:为 Showdown 添加脚注支持
功能介绍
这些扩展库为 Showdown.js 添加了额外的功能,使其能够支持更多的 Markdown 语法和特性:
- showdown-toc.js:自动生成文档目录,基于标题层级
- showdown-table.js:支持 Markdown 表格语法
- showdown-footnotes.js:支持脚注语法
在模块中的集成
这些扩展库与 Showdown.js 一起被包含在模块中,但同样未在核心实现中直接使用。它们可能是为了在未来版本中提供更丰富的功能,或者作为备选方案。
Marked.js
基本信息
- 文件路径:
/static/src/lib/marked.js
- 官方网站:https://marked.js.org/
- 许可证:MIT
功能介绍
Marked.js 是另一个流行的 JavaScript Markdown 解析器,以其速度和安全性而闻名。它支持标准 Markdown 语法,并提供了丰富的配置选项。
主要特性
- 高性能:Marked.js 以其高效的解析速度而著称
- 安全性:内置防止 XSS 攻击的安全措施
- 扩展性:支持自定义渲染器和扩展
- 兼容性:支持多种环境,包括浏览器和 Node.js
在模块中的集成
Marked.js 也被包含在模块中,但未在核心实现中直接使用。它可能是作为 Showdown.js 的替代方案,或者为未来版本提供更多选择。
jQuery
基本信息
- 文件路径:
/static/lib/jquery.min.js
- 官方网站:https://jquery.com/
- 许可证:MIT
功能介绍
jQuery 是一个快速、小巧、功能丰富的 JavaScript 库,它简化了 HTML 文档遍历、事件处理、动画和 Ajax 交互。
在模块中的集成
jQuery 被包含在模块中,可能是为了支持 Bootstrap-Markdown.js,因为后者依赖于 jQuery。然而,在 Odoo 18 中,前端框架已经不再依赖 jQuery,而是使用原生 JavaScript 和 OWL 框架。
依赖库集成策略分析
通过分析模块中包含的依赖库和实际实现,可以看出模块采用了一种灵活的依赖管理策略:
- 自定义实现优先:核心功能使用自定义实现,避免对第三方库的直接依赖
- 备选方案保留:保留第三方库作为备选方案,以便在需要时使用
- 渐进增强:为未来版本的功能扩展预留空间
这种策略有以下优势:
- 减少依赖:降低对外部库的依赖,减少潜在的兼容性问题
- 提高可控性:自定义实现更容易控制和维护
- 性能优化:针对特定需求优化的实现可能比通用库更高效
- 灵活性:保留多种选择,可以根据需要切换实现方式
依赖库版本管理
模块包含的依赖库版本相对较旧,例如:
- Showdown.js:2.0.0-alpha1
- Bootstrap-Markdown.js:2.10.0
这可能是因为:
- 稳定性:选择经过验证的稳定版本
- 兼容性:确保与 Odoo 环境的兼容性
- 最小变更:避免因库更新带来的不必要变更
在未来版本中,可以考虑更新这些库到最新版本,以获取新功能和安全修复。
依赖库安全性考虑
包含第三方库时,安全性是一个重要考虑因素。模块中的库可能存在潜在的安全风险,特别是较旧版本的库。建议:
- 定期更新:定期检查并更新依赖库到最新版本
- 安全审计:对包含的库进行安全审计
- 最小化使用:只包含必要的库,减少攻击面
总结
Odoo 18 Markdown Widget 模块包含了多个功能强大的第三方库,为 Markdown 编辑和渲染提供了支持。虽然在当前实现中并未直接使用这些库,但它们为模块提供了扩展性和灵活性,为未来版本的功能增强奠定了基础。模块采用的依赖管理策略平衡了自定义实现和第三方库的优势,提供了一个稳定、可靠的 Markdown 编辑解决方案。
使用示例与最佳实践
基本使用示例
Odoo 18 Markdown Widget 的使用非常简单直观,只需在视图定义中为字段添加 widget="bootstrap_markdown"
属性即可。以下是几个常见场景的使用示例。
在表单视图中使用
以下是在客户模型表单视图中为备注字段添加 Markdown 支持的示例:
<?xml version="1.0" encoding="utf-8"?>
<odoo><record id="view_partner_form_markdown" model="ir.ui.view"><field name="name">res.partner.form.markdown</field><field name="model">res.partner</field><field name="inherit_id" ref="base.view_partner_form"/><field name="arch" type="xml"><field name="comment" position="attributes"><attribute name="widget">bootstrap_markdown</attribute></field></field></record>
</odoo>
这个示例继承了标准的合作伙伴表单视图,并为 comment
字段添加了 Markdown 小部件。用户现在可以使用 Markdown 格式编辑备注,并在预览模式下查看渲染后的 HTML。
在自定义模型中使用
以下是在自定义模型中定义一个支持 Markdown 的字段的示例:
# models/document.py
from odoo import models, fields, apiclass Document(models.Model):_name = 'custom.document'_description = 'Custom Document'name = fields.Char(string='Title', required=True)content = fields.Text(string='Content') # 将使用 Markdown 小部件
<!-- views/document_views.xml -->
<?xml version="1.0" encoding="utf-8"?>
<odoo><record id="view_document_form" model="ir.ui.view"><field name="name">custom.document.form</field><field name="model">custom.document</field><field name="arch" type="xml"><form string="Document"><sheet><group><field name="name"/><field name="content" widget="bootstrap_markdown"/></group></sheet></form></field></record>
</odoo>
在这个示例中,我们创建了一个名为 custom.document
的自定义模型,并为其 content
字段添加了 Markdown 小部件。
在网站内容中使用
如果您想在网站内容中使用 Markdown,可以这样配置:
<?xml version="1.0" encoding="utf-8"?>
<odoo><record id="view_website_page_form_markdown" model="ir.ui.view"><field name="name">website.page.form.markdown</field><field name="model">website.page</field><field name="inherit_id" ref="website.website_page_form_view"/><field name="arch" type="xml"><field name="content" position="attributes"><attribute name="widget">bootstrap_markdown</attribute></field></field></record>
</odoo>
这个示例为网站页面的内容字段添加了 Markdown 支持,使网站编辑人员可以使用 Markdown 格式编写页面内容。
高级使用场景
与富文本编辑器结合使用
在某些情况下,您可能希望为用户提供选择使用 Markdown 或富文本编辑器的选项。以下是一个实现这种功能的示例:
<?xml version="1.0" encoding="utf-8"?>
<odoo><record id="view_document_form_with_editor_choice" model="ir.ui.view"><field name="name">custom.document.form.editor.choice</field><field name="model">custom.document</field><field name="arch" type="xml"><form string="Document"><sheet><group><field name="name"/><field name="editor_type" widget="radio"/><field name="content" widget="bootstrap_markdown" attrs="{'invisible': [('editor_type', '!=', 'markdown')]}"/><field name="content" widget="html" attrs="{'invisible': [('editor_type', '!=', 'html')]}"/></group></sheet></form></field></record>
</odoo>
# models/document.py
from odoo import models, fields, apiclass Document(models.Model):_name = 'custom.document'_description = 'Custom Document'name = fields.Char(string='Title', required=True)content = fields.Text(string='Content')editor_type = fields.Selection([('markdown', 'Markdown'),('html', 'Rich Text')], string='Editor Type', default='markdown')
在这个示例中,我们添加了一个 editor_type
字段,允许用户选择使用 Markdown 或富文本编辑器。根据用户的选择,显示相应的编辑器。
自定义 Markdown 预览样式
如果您想自定义 Markdown 预览的样式,可以通过继承 CSS 类来实现:
/* /static/src/css/custom_markdown.css */
.markdown-content h1 {color: #3498db;border-bottom: 1px solid #eee;padding-bottom: 10px;
}.markdown-content h2 {color: #2ecc71;margin-top: 20px;
}.markdown-content blockquote {background-color: #f9f9f9;border-left: 5px solid #3498db;padding: 10px 15px;
}.markdown-content code {background-color: #f8f8f8;color: #e74c3c;padding: 2px 4px;border-radius: 3px;
}
然后在模块的 __manifest__.py
中添加这个 CSS 文件:
"assets": {"web.assets_backend": [# 其他资产..."your_module/static/src/css/custom_markdown.css",],
},
这样,您就可以为 Markdown 预览区域应用自定义样式,使其更符合您的应用程序的整体设计。
最佳实践
1. 字段选择
Markdown 小部件最适合用于以下类型的字段:
- 长文本描述:产品描述、项目说明等
- 技术文档:帮助文档、指南等
- 结构化内容:需要标题、列表、表格等格式的内容
不建议用于:
- 简短的单行文本:名称、标题等
- 高度格式化的内容:需要复杂布局和样式的内容
- 需要嵌入复杂元素的内容:如需要嵌入表单、交互元素等
2. 用户培训
虽然 Markdown 语法相对简单,但对于不熟悉的用户可能需要一些指导。考虑提供以下资源:
- 简短的 Markdown 语法指南
- 常用格式的示例
- 预览功能的使用说明
3. 性能考虑
在处理大量 Markdown 内容时,请注意以下性能优化措施:
- 避免在列表视图中渲染 Markdown 内容
- 考虑在服务器端预渲染 Markdown 内容
- 对于只读场景,可以缓存渲染后的 HTML
4. 安全性考虑
Markdown 转换为 HTML 可能带来安全风险,特别是当内容来自不受信任的来源时。建议:
- 在服务器端验证和清理 Markdown 内容
- 使用安全的 Markdown 解析器
- 限制允许的 HTML 标签和属性
5. 与其他功能的集成
Markdown 小部件可以与其他 Odoo 功能集成,提供更丰富的体验:
- 文件上传:允许在 Markdown 中引用上传的图片
- 链接自动完成:自动完成对其他记录的链接
- 版本控制:跟踪 Markdown 内容的变更历史
6. 移动兼容性
确保 Markdown 编辑器在移动设备上也能正常工作:
- 测试不同屏幕尺寸下的编辑体验
- 优化移动设备上的工具栏布局
- 考虑为移动用户提供简化的编辑界面
7. 国际化支持
如果您的应用程序需要支持多种语言,请确保:
- 编辑器界面元素已翻译
- Markdown 内容可以包含不同语言的字符
- 预览正确渲染各种语言的内容
常见问题与解决方案
问题:Markdown 内容在保存后丢失格式
解决方案:确保字段类型为 Text
或 HTML
,而不是 Char
。Char
类型字段长度有限,可能会截断 Markdown 内容。
问题:预览中的样式与最终显示不一致
解决方案:确保 CSS 样式一致应用于预览和最终显示。可能需要调整 .markdown-content
类的样式,使其与最终显示环境匹配。
问题:Markdown 编辑器在某些浏览器中显示异常
解决方案:检查浏览器兼容性,可能需要添加特定的 CSS 修复或 polyfill。确保测试主流浏览器,包括 Chrome、Firefox、Safari 和 Edge。
问题:图片上传和引用困难
解决方案:考虑扩展 Markdown 编辑器,添加图片上传功能,或提供清晰的指导,说明如何在 Markdown 中引用已上传的图片。
问题:Markdown 内容在搜索结果中不可见
解决方案:考虑在搜索视图中添加纯文本版本的字段,或使用 Odoo 的全文搜索功能。
总结
Odoo 18 Markdown Widget 提供了一种简单而强大的方式,为表单中的文本字段添加 Markdown 编辑功能。通过遵循本章介绍的最佳实践和示例,您可以充分利用这个小部件,为用户提供更好的内容编辑体验。无论是用于内部文档、产品描述还是客户沟通,Markdown 都能帮助用户创建格式丰富、结构清晰的内容,同时保持编辑过程的简单直观。
技术架构与设计模式分析
整体架构设计
Odoo 18 Markdown Widget 模块采用了现代化的前端架构设计,充分利用了 Odoo 18 的 OWL 框架和组件化开发模式。本章将从架构设计、设计模式、代码组织和技术选型等方面深入分析该模块的技术实现。
分层架构
模块采用了清晰的分层架构,各层职责明确:
-
表现层:
- 用户界面组件,包括编辑器和预览区域
- CSS 样式定义,确保视觉一致性
-
业务逻辑层:
- Markdown 转换逻辑
- 状态管理和数据同步
-
数据层:
- 与 Odoo 模型的交互
- 数据持久化
这种分层设计使得代码结构清晰,各部分职责明确,便于维护和扩展。
模块化设计
模块采用了高度模块化的设计,主要体现在:
-
资源组织:
- JavaScript 文件:实现核心逻辑
- CSS 文件:定义样式
- 依赖库:提供扩展功能
-
功能分离:
- 编辑功能
- 预览功能
- 数据同步
这种模块化设计使得各部分可以独立开发和测试,提高了代码的可维护性和可扩展性。
设计模式分析
组件模式
模块使用 OWL 框架实现了组件模式,将 UI 和逻辑封装在一起:
export class MarkdownField extends Component {static template = xml`...`;static props = {...};setup() {// 组件初始化}// 组件方法
}
组件模式的优势:
- 封装性:UI 和逻辑封装在一起,形成独立单元
- 可复用性:组件可以在不同场景中复用
- 可测试性:组件可以独立测试
- 可维护性:修改一个组件不会影响其他组件
观察者模式
模块使用 useRecordObserver
实现了观察者模式,监听记录变化并更新 UI:
useRecordObserver((record) => {if (!this.isDirty) {const newValue = record.data[this.props.name] || "";if (this.lastValue !== newValue) {this.state.value = newValue;this.state.htmlContent = markup(this.convertToHtml(newValue));this.lastValue = newValue;}}
});
观察者模式的优势:
- 松耦合:观察者和被观察者之间松散耦合
- 事件驱动:基于事件的通信机制
- 实时更新:数据变化时自动更新 UI
状态管理模式
模块使用 OWL 的 useState
实现了响应式状态管理:
this.state = useState({isPreview: false,value: this.props.record.data[this.props.name] || "",htmlContent: markup(this.convertToHtml(this.props.record.data[this.props.name] || "")),
});
状态管理模式的优势:
- 集中管理:状态集中管理,便于跟踪
- 响应式更新:状态变化自动触发 UI 更新
- 可预测性:状态变化路径清晰可预测
策略模式
模块在 Markdown 转换实现中采用了策略模式的思想,通过 convertToHtml
方法实现转换策略:
convertToHtml(text) {if (!text) return "";// 转换策略实现const html = text.replace(/^# (.*$)/gm, '<h1>$1</h1>').replace(/^## (.*$)/gm, '<h2>$1</h2>')// 更多转换规则...return html;
}
策略模式的优势:
- 算法封装:将算法封装在独立方法中
- 可替换性:可以轻松替换为其他转换策略
- 扩展性:可以添加新的转换规则
工厂模式
模块在注册字段组件时使用了工厂模式的思想:
export const markdownField = {component: MarkdownField,displayName: _t("Markdown"),supportedTypes: ["text", "html"],extractProps({ attrs }) {return {placeholder: attrs.placeholder,};},
};registry.category("fields").add("bootstrap_markdown", markdownField);
工厂模式的优势:
- 对象创建抽象:抽象了组件的创建过程
- 配置灵活:可以通过配置创建不同的组件实例
- 统一接口:提供统一的接口创建对象
技术选型分析
前端框架选择
模块选择了 Odoo 18 的 OWL 框架作为前端开发框架,这是一个明智的选择:
- 与 Odoo 生态集成:OWL 是 Odoo 官方前端框架,与 Odoo 生态系统深度集成
- 轻量级:OWL 相比其他前端框架更轻量,性能更好
- 组件化:支持组件化开发,提高代码复用性
- 响应式:内置响应式状态管理
Markdown 转换实现
模块选择了自定义实现 Markdown 转换,而非直接使用第三方库:
-
优势:
- 减少依赖
- 更好的控制性
- 可能的性能优化
- 简化实现
-
劣势:
- 功能可能不如专业库完整
- 需要自行维护转换逻辑
- 可能存在边缘情况处理不当
CSS 样式实现
模块使用了原生 CSS 而非预处理器(如 SCSS 或 LESS):
-
优势:
- 简单直接
- 无需额外编译步骤
- 与 Odoo 默认样式系统一致
-
劣势:
- 缺少变量、嵌套等高级功能
- 可能导致代码重复
- 维护性相对较低
代码质量与最佳实践
代码组织
模块的代码组织遵循了 Odoo 的最佳实践:
- 目录结构:符合 Odoo 模块标准结构
- 文件命名:清晰明确,反映文件用途
- 代码分离:JS、CSS 分离,职责明确
命名规范
模块使用了一致的命名规范:
- 类名:使用 PascalCase(如
MarkdownField
) - 方法名:使用 camelCase(如
convertToHtml
) - CSS 类名:使用带前缀的 kebab-case(如
o_field_markdown
)
注释与文档
模块代码中包含了适当的注释:
/*** Markdown 文本字段小部件* 支持在表单视图中编辑和显示 Markdown 格式文本*/
export class MarkdownField extends Component {// ...
}/*** 将 Markdown 转换为 HTML* @param {string} text * @returns {string} HTML*/
convertToHtml(text) {// ...
}
这些注释有助于理解代码的用途和功能,提高了代码的可读性和可维护性。
错误处理
模块实现了基本的错误处理机制:
await this.props.record.update({ [this.props.name]: this.state.value }).catch(() => {this.isDirty = true;
});
在数据更新失败时,将 isDirty
标志重置为 true
,确保数据不会丢失。
性能优化
渲染优化
模块实现了一些渲染优化措施:
- 条件渲染:使用
t-if
和t-else
条件渲染组件 - 懒加载:只在需要时转换 Markdown 为 HTML
- 状态缓存:缓存上次值,避免不必要的更新
并发控制
模块使用 Mutex
控制并发操作:
async commitChanges({ urgent } = {}) {if (urgent) {this._commitChanges({ urgent });} else {return this.mutex.exec(() => this._commitChanges({ urgent }));}
}
这确保了多个异步操作按顺序执行,避免了竞态条件。
扩展性分析
扩展点
模块提供了多个扩展点:
- 样式扩展:可以通过 CSS 自定义样式
- 转换逻辑扩展:可以替换
convertToHtml
方法 - UI 扩展:可以修改组件模板
继承与扩展示例
以下是扩展该模块的示例:
// 扩展 MarkdownField 组件
import { MarkdownField } from "web_widget_markdown/static/src/js/web_widget_text_markdown";export class ExtendedMarkdownField extends MarkdownField {// 重写转换方法,使用第三方库convertToHtml(text) {if (!text) return "";return showdown.makeHtml(text);}// 添加新方法exportAsPdf() {// 实现导出为 PDF 的功能}
}// 注册扩展组件
registry.category("fields").add("extended_markdown", {component: ExtendedMarkdownField,displayName: _t("Extended Markdown"),supportedTypes: ["text", "html"],extractProps({ attrs }) {return {placeholder: attrs.placeholder,};},
});
安全性考虑
XSS 防护
模块在处理 Markdown 转 HTML 时需要注意 XSS 攻击风险:
- 当前实现:使用简单的正则替换,可能存在安全风险
- 改进建议:
- 使用安全的 Markdown 解析库
- 实现 HTML 清理和过滤
- 限制允许的 HTML 标签和属性
输入验证
模块目前没有实现输入验证,可能导致安全风险或数据问题:
- 当前实现:直接处理用户输入
- 改进建议:
- 添加输入验证
- 限制输入长度
- 过滤危险字符
未来改进方向
功能增强
- 工具栏:添加 Markdown 编辑工具栏,提高用户友好性
- 图片上传:集成图片上传功能
- 实时协作:支持多用户实时编辑
- 版本历史:跟踪内容变更历史
技术改进
- 使用专业 Markdown 库:替换自定义转换逻辑,使用成熟的 Markdown 库
- 性能优化:优化大文档的渲染性能
- 测试覆盖:添加单元测试和集成测试
- 国际化:完善多语言支持
架构优化
- 组件拆分:将大组件拆分为更小的子组件
- 状态管理优化:使用更结构化的状态管理
- 插件系统:实现插件系统,支持功能扩展
总结
Odoo 18 Markdown Widget 模块采用了现代化的前端架构和设计模式,实现了一个简洁而功能完备的 Markdown 编辑器。通过组件化设计、响应式状态管理和清晰的代码组织,该模块展示了 Odoo 18 前端开发的最佳实践。
虽然在某些方面还有改进空间,如使用专业 Markdown 库、增强安全性和添加更多功能,但总体而言,该模块提供了一个稳定、可靠的 Markdown 编辑解决方案,可以满足大多数业务场景的需求。
通过本章的分析,我们不仅了解了该模块的技术实现细节,还探讨了其架构设计思想和未来发展方向,为开发者提供了全面的技术参考。