Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a36de297f7 | ||
|
|
8cf4fd4ef7 | ||
|
|
f330f3dd7c | ||
|
|
155665721d | ||
|
|
f03a046a93 | ||
|
|
62a2029fe9 | ||
|
|
9fa5d1b3d9 | ||
|
|
4b8d36c51c | ||
|
|
106d1835fa | ||
|
|
a1f8cfbeb7 | ||
|
|
3db929b1bd | ||
|
|
7626bc6e99 | ||
|
|
c5869332ae | ||
|
|
01f56bfe06 | ||
|
|
9b913eecb8 | ||
|
|
e06bf54607 | ||
|
|
c9786121b1 | ||
|
|
0bbc93a39f | ||
|
|
5dcd58934d | ||
|
|
ff3b58c2e1 | ||
|
|
e53365fd2d |
@@ -1,2 +1,19 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
# Set default behavior to automatically normalize line endings
|
||||
* text=auto
|
||||
|
||||
# Explicitly declare text files you want to always be normalized and converted
|
||||
*.md text
|
||||
*.py text
|
||||
*.js text
|
||||
*.css text
|
||||
*.html text
|
||||
|
||||
# Declare files that will always have CRLF line endings on checkout
|
||||
*.bat text eol=crlf
|
||||
|
||||
# Denote all files that are truly binary and should not be modified
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.zip binary
|
||||
@@ -83,5 +83,13 @@ npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Ruff cache
|
||||
.ruff_cache/
|
||||
|
||||
# AF specific
|
||||
Status.txt
|
||||
Status.txt
|
||||
*.psd
|
||||
*.docx
|
||||
helpers/
|
||||
nodes.zip
|
||||
node.zip
|
||||
@@ -1,179 +1,171 @@
|
||||
# ****** ComfyUI-AF - Enhanced HTML-NOTE ******
|
||||
#
|
||||
# Creator: Alex Furer - Co-Creator(s): Claude AI & DeepSeek
|
||||
#
|
||||
# Praise, comment, bugs, improvements: https://github.com/alFrame/ComfyUI_AF_FindNodeByID
|
||||
#
|
||||
# LICENSE: MIT License
|
||||
#
|
||||
# v0.0.06
|
||||
#
|
||||
# Description:
|
||||
# A ComfyUI Note node with html capabilities. Create notes, UI's and ReadMe's directly inside the workflow, using html.
|
||||
#
|
||||
# Usage:
|
||||
# Read Me on Github
|
||||
#
|
||||
# Changelog:
|
||||
# "v0.0.06 - Removed all Java Script functionality for links and updated README",
|
||||
# "v0.0.05 - Made edit mode less intrusive and keep ComfyUI canvas navigation (panning, zooming [space bar] and [H] now work to pan around the canvas)",
|
||||
# "v0.0.04 - Fixed node selection and resizing by removing OUTPUT_NODE and using proper widget approach. Double clicking is now used to edit the html Mouse interaction is now persistent to ComfyUI behavior. Except middle mouse button for panning.",
|
||||
# "v0.0.03 - Fixed node resizing issues on save/reload. Improved selection and edit mode behavior to match ComfyUI core notes. Fixed middle mouse button panning over the node. Improved HTML indentation in default content",
|
||||
# "v0.0.02 - Fixed naming consistency to HTML_Note Implemented single-window toggle behavior",
|
||||
# "v0.0.01 - Initial Version"
|
||||
#
|
||||
|
||||
class AF_Enhanced_HTML_Note:
|
||||
"""
|
||||
AF Enhanced HTML-Note node with colored sections and rich formatting
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"html_content": ("STRING", {
|
||||
"multiline": True,
|
||||
"default": """<h2>Positive Prompt Section</h2>
|
||||
<div class="af-section af-positive">
|
||||
<h2>🟢 Positive Prompts</h2>
|
||||
<p><strong>masterpiece, best quality, highly detailed</strong></p>
|
||||
<ul>
|
||||
<li>Professional lighting</li>
|
||||
<li>Sharp focus</li>
|
||||
<li>8K resolution</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<h2>Negative Prompt Section</h2>
|
||||
<div class="af-section af-negative">
|
||||
<h2>🔴 Negative Prompts</h2>
|
||||
<p><em>low quality, blurry, distorted</em></p>
|
||||
<p><code>nsfw, watermark, signature, bad anatomy</code></p>
|
||||
</div>
|
||||
|
||||
|
||||
<h2>Neutral Prompt Section</h2>
|
||||
<div class="af-section af-neutral">
|
||||
<h3>📝 Notes</h3>
|
||||
<p>General notes and information...</p>
|
||||
<ul>
|
||||
<li>Item 1</li>
|
||||
<li>Item 2</li>
|
||||
<li>Item 3</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Info Section</h2>
|
||||
<div class="af-section af-info">
|
||||
<h3>ℹ️ Information</h3>
|
||||
<p>Using: <strong>SDXL 1.0</strong></p>
|
||||
<p>Steps: <code>25</code> | CFG: <code>7.5</code> | Sampler: <code>DPM++ 2M Karras</code></p>
|
||||
</div>
|
||||
|
||||
<h2>Warning Section</h2>
|
||||
<div class="af-section af-warning">
|
||||
<h3>⚠️ Warning</h3>
|
||||
<p>This workflow requires:</p>
|
||||
<ol>
|
||||
<li>High VRAM (8GB+)</li>
|
||||
<li>Latest ComfyUI version</li>
|
||||
<li>SDXL compatible models</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<h2>Column Layouts</h2>
|
||||
<div class="row">
|
||||
<div class="af-column-half-left">
|
||||
<div class="af-section af-positive">
|
||||
<h3>🎨 Style Settings</h3>
|
||||
<ul>
|
||||
<li>Style: Photorealistic</li>
|
||||
<li>Aspect: 16:9</li>
|
||||
<li>Quality: Ultra High</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="af-spacer"></div>
|
||||
|
||||
<div class="af-section af-info">
|
||||
<h3>⚙️ Technical</h3>
|
||||
<p>Resolution: <code>1920x1080</code></p>
|
||||
<p>Batch: <code>4</code></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="af-column-half-right">
|
||||
<div class="af-section af-negative">
|
||||
<h3>🚫 Exclusions</h3>
|
||||
<ul>
|
||||
<li>No text in image</li>
|
||||
<li>Avoid oversaturation</li>
|
||||
<li>No watermarks</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="af-spacer"></div>
|
||||
|
||||
<div class="af-section af-warning">
|
||||
<h3>💡 Tips</h3>
|
||||
<p>Use lower CFG for more creative results</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Clickable Links & Actions</h2>
|
||||
<div class="af-section af-info">
|
||||
<h3>🔗 Interactive Links</h3>
|
||||
|
||||
<!-- External links (opens in new tab with Ctrl+click) -->
|
||||
<h4>External link (opens in new tab with Ctrl+click)</h4>
|
||||
<p><a href="https://www.fullframestudios.ch/">Full Frame Studios - Alex Furer</a></p>
|
||||
|
||||
<h4>Anchor Links</h4>
|
||||
<p><a href="https://www.fullframestudios.ch/#ThreeDee">Full Frame Studios - Alex Furer - 3D</a></p>
|
||||
</div>
|
||||
|
||||
<h2>Custom Info Cards</h2>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<div style="flex: 1; min-width: 150px; background: rgba(144, 238, 144, 0.1); border: 1px solid #90EE90; padding: 12px; border-radius: 6px; text-align: center;">
|
||||
<strong style="color: #90EE90; display: block; margin-bottom: 4px;">Steps</strong>
|
||||
<p style="margin: 0; font-size: 24px; color: #fff; font-weight: bold;">25</p>
|
||||
</div>
|
||||
|
||||
<div style="flex: 1; min-width: 150px; background: rgba(135, 206, 235, 0.1); border: 1px solid #87CEEB; padding: 12px; border-radius: 6px; text-align: center;">
|
||||
<strong style="color: #87CEEB; display: block; margin-bottom: 4px;">CFG Scale</strong>
|
||||
<p style="margin: 0; font-size: 24px; color: #fff; font-weight: bold;">7.5</p>
|
||||
</div>
|
||||
|
||||
<div style="flex: 1; min-width: 150px; background: rgba(186, 85, 211, 0.1); border: 1px solid #BA55D3; padding: 12px; border-radius: 6px; text-align: center;">
|
||||
<strong style="color: #BA55D3; display: block; margin-bottom: 4px;">Batch Size</strong>
|
||||
<p style="margin: 0; font-size: 24px; color: #fff; font-weight: bold;">4</p>
|
||||
</div>
|
||||
</div>""",
|
||||
"placeholder": "Enter your HTML content here..."
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "display_html"
|
||||
CATEGORY = "AF - Nodes"
|
||||
OUTPUT_NODE = False
|
||||
|
||||
def display_html(self, html_content):
|
||||
"""
|
||||
Display enhanced HTML content
|
||||
"""
|
||||
return {"ui": {"text": html_content}, "result": (html_content,)}
|
||||
|
||||
# Node mappings for ComfyUI
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"AF_Enhanced_HTML_Note": AF_Enhanced_HTML_Note,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"AF_Enhanced_HTML_Note": "AF - Enhanced HTML Note",
|
||||
}
|
||||
|
||||
# ****** ComfyUI-AF - Enhanced HTML-NOTE ******
|
||||
#
|
||||
# Creator: Alex Furer - Co-Creator(s): Claude AI & DeepSeek
|
||||
#
|
||||
# Description: A ComfyUI Note node with html capabilities. Create notes, UI's and ReadMe's directly inside the workflow, using html.
|
||||
#
|
||||
# Repo and ReadMe URL: https://github.com/alFrame/ComfyUI-AF-Enhanced-HTML-Note
|
||||
#
|
||||
# Issues praise, comment, bugs, improvements: https://github.com/alFrame/ComfyUI-AF-Enhanced-HTML-Note/issues
|
||||
#
|
||||
# LICENSE: MIT License
|
||||
#
|
||||
# Usage: https://github.com/alFrame/ComfyUI-AF-Enhanced-HTML-Note
|
||||
#
|
||||
# Feature Requests / Wet Dreams
|
||||
# -
|
||||
|
||||
class AF_Enhanced_HTML_Note:
|
||||
"""
|
||||
AF Enhanced HTML-Note node with colored sections and rich formatting
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"html_content": ("STRING", {
|
||||
"multiline": True,
|
||||
"default": """<h2>Positive Prompt Section</h2>
|
||||
<div class="af-section af-positive">
|
||||
<h2>🟢 Positive Prompts</h2>
|
||||
<p><strong>masterpiece, best quality, highly detailed</strong></p>
|
||||
<ul>
|
||||
<li>Professional lighting</li>
|
||||
<li>Sharp focus</li>
|
||||
<li>8K resolution</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<h2>Negative Prompt Section</h2>
|
||||
<div class="af-section af-negative">
|
||||
<h2>🔴 Negative Prompts</h2>
|
||||
<p><em>low quality, blurry, distorted</em></p>
|
||||
<p><code>nsfw, watermark, signature, bad anatomy</code></p>
|
||||
</div>
|
||||
|
||||
|
||||
<h2>Neutral Prompt Section</h2>
|
||||
<div class="af-section af-neutral">
|
||||
<h3>📝 Notes</h3>
|
||||
<p>General notes and information...</p>
|
||||
<ul>
|
||||
<li>Item 1</li>
|
||||
<li>Item 2</li>
|
||||
<li>Item 3</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Info Section</h2>
|
||||
<div class="af-section af-info">
|
||||
<h3>ℹ️ Information</h3>
|
||||
<p>Using: <strong>SDXL 1.0</strong></p>
|
||||
<p>Steps: <code>25</code> | CFG: <code>7.5</code> | Sampler: <code>DPM++ 2M Karras</code></p>
|
||||
</div>
|
||||
|
||||
<h2>Warning Section</h2>
|
||||
<div class="af-section af-warning">
|
||||
<h3>⚠️ Warning</h3>
|
||||
<p>This workflow requires:</p>
|
||||
<ol>
|
||||
<li>High VRAM (8GB+)</li>
|
||||
<li>Latest ComfyUI version</li>
|
||||
<li>SDXL compatible models</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<h2>Column Layouts</h2>
|
||||
<div class="row">
|
||||
<div class="af-column-half-left">
|
||||
<div class="af-section af-positive">
|
||||
<h3>🎨 Style Settings</h3>
|
||||
<ul>
|
||||
<li>Style: Photorealistic</li>
|
||||
<li>Aspect: 16:9</li>
|
||||
<li>Quality: Ultra High</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="af-spacer"></div>
|
||||
|
||||
<div class="af-section af-info">
|
||||
<h3>⚙️ Technical</h3>
|
||||
<p>Resolution: <code>1920x1080</code></p>
|
||||
<p>Batch: <code>4</code></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="af-column-half-right">
|
||||
<div class="af-section af-negative">
|
||||
<h3>🚫 Exclusions</h3>
|
||||
<ul>
|
||||
<li>No text in image</li>
|
||||
<li>Avoid oversaturation</li>
|
||||
<li>No watermarks</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="af-spacer"></div>
|
||||
|
||||
<div class="af-section af-warning">
|
||||
<h3>💡 Tips</h3>
|
||||
<p>Use lower CFG for more creative results</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Clickable Links & Actions</h2>
|
||||
<div class="af-section af-info">
|
||||
<h3>🔗 Interactive Links</h3>
|
||||
|
||||
<!-- External links (opens in new tab with Ctrl+click) -->
|
||||
<h4>External link (opens in new tab with Ctrl+click)</h4>
|
||||
<p><a href="https://www.fullframestudios.ch/">Full Frame Studios - Alex Furer</a></p>
|
||||
|
||||
<h4>Anchor Links</h4>
|
||||
<p><a href="https://www.fullframestudios.ch/#ThreeDee">Full Frame Studios - Alex Furer - 3D</a></p>
|
||||
</div>
|
||||
|
||||
<h2>Custom Info Cards</h2>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<div style="flex: 1; min-width: 150px; background: rgba(144, 238, 144, 0.1); border: 1px solid #90EE90; padding: 12px; border-radius: 6px; text-align: center;">
|
||||
<strong style="color: #90EE90; display: block; margin-bottom: 4px;">Steps</strong>
|
||||
<p style="margin: 0; font-size: 24px; color: #fff; font-weight: bold;">25</p>
|
||||
</div>
|
||||
|
||||
<div style="flex: 1; min-width: 150px; background: rgba(135, 206, 235, 0.1); border: 1px solid #87CEEB; padding: 12px; border-radius: 6px; text-align: center;">
|
||||
<strong style="color: #87CEEB; display: block; margin-bottom: 4px;">CFG Scale</strong>
|
||||
<p style="margin: 0; font-size: 24px; color: #fff; font-weight: bold;">7.5</p>
|
||||
</div>
|
||||
|
||||
<div style="flex: 1; min-width: 150px; background: rgba(186, 85, 211, 0.1); border: 1px solid #BA55D3; padding: 12px; border-radius: 6px; text-align: center;">
|
||||
<strong style="color: #BA55D3; display: block; margin-bottom: 4px;">Batch Size</strong>
|
||||
<p style="margin: 0; font-size: 24px; color: #fff; font-weight: bold;">4</p>
|
||||
</div>
|
||||
</div>""",
|
||||
"placeholder": "Enter your HTML content here..."
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "display_html"
|
||||
CATEGORY = "AF - Nodes/AF - HTML Pack"
|
||||
OUTPUT_NODE = False
|
||||
|
||||
def display_html(self, html_content):
|
||||
"""
|
||||
Display enhanced HTML content
|
||||
"""
|
||||
return {"ui": {"text": html_content}, "result": (html_content,)}
|
||||
|
||||
# Node mappings for ComfyUI
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"AF_Enhanced_HTML_Note": AF_Enhanced_HTML_Note,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"AF_Enhanced_HTML_Note": "AF - Enhanced HTML Note",
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
## Changelog
|
||||
|
||||
### v0.0.12
|
||||
- Updated the README. Explained the limitation of the middle mouse ComfyUI panning being blocked by the node
|
||||
- Changed the ComfyUI category for cleanliness and standardization
|
||||
|
||||
### v0.0.11
|
||||
- Changed Icon location to my own website as the ComfyUI Registry botched out on any GitHub URL I tried...
|
||||
|
||||
### v0.0.10
|
||||
- Updated the README (CSS file editing)
|
||||
|
||||
### v0.0.09
|
||||
- Added nodes.py and node.js for ComfyUI registry and Manager compliance
|
||||
- Made sure the repo name change from "ComfyUI-AF-Enhanced_HTML_Note" to "ComfyUI-AF-Enhanced-HTML-Note" is consistent for all links and such
|
||||
|
||||
### v0.0.08
|
||||
- Moved the CSS from the JS file to it's own file.
|
||||
- Added CHANGELOG.md
|
||||
- Added package.json for ComfyUI Manager compliance
|
||||
- Added pyproject.toml for registry compliance
|
||||
- Reworked the README
|
||||
- Added the "helpers" folder to the .gitignore
|
||||
- Added an icon to the pyproject.toml
|
||||
|
||||
### v0.0.07
|
||||
- Comsetics, making tis a version
|
||||
|
||||
### v0.0.06
|
||||
- Removed all Java Script functionality for links and updated README
|
||||
|
||||
### v0.0.05
|
||||
- Made edit mode less intrusive and keep ComfyUI canvas navigation (panning, zooming [space bar] and [H] now work to pan around the canvas)
|
||||
|
||||
### v0.0.04
|
||||
- Fixed node selection and resizing by removing OUTPUT_NODE and using proper widget approach. Double clicking is now used to edit the html Mouse interaction is now persistent to ComfyUI behavior. Except middle mouse button for panning.
|
||||
|
||||
### v0.0.03
|
||||
- Fixed node resizing issues on save/reload. Improved selection and edit mode behavior to match ComfyUI core notes. Fixed middle mouse button panning over the node. Improved HTML indentation in default content
|
||||
|
||||
### v0.0.02
|
||||
- Fixed naming consistency to HTML_Note Implemented single-window toggle behavior
|
||||
|
||||
### v0.0.01
|
||||
- Initial Version
|
||||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
@@ -2,10 +2,6 @@
|
||||
|
||||
A ComfyUI Note node with HTML capabilities. Create notes, UI's and ReadMe's directly inside the workflow using rich HTML formatting.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
## ✨ Key Features
|
||||
@@ -15,8 +11,32 @@ A ComfyUI Note node with HTML capabilities. Create notes, UI's and ReadMe's dire
|
||||
- **Easy Editing** - Double-click anywhere on the content to edit HTML
|
||||
- **Smart Links** - Ctrl+click to open external links (JavaScript disabled for security)
|
||||
- **Beautiful Styling** - Predefined color schemes that match ComfyUI's theme
|
||||
- **Custom HTML Support** - Write your own HTML with inline styles
|
||||
- **No Dependencies** - All CSS is inlined for reliable performance
|
||||
- **Custom HTML Support** - Write your own HTML with inline styles or customize the external CSS file
|
||||
|
||||
## ⚠️ Known Limitation: Middle Mouse Button Panning
|
||||
|
||||
**Issue:** Middle mouse button panning does not work when the cursor is over this node.
|
||||
|
||||
**Workaround:** Use the hand tool by pressing `Space` on your keyboard, or pan from outside the node area.
|
||||
|
||||
**Technical Context:** Since this node renders HTML content, the browser intercepts middle mouse button events before they can reach the ComfyUI canvas. Despite multiple attempts to capture and forward these events to the underlying canvas, browser security and event handling policies prevent a reliable solution.
|
||||
|
||||
If you have ideas for resolving this limitation, please open an [issue](https://github.com/alFrame/ComfyUI-AF-Enhanced-HTML-Note/issues) on the repository.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Installation
|
||||
|
||||
### Via ComfyUI Manager (Recommended)
|
||||
1. Open ComfyUI Manager
|
||||
2. Search for "AF - Enhanced HTML Note"
|
||||
3. Install
|
||||
|
||||
### Manual Installation
|
||||
1. Clone this repository into your `ComfyUI/custom_nodes/` directory:
|
||||
```bash
|
||||
git clone https://github.com/alFrame/ComfyUI-AF-Enhanced-HTML-Note.git
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -39,6 +59,48 @@ A ComfyUI Note node with HTML capabilities. Create notes, UI's and ReadMe's dire
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Custom CSS Styling
|
||||
|
||||
The node uses an external CSS file (`AF-Enhanced-HTML-Note.css`) that you can customize to match your preferences or create your own styling themes.
|
||||
|
||||
### Editing the CSS
|
||||
|
||||
The CSS file is located at:
|
||||
```
|
||||
ComfyUI/custom_nodes/ComfyUI-AF-Enhanced-HTML-Note/web/AF-Enhanced-HTML-Note.css
|
||||
```
|
||||
|
||||
You can edit this file directly to:
|
||||
- Change colors of predefined sections (`.af-positive`, `.af-negative`, etc.)
|
||||
- Modify spacing and padding
|
||||
- Adjust font sizes and typography
|
||||
- Create new custom section classes
|
||||
- Change scrollbar styling
|
||||
- Customize the edit button appearance
|
||||
- OR **completely** replace the entire CSS and use your own classes. (Don't forget to change the classes in the HTML node in your ComfyUI workflow !!)
|
||||
|
||||
### Example: Creating a Custom Section Color
|
||||
|
||||
Add this to the CSS file to create a new teal section style:
|
||||
```css
|
||||
.af-html-note-content .af-section.af-teal {
|
||||
background: rgba(64, 224, 208, 0.1);
|
||||
border-left-color: #40E0D0;
|
||||
}
|
||||
```
|
||||
|
||||
Then use it in your HTML:
|
||||
```html
|
||||
<div class="af-section af-teal">
|
||||
<h3>🟢 Teal Section</h3>
|
||||
<p>Your custom styled content...</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Note:** After editing the CSS file, refresh your browser (Ctrl+F5 or Cmd+Shift+R) to see the changes.
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Predefined Section Colors
|
||||
|
||||
The node comes with several predefined colored section styles. Click each section below to see the code and rendered example.
|
||||
@@ -60,7 +122,7 @@ The node comes with several predefined colored section styles. Click each sectio
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -77,7 +139,7 @@ The node comes with several predefined colored section styles. Click each sectio
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -98,7 +160,7 @@ The node comes with several predefined colored section styles. Click each sectio
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -115,7 +177,7 @@ The node comes with several predefined colored section styles. Click each sectio
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -136,7 +198,7 @@ The node comes with several predefined colored section styles. Click each sectio
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -152,7 +214,7 @@ The node comes with several predefined colored section styles. Click each sectio
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -168,7 +230,7 @@ The node comes with several predefined colored section styles. Click each sectio
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -184,7 +246,7 @@ The node comes with several predefined colored section styles. Click each sectio
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -233,7 +295,7 @@ Use these spacer classes to control vertical spacing between elements.
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
### Size Reference:
|
||||
- `af-spacer-top` → 2px
|
||||
@@ -297,7 +359,7 @@ Create two-column layouts using the predefined column classes.
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
**Note:** The `row` class ensures proper column wrapping, and each column is 49% width with 1% margin.
|
||||
|
||||
@@ -331,11 +393,13 @@ All links require **Ctrl+Click** to activate for safety and to prevent accidenta
|
||||
</div>
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||
|
||||
**Remember:** Hold **Ctrl** while clicking any link to activate it!
|
||||
|
||||
### Rendered Result:
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
### Supported Link Types:
|
||||
- ✅ **External URLs** (`https://`, `http://`) - Open in new tab
|
||||
- ✅ **Anchor links** (`#section`) - Page navigation
|
||||
@@ -347,7 +411,14 @@ All links require **Ctrl+Click** to activate for safety and to prevent accidenta
|
||||
- ❌ **JavaScript links** (`javascript:`) - Blocked for security
|
||||
- ❌ **onclick handlers** - Ignored for security
|
||||
|
||||
</details>
|
||||
### Security Restrictions:
|
||||
- **No JavaScript execution** - All JavaScript is blocked for security
|
||||
- **No `<script>` tags** - Script tags are not executed
|
||||
- **No external scripts** - Cannot load external JavaScript files
|
||||
- **Ctrl+Click required** - All link interactions require Control key
|
||||
- **Safe HTML only** - Only styling and navigation features are supported
|
||||
- **No DOM manipulation** - JavaScript cannot modify the HTML content
|
||||
- **No browser storage** - `localStorage` and `sessionStorage` are blocked
|
||||
|
||||
---
|
||||
|
||||
@@ -367,52 +438,6 @@ The node supports **standard HTML** with **inline CSS styling**. You can write c
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ JavaScript Limitations
|
||||
|
||||
JavaScript functionality is limited for security. It **only works on `<a>` (link) elements** and **only when Ctrl+Click is used**.
|
||||
|
||||
### ✅ What Works (with Ctrl+Click on links):
|
||||
```html
|
||||
<!-- ✅ Alert dialogs -->
|
||||
<a href="#" onclick="alert('Hello!')">Alert on click</a>
|
||||
|
||||
<!-- ✅ Console logging -->
|
||||
<a href="#" onclick="console.log('Debug info')">Log to console</a>
|
||||
|
||||
<!-- ✅ Confirmation dialogs -->
|
||||
<a href="javascript:confirm('Are you sure?')">Confirmation dialog</a>
|
||||
|
||||
<!-- ✅ Multiple statements -->
|
||||
<a href="#" onclick="alert('First'); console.log('Second')">Multiple actions</a>
|
||||
```
|
||||
|
||||
### ❌ What Doesn't Work:
|
||||
```html
|
||||
<!-- ❌ JavaScript links are blocked -->
|
||||
<a href="javascript:alert('Hello')">JavaScript link</a>
|
||||
|
||||
<!-- ❌ onclick handlers are ignored -->
|
||||
<a href="#" onclick="alert('This wont work')">Alert link</a>
|
||||
|
||||
<!-- ❌ Script tags are ignored -->
|
||||
<script>console.log('Not executed')</script>
|
||||
|
||||
<!-- ❌ Event handlers on other elements -->
|
||||
<button onclick="alert('No')">Won't work</button>
|
||||
<div onclick="alert('No')">Won't work</div>
|
||||
```
|
||||
|
||||
### Security Restrictions:
|
||||
- **No JavaScript execution** - All JavaScript is blocked for security
|
||||
- **No `<script>` tags** - Script tags are not executed
|
||||
- **No external scripts** - Cannot load external JavaScript files
|
||||
- **Ctrl+Click required** - All link interactions require Control key
|
||||
- **Safe HTML only** - Only styling and navigation features are supported
|
||||
- **No DOM manipulation** - JavaScript cannot modify the HTML content
|
||||
- **No browser storage** - `localStorage` and `sessionStorage` are blocked
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Custom Styling Examples
|
||||
|
||||
<details>
|
||||
@@ -439,7 +464,7 @@ JavaScript functionality is limited for security. It **only works on `<a>` (link
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -468,7 +493,7 @@ JavaScript functionality is limited for security. It **only works on `<a>` (link
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -501,7 +526,7 @@ JavaScript functionality is limited for security. It **only works on `<a>` (link
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -530,7 +555,7 @@ JavaScript functionality is limited for security. It **only works on `<a>` (link
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -559,7 +584,7 @@ JavaScript functionality is limited for security. It **only works on `<a>` (link
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -587,7 +612,7 @@ JavaScript functionality is limited for security. It **only works on `<a>` (link
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
@@ -660,24 +685,12 @@ JavaScript functionality is limited for security. It **only works on `<a>` (link
|
||||
```
|
||||
|
||||
### Rendered Result:
|
||||

|
||||

|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Installation
|
||||
|
||||
1. Create folder: `ComfyUI/custom_nodes/ComfyUI-AF_Enhanced_HTML_Note/`
|
||||
2. Add the Python file: `AF_Enhanced_HTML_Note.py`
|
||||
3. Add the init file: `__init__.py`
|
||||
4. Create `web/` subfolder
|
||||
5. Add the JavaScript file: `web/AF_Enhanced_HTML_Note.js`
|
||||
6. Restart ComfyUI
|
||||
7. Find the node under **AF - Nodes > AF - Enhanced HTML Note**
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
<details>
|
||||
@@ -754,45 +767,26 @@ JavaScript functionality is limited for security. It **only works on `<a>` (link
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support & Credits
|
||||
## ⚠️ Disclaimer
|
||||
|
||||
- **Creator:** Alex Furer
|
||||
- **Co-Creators:** Claude AI & DeepSeek
|
||||
- **GitHub:** [ComfyUI_AF_FindNodeByID](https://github.com/alFrame/ComfyUI_AF_FindNodeByID)
|
||||
- **License:** MIT License
|
||||
- **Version:** 0.0.5
|
||||
This ComfyUI custom node is developed through AI-assisted coding. While carefully tested, it is provided **"as is" without warranty**.
|
||||
|
||||
**By using this node:**
|
||||
- You install and run at your own risk
|
||||
- The creator is not liable for damages or data loss
|
||||
- Compatibility with your setup is not guaranteed
|
||||
- Test in a safe environment before production use
|
||||
|
||||
Report issues on GitHub - we appreciate your feedback!
|
||||
|
||||
---
|
||||
|
||||
## 📸 Creating Example Screenshots
|
||||
## 📚 Additional Resources
|
||||
|
||||
To create the example images referenced in this README:
|
||||
- **[Changelog](CHANGELOG.md)** - Version history and updates
|
||||
- [GitHub Issues](https://github.com/alFrame/ComfyUI-AF-Enhanced-HTML-Note/issues) - Report bugs & request features
|
||||
- [License](LICENSE) - MIT License details
|
||||
|
||||
1. Open ComfyUI and add the AF Enhanced HTML Note node
|
||||
2. Copy each example code from the collapsible sections above
|
||||
3. Paste into the node and exit edit mode
|
||||
4. Take a screenshot of the rendered result
|
||||
5. Save as PNG in the `examples/` folder with the corresponding filename
|
||||
6. Commit and push to your repository
|
||||
---
|
||||
|
||||
**Required Screenshots:**
|
||||
- `positive-section.png`
|
||||
- `negative-section.png`
|
||||
- `neutral-section.png`
|
||||
- `info-section.png`
|
||||
- `warning-section.png`
|
||||
- `yellow-section.png`
|
||||
- `blue-section.png`
|
||||
- `custom-section.png`
|
||||
- `spacers.png`
|
||||
- `two-column-layout.png`
|
||||
- `links.png`
|
||||
- `custom-spacers.png`
|
||||
- `custom-colors.png`
|
||||
- `custom-text.png`
|
||||
- `custom-borders.png`
|
||||
- `custom-layouts.png`
|
||||
- `custom-cards.png`
|
||||
- `complete-workflow.png`
|
||||
|
||||
Once you add the screenshots, the README will automatically display them!
|
||||
**Made with ❤️ by Alex Furer & Qwen3, Claude AI, DeepSeek**
|
||||
|
||||
@@ -1,41 +1,23 @@
|
||||
"""
|
||||
@author: Alex Furer
|
||||
@title: AF - Enhanced HTML Note
|
||||
@nickname: AF - Enhanced HTML Note
|
||||
@description: A ComfyUI custom node that allows you to create a note node with HTML support.
|
||||
"""
|
||||
|
||||
import os
|
||||
from .AF_Enhanced_HTML_Note import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
|
||||
|
||||
# Tell ComfyUI about our web files
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
__version__ = "0.0.06"
|
||||
__author__ = "Alex Furer"
|
||||
__title__ = "AF - Enhanced HTML Note"
|
||||
__description__ = " ComfyUI custom node that allows you to create a note node with HTML support."
|
||||
__license__ = "MIT"
|
||||
__changelog__ = [
|
||||
"v0.0.06 - Removed all Java Script functionality for links and updated README",
|
||||
"v0.0.05 - Made edit mode less intrusive and keep ComfyUI canvas navigation (panning, zooming [space bar] and [H] now work to pan around the canvas)",
|
||||
"v0.0.04 - Fixed node selection and resizing by removing OUTPUT_NODE and using proper widget approach. Double clicking is now used to edit the html Mouse interaction is now persistent to ComfyUI behavior. Except middle mouse button for panning.",
|
||||
"v0.0.03 - Fixed node resizing issues on save/reload. Improved selection and edit mode behavior to match ComfyUI core notes. Fixed middle mouse button panning over the node. Improved HTML indentation in default content",
|
||||
"v0.0.02 - Fixed naming consistency to HTML_Note Implemented single-window toggle behavior",
|
||||
"v0.0.01 - Initial Version"
|
||||
]
|
||||
|
||||
print("* * * * * * * * * * * * * * * * * * * * * * * * * *")
|
||||
print(r"""
|
||||
___ ____ _____ ___ __ ______ _ __ __
|
||||
/ _ | / __/ ____ / ___/__ __ _ / _/_ __/ / / / _/ / |/ /__ ___/ /__ ___
|
||||
/ __ |/ _/ /___/ / /__/ _ \/ ' \/ _/ // / /_/ // / / / _ Y _ / -_|_-<
|
||||
/_/ |_/_/ \___/\___/_/_/_/_/ \_, /\____/___/ /_/|_/\___|_,_/\__/___/
|
||||
/___/
|
||||
|
||||
📋 AF - Enhanced HTML Note Loaded !
|
||||
|
||||
""")
|
||||
print("* * * * * * * * * * * * * * * * * * * * * * * * * *")
|
||||
"""
|
||||
@author: Alex Furer
|
||||
@title: AF - Enhanced HTML Note
|
||||
@nickname: AF - Enhanced HTML Note
|
||||
@description: A ComfyUI custom node that allows you to create a note node with HTML support.
|
||||
"""
|
||||
|
||||
import os
|
||||
from .node import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
# Tell ComfyUI about our web files
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
|
||||
|
||||
|
||||
print("* * * * * * * * * * * * * * * * *")
|
||||
print(r"""
|
||||
AF - ComfyUI Nodes
|
||||
|
||||
📋 AF - Enhanced HTML Note Loaded !
|
||||
""")
|
||||
print("* * * * * * * * * * * * * * * * *")
|
||||
@@ -184,24 +184,16 @@
|
||||
|
||||
<h2>Clickable Links & Actions</h2>
|
||||
<div style="border: 1px solid #808080; padding: 8px; border-radius: 6px; margin: 8px 0;">
|
||||
<div class="af-section af-info">
|
||||
<h3>🔗 Interactive Links</h3>
|
||||
|
||||
<!-- External link (opens in new tab with Ctrl+click) -->
|
||||
<p><a href="https://github.com/comfyanonymous/ComfyUI">ComfyUI GitHub Repository</a></p>
|
||||
|
||||
<!-- Alert action (triggers with Ctrl+click) -->
|
||||
<p><a href="#" onclick="alert('Custom action triggered!')">Click for alert</a></p>
|
||||
|
||||
<!-- Console log action -->
|
||||
<p><a href="#" onclick="console.log('Debug message logged')">Log to console</a></p>
|
||||
|
||||
<!-- Multiple actions -->
|
||||
<p><a href="https://example.com" onclick="console.log('Link clicked')">Link with logging</a></p>
|
||||
|
||||
<!-- JavaScript links -->
|
||||
<p><a href="javascript:alert('Hello from JavaScript!')">JavaScript alert link</a></p>
|
||||
</div>
|
||||
<div class="af-section af-info">
|
||||
<h3>🔗 Interactive Links</h3>
|
||||
|
||||
<!-- External links (opens in new tab with Ctrl+click) -->
|
||||
<h4>External link (opens in new tab with Ctrl+click)</h4>
|
||||
<p><a href="https://www.fullframestudios.ch/">Full Frame Studios - Alex Furer</a></p>
|
||||
|
||||
<h4>Anchor Links</h4>
|
||||
<p><a href="https://www.fullframestudios.ch/#ThreeDee">Full Frame Studios - Alex Furer - 3D</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Custom Styling Examples</h2>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# node.py - Main entry point for ComfyUI registry
|
||||
|
||||
from .AF_Enhanced_HTML_Note import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "comfyui-af-enhanced-html-note",
|
||||
"version": "0.0.12",
|
||||
"description": "A ComfyUI Note node with HTML capabilities. Create notes, UI's and ReadMe's directly inside the workflow using rich HTML formatting.",
|
||||
"author": "Alex Furer",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/alFrame/ComfyUI-AF-Enhanced-HTML-Note"
|
||||
},
|
||||
"nodepath": "custom_nodes/ComfyUI-AF-Enhanced-HTML-Note"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
[project]
|
||||
name = "comfyui-af-enhanced-html-note"
|
||||
description = "A ComfyUI Note node with HTML capabilities. Create notes, UI's and ReadMe's directly inside the workflow using rich HTML formatting."
|
||||
version = "0.0.12"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = []
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/alFrame/ComfyUI-AF-Enhanced-HTML-Note"
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "alframe"
|
||||
DisplayName = "ComfyUI AF - Enhanced HTML Note"
|
||||
Icon = "https://fullframestudios.ch/ComfyUI-Icon/icon.png"
|
||||
@@ -0,0 +1,276 @@
|
||||
.af-html-note-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100px;
|
||||
background: var(--comfy-menu-bg, #2a2a2a);
|
||||
/* border: 1px solid var(--border-color, #555); */
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
cursor: default;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--input-text, #ffffff);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.af-html-note-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
/* Prevent text selection */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
cursor: default;
|
||||
/* Prevent keyboard focus */
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Ensure content area can't be focused */
|
||||
.af-html-note-content:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.af-html-note-editor {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--comfy-menu-bg, #2a2a2a);
|
||||
border: 1px solid var(--border-color, #555);
|
||||
padding: 8px;
|
||||
font-family: 'Monaco', 'Consolas', 'Courier New', monospace;
|
||||
line-height: 1.4;
|
||||
color: var(--input-text, #ffffff);
|
||||
box-sizing: border-box;
|
||||
resize: none;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
display: none;
|
||||
flex: 1;
|
||||
/* Add text selection capabilities */
|
||||
user-select: auto;
|
||||
-webkit-user-select: auto;
|
||||
-moz-user-select: auto;
|
||||
-ms-user-select: auto;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
/* Floating edit button */
|
||||
.af-floating-edit-btn {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
border: 1px solid var(--border-color, #555);
|
||||
color: var(--input-text, #ffffff);
|
||||
border-radius: 3px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
cursor: pointer;
|
||||
z-index: 1000;
|
||||
font-size: 11px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.af-floating-edit-btn:hover {
|
||||
opacity: 1;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
border-color: #777;
|
||||
}
|
||||
|
||||
/* Enable link interactions only when Ctrl is held */
|
||||
.af-html-note-content a {
|
||||
color: #58a6ff;
|
||||
text-decoration: none;
|
||||
/* Links are never directly clickable - only via Ctrl */
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
body.af-ctrl-active .af-html-note-content a {
|
||||
/* Only become clickable when Ctrl is held globally */
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
body.af-ctrl-active .af-html-note-content a:hover {
|
||||
color: #79c0ff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.af-html-note-container.edit-mode .af-html-note-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.af-html-note-container.edit-mode .af-html-note-editor {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Hide edit button in edit mode */
|
||||
.af-html-note-container.edit-mode .af-floating-edit-btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Rest of your CSS remains the same... */
|
||||
.af-html-note-content h1, .af-html-note-content h2, .af-html-note-content h3,
|
||||
.af-html-note-content h4, .af-html-note-content h5, .af-html-note-content h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.af-html-note-content h1 { font-size: 1.5em; }
|
||||
.af-html-note-content h2 { font-size: 1.3em; }
|
||||
.af-html-note-content h3 { font-size: 1.1em; }
|
||||
|
||||
.af-html-note-content .af-section {
|
||||
padding: 8px 16px;
|
||||
margin: 8px 0;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-section.af-positive {
|
||||
background: rgba(144, 238, 144, 0.1);
|
||||
border-left-color: #90EE90;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-section.af-negative {
|
||||
background: rgba(255, 107, 107, 0.1);
|
||||
border-left-color: #FF6B6B;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-section.af-neutral {
|
||||
background: rgba(224, 224, 224, 0.1);
|
||||
border-left-color: #E0E0E0;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-section.af-info {
|
||||
background: rgba(135, 206, 235, 0.1);
|
||||
border-left-color: #87CEEB;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-section.af-warning {
|
||||
background: rgba(255, 128, 0, 0.1);
|
||||
border-left-color: #FFD700;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-section.af-yellow {
|
||||
background: rgba(255, 128, 0, 0.1);
|
||||
border-left-color: #FFD700;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-section.af-blue {
|
||||
background: rgba(102, 105, 255, 0.1);
|
||||
border-left-color: #000099;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-section.af-custom {
|
||||
background: rgba(186, 85, 211, 0.1);
|
||||
border-left-color: #BA55D3;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-spacer-top { height: 2px; margin: 4px 0; }
|
||||
.af-html-note-content .af-spacer-mini { height: 4px; margin: 4px 0; }
|
||||
.af-html-note-content .af-spacer-small { height: 8px; margin: 4px 0; }
|
||||
.af-html-note-content .af-spacer { height: 16px; margin: 8px 0; }
|
||||
.af-html-note-content .af-spacer-large { height: 32px; margin: 16px 0; }
|
||||
.af-html-note-content .af-spacer-xl { height: 48px; margin: 24px 0; }
|
||||
|
||||
.af-html-note-content code {
|
||||
background: rgba(0,0,0,0.3);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Monaco', 'Consolas', 'Courier New', monospace;
|
||||
font-size: 0.9em;
|
||||
color: #f0f6fc;
|
||||
}
|
||||
|
||||
.af-html-note-content pre {
|
||||
background: rgba(0,0,0,0.4);
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin: 16px 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.af-html-note-content pre code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.af-html-note-content ul, .af-html-note-content ol {
|
||||
padding-left: 20px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.af-html-note-content li { margin: 4px 0; }
|
||||
.af-html-note-content p { margin: 8px 0; }
|
||||
|
||||
.af-html-note-content table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.af-html-note-content th, .af-html-note-content td {
|
||||
border: 1px solid var(--border-color, #555);
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.af-html-note-content th {
|
||||
background: rgba(255,255,255,0.1);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.af-html-note-content hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border-color, #555);
|
||||
margin: 16px 0;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.af-html-note-container::-webkit-scrollbar { width: 8px; }
|
||||
.af-html-note-container::-webkit-scrollbar-track { background: rgba(0,0,0,0.1); }
|
||||
.af-html-note-container::-webkit-scrollbar-thumb {
|
||||
background: var(--border-color, #555);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.af-html-note-container::-webkit-scrollbar-thumb:hover { background: #777; }
|
||||
|
||||
.af-html-note-editor::-webkit-scrollbar { width: 8px; }
|
||||
.af-html-note-editor::-webkit-scrollbar-track { background: rgba(0,0,0,0.1); }
|
||||
.af-html-note-editor::-webkit-scrollbar-thumb {
|
||||
background: var(--border-color, #555);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.af-html-note-editor::-webkit-scrollbar-thumb:hover { background: #777; }
|
||||
|
||||
/* ****** MY OWN STYLES ****** */
|
||||
.af-column-half-left {
|
||||
box-sizing: border-box;
|
||||
float: left;
|
||||
width: 49%;
|
||||
margin-right: 1% !important;
|
||||
}
|
||||
.af-column-half-right {
|
||||
box-sizing: border-box;
|
||||
float: left;
|
||||
width: 49%;
|
||||
margin-left: 1% !important;
|
||||
}
|
||||
.row:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
// File: web/AF-Enhanced-HTML-Note.js
|
||||
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
// Inject styles from external CSS file
|
||||
function injectStyles() {
|
||||
const styleId = 'af-html-note-styles';
|
||||
if (!document.getElementById(styleId)) {
|
||||
const link = document.createElement('link');
|
||||
link.id = styleId;
|
||||
link.rel = 'stylesheet';
|
||||
|
||||
// Use the same base path as our JS file
|
||||
const jsPath = import.meta.url;
|
||||
const basePath = jsPath.substring(0, jsPath.lastIndexOf('/'));
|
||||
link.href = basePath + '/AF-Enhanced-HTML-Note.css';
|
||||
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "AF.enhanced.html.note",
|
||||
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "AF_Enhanced_HTML_Note") {
|
||||
injectStyles();
|
||||
|
||||
// Store the original onNodeCreated
|
||||
const originalOnNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
const result = originalOnNodeCreated?.apply(this, arguments);
|
||||
|
||||
this.isEditMode = false;
|
||||
this.size = [400, 300];
|
||||
|
||||
// Don't hide the original widget - instead, replace it with our custom display
|
||||
setTimeout(() => {
|
||||
const htmlWidget = this.widgets?.find(w => w.name === "html_content");
|
||||
if (htmlWidget && htmlWidget.element) {
|
||||
// Store the original value
|
||||
this.htmlContent = htmlWidget.value;
|
||||
|
||||
// Create our custom container
|
||||
this.createHTMLNoteDisplay(this.htmlContent);
|
||||
|
||||
// Hide the original widget but keep its value
|
||||
htmlWidget.computeSize = () => [0, 0];
|
||||
htmlWidget.element.style.display = 'none';
|
||||
}
|
||||
}, 0);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
nodeType.prototype.createHTMLNoteDisplay = function(content) {
|
||||
// Create container element
|
||||
this.containerElement = document.createElement("div");
|
||||
this.containerElement.className = "af-html-note-container";
|
||||
|
||||
// Create HTML content display
|
||||
this.contentElement = document.createElement("div");
|
||||
this.contentElement.className = "af-html-note-content";
|
||||
this.contentElement.innerHTML = content;
|
||||
|
||||
// Create editor element
|
||||
this.editorElement = document.createElement("textarea");
|
||||
this.editorElement.className = "af-html-note-editor";
|
||||
this.editorElement.value = content;
|
||||
|
||||
// Create floating edit button
|
||||
this.editButton = document.createElement("div");
|
||||
this.editButton.className = "af-floating-edit-btn";
|
||||
this.editButton.innerHTML = "✏️";
|
||||
this.editButton.title = "Edit HTML";
|
||||
|
||||
// Append all elements to container
|
||||
this.containerElement.appendChild(this.contentElement);
|
||||
this.containerElement.appendChild(this.editorElement);
|
||||
this.containerElement.appendChild(this.editButton);
|
||||
|
||||
// Edit button click handler
|
||||
this.editButton.addEventListener('click', (e) => {
|
||||
if (!this.isEditMode) {
|
||||
this.enterEditMode();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
// Add back double-click editing (user-friendly alternative)
|
||||
this.contentElement.addEventListener('dblclick', (e) => {
|
||||
if (!this.isEditMode) {
|
||||
this.enterEditMode();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle links
|
||||
this.handleLinksInHTML();
|
||||
|
||||
// Handle editor events
|
||||
this.editorElement.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
this.exitEditMode();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
// Comment
|
||||
this.contentElement.setAttribute('tabindex', '-1');
|
||||
|
||||
this.editorElement.addEventListener('blur', () => {
|
||||
setTimeout(() => {
|
||||
if (this.isEditMode && document.activeElement !== this.editorElement) {
|
||||
this.exitEditMode();
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
|
||||
this.contentElement.addEventListener('mousedown', (e) => {
|
||||
// Prevent the content area from getting focus on click
|
||||
e.preventDefault();
|
||||
|
||||
// But don't stop propagation - let ComfyUI handle node selection
|
||||
// Only prevent the default focus behavior
|
||||
});
|
||||
|
||||
// Add as DOM widget
|
||||
this.addDOMWidget("html_note_display", "div", this.containerElement);
|
||||
};
|
||||
|
||||
nodeType.prototype.enterEditMode = function() {
|
||||
if (this.isEditMode) return;
|
||||
|
||||
this.isEditMode = true;
|
||||
this.containerElement.classList.add('edit-mode');
|
||||
this.editorElement.value = this.htmlContent;
|
||||
|
||||
setTimeout(() => {
|
||||
this.editorElement.focus();
|
||||
this.editorElement.select();
|
||||
}, 10);
|
||||
};
|
||||
|
||||
nodeType.prototype.exitEditMode = function() {
|
||||
if (!this.isEditMode) return;
|
||||
|
||||
this.isEditMode = false;
|
||||
this.containerElement.classList.remove('edit-mode');
|
||||
this.htmlContent = this.editorElement.value;
|
||||
this.contentElement.innerHTML = this.htmlContent;
|
||||
|
||||
// Update the original widget value
|
||||
const htmlWidget = this.widgets?.find(w => w.name === "html_content");
|
||||
if (htmlWidget) {
|
||||
htmlWidget.value = this.htmlContent;
|
||||
if (htmlWidget.callback) {
|
||||
htmlWidget.callback(this.htmlContent);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-attach link handlers
|
||||
this.handleLinksInHTML();
|
||||
};
|
||||
|
||||
// Updated link handler - REMOVED JavaScript execution
|
||||
nodeType.prototype.handleLinksInHTML = function() {
|
||||
if (!this.contentElement) return;
|
||||
|
||||
const links = this.contentElement.querySelectorAll('a');
|
||||
links.forEach(link => {
|
||||
// Remove any existing onclick handlers to prevent JavaScript execution
|
||||
link.onclick = null;
|
||||
|
||||
link.addEventListener('click', (e) => {
|
||||
// Only allow clicks when Ctrl is held
|
||||
if (!document.body.classList.contains('af-ctrl-active')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
const href = link.getAttribute('href');
|
||||
|
||||
// Handle different types of links
|
||||
if (href) {
|
||||
// External URLs and anchor links - open in new tab
|
||||
if (href.startsWith('http://') ||
|
||||
href.startsWith('https://') ||
|
||||
href.startsWith('mailto:') ||
|
||||
href.startsWith('tel:')) {
|
||||
e.preventDefault();
|
||||
window.open(href, '_blank');
|
||||
}
|
||||
// Internal anchor links (page navigation) - let browser handle
|
||||
else if (href.startsWith('#') || href === '#') {
|
||||
// Allow default behavior for anchor links
|
||||
// They will navigate within the same page
|
||||
}
|
||||
// JavaScript links - BLOCK for security
|
||||
else if (href.startsWith('javascript:')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.warn('AF HTML Note: JavaScript links are disabled for security');
|
||||
return false;
|
||||
}
|
||||
// Relative URLs and other links - open in new tab
|
||||
else {
|
||||
e.preventDefault();
|
||||
window.open(href, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Global Ctrl key handling
|
||||
let ctrlKeyActive = false;
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Control') {
|
||||
ctrlKeyActive = true;
|
||||
document.body.classList.add('af-ctrl-active');
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keyup', (e) => {
|
||||
if (e.key === 'Control') {
|
||||
ctrlKeyActive = false;
|
||||
document.body.classList.remove('af-ctrl-active');
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up when node is removed
|
||||
const originalOnRemoved = nodeType.prototype.onRemoved;
|
||||
nodeType.prototype.onRemoved = function() {
|
||||
document.body.classList.remove('af-ctrl-active');
|
||||
originalOnRemoved?.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,521 +0,0 @@
|
||||
// File: web/AF_Enhanced_HTML_Note.js
|
||||
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
// Define inline CSS styles
|
||||
const AF_HTML_NOTE_STYLES = `
|
||||
.af-html-note-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100px;
|
||||
background: var(--comfy-menu-bg, #2a2a2a);
|
||||
/* border: 1px solid var(--border-color, #555); */
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
cursor: default;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--input-text, #ffffff);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.af-html-note-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
/* Prevent text selection */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
cursor: default;
|
||||
/* Prevent keyboard focus */
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Ensure content area can't be focused */
|
||||
.af-html-note-content:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.af-html-note-editor {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--comfy-menu-bg, #2a2a2a);
|
||||
border: 1px solid var(--border-color, #555);
|
||||
padding: 8px;
|
||||
font-family: 'Monaco', 'Consolas', 'Courier New', monospace;
|
||||
line-height: 1.4;
|
||||
color: var(--input-text, #ffffff);
|
||||
box-sizing: border-box;
|
||||
resize: none;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
display: none;
|
||||
flex: 1;
|
||||
/* Add text selection capabilities */
|
||||
user-select: auto;
|
||||
-webkit-user-select: auto;
|
||||
-moz-user-select: auto;
|
||||
-ms-user-select: auto;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
/* Floating edit button */
|
||||
.af-floating-edit-btn {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
border: 1px solid var(--border-color, #555);
|
||||
color: var(--input-text, #ffffff);
|
||||
border-radius: 3px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
cursor: pointer;
|
||||
z-index: 1000;
|
||||
font-size: 11px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.af-floating-edit-btn:hover {
|
||||
opacity: 1;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
border-color: #777;
|
||||
}
|
||||
|
||||
/* Enable link interactions only when Ctrl is held */
|
||||
.af-html-note-content a {
|
||||
color: #58a6ff;
|
||||
text-decoration: none;
|
||||
/* Links are never directly clickable - only via Ctrl */
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
body.af-ctrl-active .af-html-note-content a {
|
||||
/* Only become clickable when Ctrl is held globally */
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
body.af-ctrl-active .af-html-note-content a:hover {
|
||||
color: #79c0ff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.af-html-note-container.edit-mode .af-html-note-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.af-html-note-container.edit-mode .af-html-note-editor {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Hide edit button in edit mode */
|
||||
.af-html-note-container.edit-mode .af-floating-edit-btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Rest of your CSS remains the same... */
|
||||
.af-html-note-content h1, .af-html-note-content h2, .af-html-note-content h3,
|
||||
.af-html-note-content h4, .af-html-note-content h5, .af-html-note-content h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.af-html-note-content h1 { font-size: 1.5em; }
|
||||
.af-html-note-content h2 { font-size: 1.3em; }
|
||||
.af-html-note-content h3 { font-size: 1.1em; }
|
||||
|
||||
.af-html-note-content .af-section {
|
||||
padding: 8px 16px;
|
||||
margin: 8px 0;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-section.af-positive {
|
||||
background: rgba(144, 238, 144, 0.1);
|
||||
border-left-color: #90EE90;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-section.af-negative {
|
||||
background: rgba(255, 107, 107, 0.1);
|
||||
border-left-color: #FF6B6B;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-section.af-neutral {
|
||||
background: rgba(224, 224, 224, 0.1);
|
||||
border-left-color: #E0E0E0;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-section.af-info {
|
||||
background: rgba(135, 206, 235, 0.1);
|
||||
border-left-color: #87CEEB;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-section.af-warning {
|
||||
background: rgba(255, 128, 0, 0.1);
|
||||
border-left-color: #FFD700;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-section.af-yellow {
|
||||
background: rgba(255, 128, 0, 0.1);
|
||||
border-left-color: #FFD700;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-section.af-blue {
|
||||
background: rgba(102, 105, 255, 0.1);
|
||||
border-left-color: #000099;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-section.af-custom {
|
||||
background: rgba(186, 85, 211, 0.1);
|
||||
border-left-color: #BA55D3;
|
||||
}
|
||||
|
||||
.af-html-note-content .af-spacer-top { height: 2px; margin: 4px 0; }
|
||||
.af-html-note-content .af-spacer-mini { height: 4px; margin: 4px 0; }
|
||||
.af-html-note-content .af-spacer-small { height: 8px; margin: 4px 0; }
|
||||
.af-html-note-content .af-spacer { height: 16px; margin: 8px 0; }
|
||||
.af-html-note-content .af-spacer-large { height: 32px; margin: 16px 0; }
|
||||
.af-html-note-content .af-spacer-xl { height: 48px; margin: 24px 0; }
|
||||
|
||||
.af-html-note-content code {
|
||||
background: rgba(0,0,0,0.3);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Monaco', 'Consolas', 'Courier New', monospace;
|
||||
font-size: 0.9em;
|
||||
color: #f0f6fc;
|
||||
}
|
||||
|
||||
.af-html-note-content pre {
|
||||
background: rgba(0,0,0,0.4);
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin: 16px 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.af-html-note-content pre code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.af-html-note-content ul, .af-html-note-content ol {
|
||||
padding-left: 20px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.af-html-note-content li { margin: 4px 0; }
|
||||
.af-html-note-content p { margin: 8px 0; }
|
||||
|
||||
.af-html-note-content table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.af-html-note-content th, .af-html-note-content td {
|
||||
border: 1px solid var(--border-color, #555);
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.af-html-note-content th {
|
||||
background: rgba(255,255,255,0.1);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.af-html-note-content hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border-color, #555);
|
||||
margin: 16px 0;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.af-html-note-container::-webkit-scrollbar { width: 8px; }
|
||||
.af-html-note-container::-webkit-scrollbar-track { background: rgba(0,0,0,0.1); }
|
||||
.af-html-note-container::-webkit-scrollbar-thumb {
|
||||
background: var(--border-color, #555);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.af-html-note-container::-webkit-scrollbar-thumb:hover { background: #777; }
|
||||
|
||||
.af-html-note-editor::-webkit-scrollbar { width: 8px; }
|
||||
.af-html-note-editor::-webkit-scrollbar-track { background: rgba(0,0,0,0.1); }
|
||||
.af-html-note-editor::-webkit-scrollbar-thumb {
|
||||
background: var(--border-color, #555);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.af-html-note-editor::-webkit-scrollbar-thumb:hover { background: #777; }
|
||||
|
||||
/* ****** MY OWN STYLES ****** */
|
||||
.af-column-half-left {
|
||||
box-sizing: border-box;
|
||||
float: left;
|
||||
width: 49%;
|
||||
margin-right: 1% !important;
|
||||
}
|
||||
.af-column-half-right {
|
||||
box-sizing: border-box;
|
||||
float: left;
|
||||
width: 49%;
|
||||
margin-left: 1% !important;
|
||||
}
|
||||
.row:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
`;
|
||||
|
||||
// Inject styles
|
||||
function injectStyles() {
|
||||
const styleId = 'af-html-note-styles';
|
||||
if (!document.getElementById(styleId)) {
|
||||
const style = document.createElement('style');
|
||||
style.id = styleId;
|
||||
style.textContent = AF_HTML_NOTE_STYLES;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "AF.enhanced.html.note",
|
||||
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "AF_Enhanced_HTML_Note") {
|
||||
injectStyles();
|
||||
|
||||
// Store the original onNodeCreated
|
||||
const originalOnNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
const result = originalOnNodeCreated?.apply(this, arguments);
|
||||
|
||||
this.isEditMode = false;
|
||||
this.size = [400, 300];
|
||||
|
||||
// Don't hide the original widget - instead, replace it with our custom display
|
||||
setTimeout(() => {
|
||||
const htmlWidget = this.widgets?.find(w => w.name === "html_content");
|
||||
if (htmlWidget && htmlWidget.element) {
|
||||
// Store the original value
|
||||
this.htmlContent = htmlWidget.value;
|
||||
|
||||
// Create our custom container
|
||||
this.createHTMLNoteDisplay(this.htmlContent);
|
||||
|
||||
// Hide the original widget but keep its value
|
||||
htmlWidget.computeSize = () => [0, 0];
|
||||
htmlWidget.element.style.display = 'none';
|
||||
}
|
||||
}, 0);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
nodeType.prototype.createHTMLNoteDisplay = function(content) {
|
||||
// Create container element
|
||||
this.containerElement = document.createElement("div");
|
||||
this.containerElement.className = "af-html-note-container";
|
||||
|
||||
// Create HTML content display
|
||||
this.contentElement = document.createElement("div");
|
||||
this.contentElement.className = "af-html-note-content";
|
||||
this.contentElement.innerHTML = content;
|
||||
|
||||
// Create editor element
|
||||
this.editorElement = document.createElement("textarea");
|
||||
this.editorElement.className = "af-html-note-editor";
|
||||
this.editorElement.value = content;
|
||||
|
||||
// Create floating edit button
|
||||
this.editButton = document.createElement("div");
|
||||
this.editButton.className = "af-floating-edit-btn";
|
||||
this.editButton.innerHTML = "✏️";
|
||||
this.editButton.title = "Edit HTML";
|
||||
|
||||
// Append all elements to container
|
||||
this.containerElement.appendChild(this.contentElement);
|
||||
this.containerElement.appendChild(this.editorElement);
|
||||
this.containerElement.appendChild(this.editButton);
|
||||
|
||||
// Edit button click handler
|
||||
this.editButton.addEventListener('click', (e) => {
|
||||
if (!this.isEditMode) {
|
||||
this.enterEditMode();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
// Add back double-click editing (user-friendly alternative)
|
||||
this.contentElement.addEventListener('dblclick', (e) => {
|
||||
if (!this.isEditMode) {
|
||||
this.enterEditMode();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle links
|
||||
this.handleLinksInHTML();
|
||||
|
||||
// Handle editor events
|
||||
this.editorElement.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
this.exitEditMode();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
// Comment
|
||||
this.contentElement.setAttribute('tabindex', '-1');
|
||||
|
||||
this.editorElement.addEventListener('blur', () => {
|
||||
setTimeout(() => {
|
||||
if (this.isEditMode && document.activeElement !== this.editorElement) {
|
||||
this.exitEditMode();
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
|
||||
this.contentElement.addEventListener('mousedown', (e) => {
|
||||
// Prevent the content area from getting focus on click
|
||||
e.preventDefault();
|
||||
|
||||
// But don't stop propagation - let ComfyUI handle node selection
|
||||
// Only prevent the default focus behavior
|
||||
});
|
||||
|
||||
// Add as DOM widget
|
||||
this.addDOMWidget("html_note_display", "div", this.containerElement);
|
||||
};
|
||||
|
||||
nodeType.prototype.enterEditMode = function() {
|
||||
if (this.isEditMode) return;
|
||||
|
||||
this.isEditMode = true;
|
||||
this.containerElement.classList.add('edit-mode');
|
||||
this.editorElement.value = this.htmlContent;
|
||||
|
||||
setTimeout(() => {
|
||||
this.editorElement.focus();
|
||||
this.editorElement.select();
|
||||
}, 10);
|
||||
};
|
||||
|
||||
nodeType.prototype.exitEditMode = function() {
|
||||
if (!this.isEditMode) return;
|
||||
|
||||
this.isEditMode = false;
|
||||
this.containerElement.classList.remove('edit-mode');
|
||||
this.htmlContent = this.editorElement.value;
|
||||
this.contentElement.innerHTML = this.htmlContent;
|
||||
|
||||
// Update the original widget value
|
||||
const htmlWidget = this.widgets?.find(w => w.name === "html_content");
|
||||
if (htmlWidget) {
|
||||
htmlWidget.value = this.htmlContent;
|
||||
if (htmlWidget.callback) {
|
||||
htmlWidget.callback(this.htmlContent);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-attach link handlers
|
||||
this.handleLinksInHTML();
|
||||
};
|
||||
|
||||
// Updated link handler - REMOVED JavaScript execution
|
||||
nodeType.prototype.handleLinksInHTML = function() {
|
||||
if (!this.contentElement) return;
|
||||
|
||||
const links = this.contentElement.querySelectorAll('a');
|
||||
links.forEach(link => {
|
||||
// Remove any existing onclick handlers to prevent JavaScript execution
|
||||
link.onclick = null;
|
||||
|
||||
link.addEventListener('click', (e) => {
|
||||
// Only allow clicks when Ctrl is held
|
||||
if (!document.body.classList.contains('af-ctrl-active')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
const href = link.getAttribute('href');
|
||||
|
||||
// Handle different types of links
|
||||
if (href) {
|
||||
// External URLs and anchor links - open in new tab
|
||||
if (href.startsWith('http://') ||
|
||||
href.startsWith('https://') ||
|
||||
href.startsWith('mailto:') ||
|
||||
href.startsWith('tel:')) {
|
||||
e.preventDefault();
|
||||
window.open(href, '_blank');
|
||||
}
|
||||
// Internal anchor links (page navigation) - let browser handle
|
||||
else if (href.startsWith('#') || href === '#') {
|
||||
// Allow default behavior for anchor links
|
||||
// They will navigate within the same page
|
||||
}
|
||||
// JavaScript links - BLOCK for security
|
||||
else if (href.startsWith('javascript:')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.warn('AF HTML Note: JavaScript links are disabled for security');
|
||||
return false;
|
||||
}
|
||||
// Relative URLs and other links - open in new tab
|
||||
else {
|
||||
e.preventDefault();
|
||||
window.open(href, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Global Ctrl key handling
|
||||
let ctrlKeyActive = false;
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Control') {
|
||||
ctrlKeyActive = true;
|
||||
document.body.classList.add('af-ctrl-active');
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keyup', (e) => {
|
||||
if (e.key === 'Control') {
|
||||
ctrlKeyActive = false;
|
||||
document.body.classList.remove('af-ctrl-active');
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up when node is removed
|
||||
const originalOnRemoved = nodeType.prototype.onRemoved;
|
||||
nodeType.prototype.onRemoved = function() {
|
||||
document.body.classList.remove('af-ctrl-active');
|
||||
originalOnRemoved?.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
// web/nodes.js - Entry point for ComfyUI
|
||||
import './AF-Enhanced-HTML-Note.js';
|
||||