先打开设置——编辑器——打开 允许执行 HTML 块内脚本(这样 html 块才能使用 JS 脚本)
提示词(可根据自己喜欢的风格微调,或者直接将提示词变成智能体用)
我需要一个为思源笔记深度定制的 HTML 块,请严格遵循以下所有要求,并且**使用 HTML 和 CSS**和JS则必须遵循和使用JS模板的全部内容(注意模板的交互内容和光标内容必须使用,其他功能JS按照用户所需使用):
1. **功能目标**: 创建一个[这里填写你的具体功能,例如:会议纪要模板、项目信息卡、读书笔记模板...]。
2. **思源笔记兼容性优化 (最高优先级,必须遵循以下规则)**:
* 最终生成的代码**绝对不能**包含 `<!DOCTYPE html>`, `<html>`, `<head>`, `<body>` 这些顶层标签。
* 所有 HTML 元素以及 `<style>` 标签,都必须被包裹在一个**唯一的父 `<div>`** 标签内。
* 请确保最终代码是**删除所有在两行内容之间起分隔作用的、完全为空的行**,以防止思源笔记错误地将其解析为多个代码块。
3. **核心交互元素 (关键!)**:
* 重点!!!模板中所有需要用户填写或修改的内容,**必须**使用 `<input>`, `<textarea>` 或任何其他表单元素,因为是笔记,所以只要能有个输入框的元素,不用真正的表单元素UI界面
**事件监听** 必须为整个组件的主容器(例如,class为.universal-container的div)添加一个全局的input事件监听器并配合JS模板的交互。如果组件中包含复选框(checkbox)或其他需要点击交互的元素,也需要为主容器添加click事件监听器,并判断事件目标是否为这些元素。
4. **外观和样式 (CSS)**:
**整体风格**: 设计为[用户选择风格]模板的风格为Notion模板风格,但**背景色如果用户定义配色则需要整体配色也请围绕这个主色调进行协调设计**
**图标化标题**: 每个主要板块的标题(如 <h2>)前添加一个合适的 Emoji 图标,以增强结构感和视觉吸引力。
**文本样式**: 确保所有文本内容(包括标题和段落)都清晰易读。你可以为不同的文本元素设置不同的颜色、字重或样式,以区分其功能。
**字体与排版**: 使用优雅、易读的中文字体(如“思源黑体”),并设置合适的行高和间距,保证整体的美观性。移除不必要的边框、阴影和复杂的背景,让它看起来像一个真正的笔记页面,而不是一个网页卡片。
**全屏宽度**: 模板的根容器必须设置为 width: 100%,并移除任何 max-width 限制和外边距 margin,使其完全占满笔记的可用宽度。
**内容延展**: 模板内部的文本和布局元素(如左右分栏)也必须能随容器延展,填充整个屏幕空间,不能被限制在一个固定宽度的居中区域内。
**栏目结构**: 设计为[这里填写布局要求,例如:优雅的单栏布局、响应式左右双栏布局...]。如果是双栏或多栏,请确保在移动端等窄屏幕上能自动切换为单栏垂直排列。
**动态效果** (可选): 如果需要动画效果(例如:[这里填写想要的动效,如“樱花飘落”...]),必须使用纯 CSS 的 @keyframes 来实现,并确保动画元素不会干扰到下方内容的编辑,并且尽量减少cpu消耗的代码例如transform 和 opacity等
5. **最重要的一点**:
* **JS必须遵循以下模板**使用任何 `<script>` 标签或任何形式的 JavaScript,需要遵循下面模板
模板要求:1.指纹与全局搜索:必须使用“CUSTOM_ID + 全局搜索”的模式来定位自身。
在脚本顶部定义一个独一无二的 const CUSTOM_ID = "一个独特的字符串";。
使用 document.querySelector(\protyle-html[data-content*="${customID}"]`)` 的方式进行全局查找。
2.使用 Shadow DOM:所有对 HTML 块内部元素的查找和操作,都必须通过获取到的 shadowRoot 引用来进行,例如 self.shadowRoot.getElementById('...')。
3.作用域隔离:所有 JavaScript 代码必须包裹在一个大括号 {...} 中,以创建局部作用域。
4.立即执行:核心逻辑应放在一个立即执行的匿名函数 (() => { ... })(); 中,并在此函数内部首先调用 findSelf() 进行初始化。
5.必须使用下面模板的全部内容,包括交互和光标,如添加其他JS,则在其他JS区添加
JS模板案例如下:
<!-- 脚本部分 -->
<script>
{
// --- 模板核心:请勿修改 ---
/**
* 1. “指纹”:为你的每个新功能生成一个独一无二的ID。
* 你可以使用在线UUID生成器来创建一个新的ID,确保它不会和别的代码块冲突。
*/
const CUSTOM_ID = "a1b2c3d4-e5f6-7890-1234-567890abcdef-merged-v14-template";
/**
* 2. “全局搜索”函数:这是保证脚本正常运行的核心,无需修改。
*/
function findSelf(customID) {
const protyle = document.querySelector(`protyle-html[data-content*="${customID}"]`);
if (protyle && protyle.parentElement && protyle.parentElement.parentElement) {
const block = protyle.parentElement.parentElement;
return { id: block.dataset.nodeId, shadowRoot: protyle.shadowRoot };
}
return null;
}
// --- 模板结束 ---
// 立即执行主逻辑
(() => {
const self = findSelf(CUSTOM_ID);
if (!self || !self.shadowRoot) {
console.error(`[${CUSTOM_ID}] 脚本初始化失败:无法定位。`);
return;
}
// --- 核心功能区:自动保存与光标恢复 ---
function initializeAutoSaveAndCursorRestore(self) {
const mainContainer = self.shadowRoot.querySelector('.universal-container');
const titleInput = self.shadowRoot.getElementById('editable-title');
const contentInput = self.shadowRoot.getElementById('editable-content');
if (!mainContainer || !titleInput || !contentInput) {
console.error(`[${CUSTOM_ID}] 核心元素获取失败,核心功能无法加载。`);
return;
}
try {
const restoreStateJSON = sessionStorage.getItem('siyuanCursorRestoreState');
if (restoreStateJSON) {
sessionStorage.removeItem('siyuanCursorRestoreState');
const restoreState = JSON.parse(restoreStateJSON);
if (restoreState.blockId === self.id) {
const elementToRestore = self.shadowRoot.getElementById(restoreState.activeElementId);
if (elementToRestore) {
setTimeout(() => {
elementToRestore.focus();
elementToRestore.setSelectionRange(restoreState.cursorPos, restoreState.cursorPos);
}, 10);
}
}
}
} catch (e) {
console.error(`[${CUSTOM_ID}] 恢复光标位置失败:`, e);
}
let initialState = { title: '', content: '' };
const saveContent = (activeElementId, cursorPos) => {
const rootWrapper = self.shadowRoot.querySelector('#block-root-wrapper');
if (!rootWrapper) return;
const restoreState = { blockId: self.id, activeElementId: activeElementId, cursorPos: cursorPos };
sessionStorage.setItem('siyuanCursorRestoreState', JSON.stringify(restoreState));
const tempWrapper = rootWrapper.cloneNode(true);
tempWrapper.querySelector('#editable-title').setAttribute('value', titleInput.value);
tempWrapper.querySelector('#editable-content').textContent = contentInput.value;
const requestData = { dataType: "markdown", data: tempWrapper.outerHTML, id: self.id };
fetch('/api/block/updateBlock', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestData)
}).then(res => res.json()).then(result => {
if (result.code !== 0) sessionStorage.removeItem('siyuanCursorRestoreState');
}).catch(() => sessionStorage.removeItem('siyuanCursorRestoreState'));
};
mainContainer.addEventListener('focusin', () => {
initialState.title = titleInput.value;
initialState.content = contentInput.value;
});
mainContainer.addEventListener('focusout', (e) => {
const focusIsLeavingComponent = !e.relatedTarget || !mainContainer.contains(e.relatedTarget);
if (focusIsLeavingComponent) {
const contentHasChanged = titleInput.value !== initialState.title || contentInput.value !== initialState.content;
if (contentHasChanged) {
saveContent(e.target.id, e.target.selectionEnd || 0);
}
}
});
mainContainer.addEventListener('input', (e) => {
const target = e.target;
if (target.matches('.editable-title, .editable-content')) {
const cursorPos = target.selectionEnd;
setTimeout(() => {
target.focus();
target.setSelectionRange(cursorPos, cursorPos);
}, 0);
}
});
console.log(`[${CUSTOM_ID}] 核心功能 (V14: 智能保存 & 光标恢复) 已加载。`);
}
initializeAutoSaveAndCursorRestore(self);
// --- [重要] 用户自定义JS区 ---
// 你可以在下方这个区域加入你自己的JavaScript代码。
// 你可以直接使用 `self` 对象来访问当前HTML块的信息 (self.id, self.shadowRoot)。
//
// 示例 (取消下面的注释来测试):
/*
const titleElement = self.shadowRoot.getElementById('editable-title');
if (titleElement) {
titleElement.addEventListener('dblclick', () => {
alert('你双击了标题!块ID是: ' + self.id);
});
}
*/
// ------------------------------------
})();
}
</script>
</div>
参考完整代码案例
<div id="block-root-wrapper">
<!-- 样式部分 -->
<style>
/* 字体与基础样式 */
:host {
font-family: "Source Han Sans CN", "思源黑体", "Noto Sans CJK SC", sans-serif;
}
/* 根容器样式:全宽、Notion风格背景 */
.notion-block-container {
width: 100%;
padding: 20px 30px;
background-color: #ffffff;
color: #37352f;
box-sizing: border-box; /* 保证 padding 不会撑开宽度 */
}
/* 标题区域样式 */
.title-container {
display: flex;
align-items: center;
margin-bottom: 16px;
border-bottom: 1px solid #eee;
padding-bottom: 8px;
}
.title-icon {
font-size: 24px;
margin-right: 12px;
}
/* 将 input 样式重置为像普通文本 */
.editable-title {
border: none;
outline: none;
padding: 0;
font-size: 24px;
font-weight: 600;
width: 100%;
background-color: transparent;
color: #37352f;
}
/* 内容区域样式 */
.editable-content {
width: 100%;
min-height: 150px;
border: none;
outline: none;
padding: 0;
resize: vertical; /* 允许用户垂直调整大小 */
font-size: 16px;
line-height: 1.6;
background-color: transparent;
color: #37352f;
}
.editable-content::placeholder,
.editable-title::placeholder {
color: #b3b3b3;
}
</style>
<!-- HTML 结构 -->
<div id="siyuan-main-wrapper" class="notion-block-container">
<div class="universal-container">
<div class="title-container">
<span class="title-icon">🖋️</span>
<input type="text" id="editable-title" class="editable-title" placeholder="输入标题...">
</div>
<textarea id="editable-content" class="editable-content" placeholder="在这里输入你的内容..."></textarea>
</div>
</div>
<!-- 脚本部分 -->
<script>
{
// --- 模板核心:请勿修改 ---
/**
* 1. “指纹”:为你的每个新功能生成一个独一无二的ID。
* 你可以使用在线UUID生成器来创建一个新的ID,确保它不会和别的代码块冲突。
*/
const CUSTOM_ID = "a1b2c3d4-e5f6-7890-1234-567890abcdef-merged-v14-template";
/**
* 2. “全局搜索”函数:这是保证脚本正常运行的核心,无需修改。
*/
function findSelf(customID) {
const protyle = document.querySelector(`protyle-html[data-content*="${customID}"]`);
if (protyle && protyle.parentElement && protyle.parentElement.parentElement) {
const block = protyle.parentElement.parentElement;
return { id: block.dataset.nodeId, shadowRoot: protyle.shadowRoot };
}
return null;
}
// --- 模板结束 ---
// 立即执行主逻辑
(() => {
const self = findSelf(CUSTOM_ID);
if (!self || !self.shadowRoot) {
console.error(`[${CUSTOM_ID}] 脚本初始化失败:无法定位。`);
return;
}
// --- 核心功能区:自动保存与光标恢复 ---
function initializeAutoSaveAndCursorRestore(self) {
const mainContainer = self.shadowRoot.querySelector('.universal-container');
const titleInput = self.shadowRoot.getElementById('editable-title');
const contentInput = self.shadowRoot.getElementById('editable-content');
if (!mainContainer || !titleInput || !contentInput) {
console.error(`[${CUSTOM_ID}] 核心元素获取失败,核心功能无法加载。`);
return;
}
try {
const restoreStateJSON = sessionStorage.getItem('siyuanCursorRestoreState');
if (restoreStateJSON) {
sessionStorage.removeItem('siyuanCursorRestoreState');
const restoreState = JSON.parse(restoreStateJSON);
if (restoreState.blockId === self.id) {
const elementToRestore = self.shadowRoot.getElementById(restoreState.activeElementId);
if (elementToRestore) {
setTimeout(() => {
elementToRestore.focus();
elementToRestore.setSelectionRange(restoreState.cursorPos, restoreState.cursorPos);
}, 10);
}
}
}
} catch (e) {
console.error(`[${CUSTOM_ID}] 恢复光标位置失败:`, e);
}
let initialState = { title: '', content: '' };
const saveContent = (activeElementId, cursorPos) => {
const rootWrapper = self.shadowRoot.querySelector('#block-root-wrapper');
if (!rootWrapper) return;
const restoreState = { blockId: self.id, activeElementId: activeElementId, cursorPos: cursorPos };
sessionStorage.setItem('siyuanCursorRestoreState', JSON.stringify(restoreState));
const tempWrapper = rootWrapper.cloneNode(true);
tempWrapper.querySelector('#editable-title').setAttribute('value', titleInput.value);
tempWrapper.querySelector('#editable-content').textContent = contentInput.value;
const requestData = { dataType: "markdown", data: tempWrapper.outerHTML, id: self.id };
fetch('/api/block/updateBlock', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestData)
}).then(res => res.json()).then(result => {
if (result.code !== 0) sessionStorage.removeItem('siyuanCursorRestoreState');
}).catch(() => sessionStorage.removeItem('siyuanCursorRestoreState'));
};
mainContainer.addEventListener('focusin', () => {
initialState.title = titleInput.value;
initialState.content = contentInput.value;
});
mainContainer.addEventListener('focusout', (e) => {
const focusIsLeavingComponent = !e.relatedTarget || !mainContainer.contains(e.relatedTarget);
if (focusIsLeavingComponent) {
const contentHasChanged = titleInput.value !== initialState.title || contentInput.value !== initialState.content;
if (contentHasChanged) {
saveContent(e.target.id, e.target.selectionEnd || 0);
}
}
});
mainContainer.addEventListener('input', (e) => {
const target = e.target;
if (target.matches('.editable-title, .editable-content')) {
const cursorPos = target.selectionEnd;
setTimeout(() => {
target.focus();
target.setSelectionRange(cursorPos, cursorPos);
}, 0);
}
});
console.log(`[${CUSTOM_ID}] 核心功能 (V14: 智能保存 & 光标恢复) 已加载。`);
}
initializeAutoSaveAndCursorRestore(self);
// --- [重要] 用户自定义JS区 ---
// 你可以在下方这个区域加入你自己的JavaScript代码。
// 你可以直接使用 `self` 对象来访问当前HTML块的信息 (self.id, self.shadowRoot)。
//
// 示例 (取消下面的注释来测试):
/*
const titleElement = self.shadowRoot.getElementById('editable-title');
if (titleElement) {
titleElement.addEventListener('dblclick', () => {
alert('你双击了标题!块ID是: ' + self.id);
});
}
*/
// ------------------------------------
})();
}
</script>
</div>
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于