editor module

Overview

The editor module provides the standalone WYSIWYG newsletter editor used by sendMail. It contains the Qt bridge, the editor window, the configuration dialogs, and the helper functions that support HTML editing, file selection, and editor startup.

Module API

WYSIWYG HTML editor for composing sendMail newsletters.

Usage:

python src/editor.py # blank editor python src/editor.py data/template.md # open existing markdown file python src/editor.py data/newsletter.html # open existing HTML file

The editor saves output as .html, ready for sendMail:

python src/sendMail.py –profile cambristi data/newsletter.html

editor.pyqtSignal(*args, **kwargs)

Fake pyqtSignal instance with connect/disconnect/emit.

editor.pyqtSlot(*args, **kwargs)

Fake @pyqtSlot that behaves as a no-op decorator.

class editor.EditorBridge(parent: _FakeQObject | None = None)[source]

Bases: _FakeQObject

QWebChannel bridge for communication with Quill.js editor.

Registered with QWebChannel as “bridge”. Provides slots callable from JavaScript for image insertion, file operations, and content changes.

Signals:

dirty_changed: Emitted when content modification state changes css_changed: Emitted when user selects custom CSS stylesheet

dirty_changed = <MagicMock id='123166021544464'>
css_changed = <MagicMock id='123166000604944'>
__init__(parent: _FakeQObject | None = None) None[source]
on_content_changed = '<function EditorBridge.on_content_changed>'
request_image_insert() str[source]

Opens a file dialog and returns a base64 data URI for the chosen image. Returns “” if the user cancels.

Opens a link dialog pre-filled with selected_text. Returns a JSON string {“url”: “…”, “text”: “…”} on confirm, or “” on cancel.

request_table_insert() str[source]

Opens a dialog asking for table dimensions. Returns JSON string {“rows”: n, “cols”: m} on confirm, or “” on cancel.

log_js_error = '<function EditorBridge.log_js_error>'
get_current_html() str[source]

Returns the last HTML content received from the editor.

reset(html: str = '') None[source]

Clears the dirty flag and updates cached HTML (called after save).

property is_dirty: bool
class editor.EditorWindow(file_path: str | None = None)[source]

Bases: _FakeQMainWindow

Main WYSIWYG newsletter editor window.

Desktop application for composing and editing HTML newsletters. Uses Quill.js for rich text editing in QWebEngineView. Supports inline images, attachments, CSS customization, and sendMail output.

Parameters:

file_path – Optional markdown or HTML file to open on startup

__init__(file_path: str | None = None) None[source]
open_file(path: str) None[source]

Open a .md or .html file into the editor.

new_document() None[source]

Clear the editor and start a new blank document.

closeEvent(event: Any) None[source]
editor.main() None[source]

Classes

class editor._LinkDialog(parent: <MagicMock name = 'mock.QWidget.__or__()' id='123165999223184'> = None, selected_text: str = '')[source]

Bases: _FakeQDialog

Small dialog asking for a URL and optional display text.

__init__(parent: <MagicMock name = 'mock.QWidget.__or__()' id='123165999223184'> = None, selected_text: str = '') None[source]
get_url() str[source]
get_text() str[source]
class editor._AnchorDialog(parent: <MagicMock name = 'mock.QWidget.__or__()' id='123165999223184'> = None)[source]

Bases: _FakeQDialog

Small dialog asking for a named anchor / bookmark identifier.

__init__(parent: <MagicMock name = 'mock.QWidget.__or__()' id='123165999223184'> = None) None[source]
get_name() str[source]
class editor._TableDialog(parent: <MagicMock name = 'mock.QWidget.__or__()' id='123165999223184'> = None)[source]

Bases: _FakeQDialog

Dialog asking for table dimensions (rows × columns).

__init__(parent: <MagicMock name = 'mock.QWidget.__or__()' id='123165999223184'> = None) None[source]
get_rows() int[source]
get_cols() int[source]
class editor._SendDialog(parent: <MagicMock name = 'mock.QWidget.__or__()' id='123165999223184'> = None, *, attachment_path: str, config_path: str, config_data: dict[str, dict[str, str | int]] | None=None, initial_profile: str = 'default')[source]

Bases: _FakeQDialog

Dialog for selecting sendMail options before sending the edited file.

__init__(parent: <MagicMock name = 'mock.QWidget.__or__()' id='123165999223184'> = None, *, attachment_path: str, config_path: str, config_data: dict[str, dict[str, str | int]] | None=None, initial_profile: str = 'default') None[source]
load_current_filter(profile: str) None[source]

Load filter from profile config and display in filter field.

load_database_records() tuple[list[list[str]], list[str]][source]

Load database records from CSV or Google Sheets (T026).

filter_and_display_records() None[source]

Load, filter, and display database records (T027, T028, T040, T041, T042).

build_args(config_data: dict[str, dict[str, str | int]]) SimpleNamespace[source]
class editor._LineFieldSpec(key: 'str', label: 'str', tooltip: 'str' = '', placeholder: 'str' = '', password: 'bool' = False, browse_caption: 'str | None' = None, browse_filter: 'str' = 'All Files (*)')[source]

Bases: object

key: str
label: str
tooltip: str = ''
placeholder: str = ''
password: bool = False
browse_caption: str | None = None
browse_filter: str = 'All Files (*)'
__init__(key: str, label: str, tooltip: str = '', placeholder: str = '', password: bool = False, browse_caption: str | None = None, browse_filter: str = 'All Files (*)') None
class editor._ConfigDialog(parent: <MagicMock name = 'mock.QWidget.__or__()' id='123165999223184'> = None, *, config_path: str, config_data: dict[str, dict[str, str | int | list[str] | dict[str, str]]] | None=None, initial_profile: str = 'default')[source]

Bases: _FakeQDialog

Dialog for editing sendMail YAML configuration by profile.

Provides tabbed interface for editing: - Identity (sender, credentials) - Delivery (SMTP/IMAP settings) - Sources (subscriber database location) - Templates (message templates, rate limits) - Filters (filter_test and filter rules with validation)

__init__(parent: <MagicMock name = 'mock.QWidget.__or__()' id='123165999223184'> = None, *, config_path: str, config_data: dict[str, dict[str, str | int | list[str] | dict[str, str]]] | None=None, initial_profile: str = 'default') None[source]
class editor.EditorBridge(parent: _FakeQObject | None = None)[source]

Bases: _FakeQObject

QWebChannel bridge for communication with Quill.js editor.

Registered with QWebChannel as “bridge”. Provides slots callable from JavaScript for image insertion, file operations, and content changes.

Signals:

dirty_changed: Emitted when content modification state changes css_changed: Emitted when user selects custom CSS stylesheet

dirty_changed = <MagicMock id='123166021544464'>
css_changed = <MagicMock id='123166000604944'>
__init__(parent: _FakeQObject | None = None) None[source]
on_content_changed = '<function EditorBridge.on_content_changed>'
request_image_insert() str[source]

Opens a file dialog and returns a base64 data URI for the chosen image. Returns “” if the user cancels.

request_link_insert(selected_text: str) str[source]

Opens a link dialog pre-filled with selected_text. Returns a JSON string {“url”: “…”, “text”: “…”} on confirm, or “” on cancel.

request_table_insert() str[source]

Opens a dialog asking for table dimensions. Returns JSON string {“rows”: n, “cols”: m} on confirm, or “” on cancel.

log_js_error = '<function EditorBridge.log_js_error>'
get_current_html() str[source]

Returns the last HTML content received from the editor.

reset(html: str = '') None[source]

Clears the dirty flag and updates cached HTML (called after save).

property is_dirty: bool
class editor.EditorWindow(file_path: str | None = None)[source]

Bases: _FakeQMainWindow

Main WYSIWYG newsletter editor window.

Desktop application for composing and editing HTML newsletters. Uses Quill.js for rich text editing in QWebEngineView. Supports inline images, attachments, CSS customization, and sendMail output.

Parameters:

file_path – Optional markdown or HTML file to open on startup

__init__(file_path: str | None = None) None[source]
open_file(path: str) None[source]

Open a .md or .html file into the editor.

new_document() None[source]

Clear the editor and start a new blank document.

closeEvent(event: Any) None[source]

Functions

editor._svg_icon(svg: str) <MagicMock name='mock.QIcon' id='123166033973584'>[source]

Create a QIcon from an SVG string; returns an empty icon if the SVG plugin is unavailable.

editor.main() None[source]