Last active
January 15, 2025 07:44
-
-
Save mshafiee/06ae7b8bdc506d292a48b9d0e92d6b5d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
TinyMCE Local Editor Server | |
This script is a simple HTTP server that serves a local HTML file with an embedded TinyMCE editor. | |
It allows you to edit the content of the HTML file in real-time using the TinyMCE rich text editor. | |
The server also saves any changes made in the editor back to the original HTML file. | |
Usage: | |
python tinymce_editor.py [-dir ltr|rtl] <html_file> | |
Arguments: | |
<html_file> - The path to the HTML file you want to edit. | |
Flags: | |
-dir - Specifies the text direction for the editor. Valid values are "ltr" (Left-to-Right) or "rtl" (Right-to-Left). | |
Default: "ltr". | |
How it works: | |
1. The script starts a local HTTP server on a free port. | |
2. It opens the specified HTML file and injects the TinyMCE editor into it. | |
3. The editor is configured to work in either LTR or RTL mode, depending on the `-dir` flag. | |
4. Any changes made in the editor are automatically saved back to the original HTML file via a POST request. | |
Requirements: | |
- Python 3.x | |
- TinyMCE library (place the TinyMCE files in a directory named 'js/tinymce' relative to the script) | |
How to Download and Save TinyMCE for This Script: | |
1. Download TinyMCE: | |
- Visit the TinyMCE download page: https://www.tiny.cloud/get-tiny/self-hosted/ | |
- Choose the version you want to download (e.g., the latest stable version). | |
- Download the .zip file for the self-hosted version. | |
2. Extract the Files: | |
- Extract the contents of the downloaded .zip file to a temporary folder. | |
3. Create the Required Directory: | |
- In the same directory where your Python script is located, create a folder named 'js'. | |
- Inside the 'js' folder, create another folder named 'tinymce'. | |
4. Copy TinyMCE Files: | |
- From the extracted TinyMCE folder, copy the contents of the 'tinymce' directory (e.g., tinymce.min.js, skins, themes, plugins, etc.) into the 'js/tinymce' folder you created in the previous step. | |
5. Verify the Structure: | |
- Ensure the directory structure looks like this: | |
your_project_directory/ | |
├── tinymce_editor.py | |
├── js/ | |
│ └── tinymce/ | |
│ ├── tinymce.min.js | |
│ ├── skins/ | |
│ ├── themes/ | |
│ └── plugins/ | |
└── your_html_file.html | |
6. Run the Script: | |
- Now, when you run the script, it will load TinyMCE from the 'js/tinymce' directory. | |
Examples: | |
1. Edit an HTML file with default LTR text direction: | |
python tinymce_editor.py myfile.html | |
2. Edit an HTML file with RTL text direction: | |
python tinymce_editor.py -dir rtl myfile.html | |
This will start the server and open the editor in your default web browser. You can then edit the content of the HTML file and save it directly from the editor. | |
""" | |
import os | |
import sys | |
import socket | |
import logging | |
import argparse | |
from http.server import BaseHTTPRequestHandler, HTTPServer | |
import webbrowser | |
from urllib.parse import urlparse, parse_qs | |
# Configure logging | |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
# TinyMCE local path | |
TINYMCE_PATH = "/js/tinymce" | |
class TinyMCEHandler(BaseHTTPRequestHandler): | |
def __init__(self, *args, **kwargs): | |
self.html_file = kwargs.pop('html_file') | |
self.text_direction = kwargs.pop('text_direction') | |
super().__init__(*args, **kwargs) | |
def do_GET(self): | |
if self.path == "/": | |
self.serve_editor() | |
elif self.path.startswith(TINYMCE_PATH): | |
self.serve_static_file("." + self.path) | |
else: | |
self.serve_static_file("." + self.path) | |
def do_POST(self): | |
if self.path.startswith("/save"): | |
self.save_content() | |
def serve_editor(self): | |
try: | |
with open(self.html_file, "r") as f: | |
content = f.read() | |
modified_content = self.inject_tinymce(content, self.text_direction) | |
self.send_response(200) | |
self.send_header("Content-type", "text/html") | |
self.end_headers() | |
self.wfile.write(modified_content.encode("utf-8")) | |
except Exception as e: | |
logging.error(f"Error serving editor: {e}") | |
self.send_error(500, f"Internal Server Error: {str(e)}") | |
def serve_static_file(self, file_path): | |
try: | |
if os.path.isfile(file_path): | |
with open(file_path, "rb") as f: | |
self.send_response(200) | |
self.send_header("Content-type", self.get_mime_type(file_path)) | |
self.end_headers() | |
self.wfile.write(f.read()) | |
else: | |
self.send_error(404, "File Not Found") | |
except Exception as e: | |
logging.error(f"Error serving static file: {e}") | |
self.send_error(500, f"Internal Server Error: {str(e)}") | |
def save_content(self): | |
try: | |
parsed_url = urlparse(self.path) | |
query_params = parse_qs(parsed_url.query) | |
filename = query_params.get('filename', [self.html_file])[0] | |
content_length = int(self.headers["Content-Length"]) | |
post_data = self.rfile.read(content_length).decode("utf-8") | |
with open(filename, "w") as f: | |
f.write(post_data) | |
self.send_response(200) | |
self.end_headers() | |
self.wfile.write(b"Saved") | |
except Exception as e: | |
logging.error(f"Error saving content: {e}") | |
self.send_error(500, f"Internal Server Error: {str(e)}") | |
def inject_tinymce(self, content, text_direction): | |
return f""" | |
<!DOCTYPE html> | |
<html lang="en" dir="{text_direction}"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>TinyMCE Editor</title> | |
<script src="{TINYMCE_PATH}/tinymce.min.js"></script> | |
<style> | |
html, body {{ | |
margin: 0; | |
padding: 0; | |
height: 100%; | |
overflow: hidden; | |
direction: {text_direction}; /* Set page direction */ | |
}} | |
#editor {{ | |
height: 100vh; | |
width: 100%; | |
}} | |
</style> | |
<script> | |
document.addEventListener('DOMContentLoaded', function () {{ | |
let autoSaveEnabled = false; // Default: auto-save is inactive | |
tinymce.init({{ | |
selector: '#editor', | |
height: '100%', | |
width: '100%', | |
directionality: '{text_direction}', /* Set TinyMCE editor direction */ | |
plugins: [ | |
'advlist autolink lists link image charmap print preview anchor', | |
'searchreplace visualblocks code fullscreen', | |
'insertdatetime media table paste code help wordcount', | |
'save autosave' // Add save and autosave plugins | |
], | |
toolbar: ` | |
undo redo | formatselect | bold italic underline strikethrough | | |
alignleft aligncenter alignright alignjustify | outdent indent | | |
bullist numlist | link image media table | forecolor backcolor | | |
code | fullscreen | save | autosave | |
`, | |
menu: {{ | |
file: {{ title: 'File', items: 'save restoredraft | preview | print | export' }}, | |
edit: {{ title: 'Edit', items: 'undo redo | cut copy paste | selectall | searchreplace' }}, | |
view: {{ title: 'View', items: 'code | visualaid visualchars visualblocks | fullscreen' }}, | |
insert: {{ title: 'Insert', items: 'image link media template codesample inserttable | charmap emoticons hr | pagebreak nonbreaking anchor toc | insertdatetime' }}, | |
format: {{ title: 'Format', items: 'bold italic underline strikethrough superscript subscript codeformat | formats blockformats fontformats fontsizes align | forecolor backcolor | removeformat' }}, | |
tools: {{ title: 'Tools', items: 'code wordcount spellchecker | restoredraft' }}, | |
table: {{ title: 'Table', items: 'inserttable tableprops deletetable | cell row column' }}, | |
}}, | |
setup: function (editor) {{ | |
// Add a custom save button to the toolbar | |
editor.ui.registry.addButton('save', {{ | |
text: 'Save', | |
icon: 'save', | |
onAction: function () {{ | |
saveContent(editor); | |
}} | |
}}); | |
// Add a custom auto-save toggle button to the toolbar | |
editor.ui.registry.addToggleButton('autosave', {{ | |
text: 'Auto-Save', | |
icon: 'autosave', | |
onAction: function (api) {{ | |
autoSaveEnabled = !autoSaveEnabled; // Toggle auto-save state | |
api.setActive(autoSaveEnabled); // Update button state | |
console.log('Auto-save is now', autoSaveEnabled ? 'enabled' : 'disabled'); | |
}}, | |
onSetup: function (api) {{ | |
api.setActive(autoSaveEnabled); // Set initial button state | |
}} | |
}}); | |
// Add a custom save command | |
editor.addCommand('saveContent', function () {{ | |
saveContent(editor); | |
}}); | |
// Add a custom save menu item to the File menu | |
editor.ui.registry.addMenuItem('save', {{ | |
text: 'Save', | |
icon: 'save', | |
onAction: function () {{ | |
saveContent(editor); | |
}} | |
}}); | |
// Add keyboard shortcut for save (Ctrl+S or Command+S) | |
editor.addShortcut('Meta+S', 'Save', function () {{ | |
saveContent(editor); | |
}}); | |
editor.addShortcut('Ctrl+S', 'Save', function () {{ | |
saveContent(editor); | |
}}); | |
// Handle auto-save on change | |
editor.on('change', function () {{ | |
if (autoSaveEnabled) {{ | |
saveContent(editor); | |
}} | |
}}); | |
}} | |
}}); | |
// Function to save content | |
function saveContent(editor) {{ | |
fetch('/save?filename={self.html_file}', {{ | |
method: 'POST', | |
body: editor.getContent(), | |
headers: {{ 'Content-Type': 'text/plain' }} | |
}}) | |
.then(response => response.text()) | |
.then(message => {{ | |
console.log(message); // Log the save confirmation | |
if (autoSaveEnabled) {{ | |
console.log('Auto-save successful!'); | |
}} else {{ | |
alert('File saved successfully!'); | |
}} | |
}}) | |
.catch(error => {{ | |
console.error('Error saving file:', error); | |
alert('Error saving file!'); | |
}}); | |
}} | |
}}); | |
</script> | |
</head> | |
<body> | |
<textarea id="editor">{content}</textarea> | |
</body> | |
</html> | |
""" | |
def get_mime_type(self, file_path): | |
if file_path.endswith(".js"): | |
return "application/javascript" | |
elif file_path.endswith(".css"): | |
return "text/css" | |
elif file_path.endswith(".png"): | |
return "image/png" | |
elif file_path.endswith(".jpg") or file_path.endswith(".jpeg"): | |
return "image/jpeg" | |
elif file_path.endswith(".gif"): | |
return "image/gif" | |
elif file_path.endswith(".html"): | |
return "text/html" | |
elif file_path.endswith(".txt"): | |
return "text/plain" | |
else: | |
return "application/octet-stream" | |
def find_free_port(): | |
"""Find a free port to use for the server.""" | |
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: | |
s.bind(('', 0)) # Bind to a free port provided by the OS | |
return s.getsockname()[1] # Return the port number | |
def run_server(html_file, text_direction): | |
port = find_free_port() # Use a random free port | |
server_address = ("", port) | |
handler = lambda *args, **kwargs: TinyMCEHandler(*args, html_file=html_file, text_direction=text_direction, **kwargs) | |
httpd = HTTPServer(server_address, handler) | |
logging.info(f"Serving at http://localhost:{port}") | |
webbrowser.open(f"http://localhost:{port}") | |
httpd.serve_forever() | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser(description="TinyMCE Local Editor Server") | |
parser.add_argument("html_file", help="The path to the HTML file you want to edit.") | |
parser.add_argument("-dir", "--direction", choices=["ltr", "rtl"], default="ltr", help="Text direction: 'ltr' (Left-to-Right) or 'rtl' (Right-to-Left). Default: 'ltr'.") | |
args = parser.parse_args() | |
if not os.path.exists(args.html_file): | |
logging.error(f"Error: File '{args.html_file}' not found.") | |
sys.exit(1) | |
run_server(args.html_file, args.direction) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment