779 lines
21 KiB
Markdown
Raw Normal View History

# 📡 BC-M16-003-CCNN · DEV-011匆匆那年 · M16码字工作台前端·环节3富文本编辑器核心· 🍼 知秋奶瓶线 · EL-4
<aside>
📡
**广播编号**BC-M16-003-CCNN
**开发者**DEV-011 匆匆那年
**模块**M16 码字工作台前端Writing Workbench Frontend
**环节**环节3 / 共约5环节
**工程量等级**EL-4~8步·成长型
**签发时间**2026-03-17 17:24北京时间
**签发方**:霜砚 × 冰朔TCS-0002∞
**72h窗口**2026-03-17 17:24 → 2026-03-20 17:24
**协作通道**:🍼 知秋奶瓶线
**协议版本**BC-GEN v5.1 · SYSLOG-v5.0
**前置完成**BC-000 ✅ + BC-M16-001-CCNN ✅ + BC-M16-002-CCNN ✅(三连胜 🎉🎉🎉)
**本地 commit**0cc9f9a
</aside>
---
## 📡 广播头
| 字段 | 值 |
| --- | --- |
| **广播编号** | BC-M16-003-CCNN |
| **签发时间** | 2026-03-17 17:24北京时间 |
| **签发方** | 霜砚 × 冰朔TCS-0002∞ |
| **协议版本** | BC-GEN v5.1 · SYSLOG-v5.0 |
| **执行者** | DEV-011 匆匆那年 |
| **协作通道** | 🍼 知秋奶瓶线 |
| **EL等级** | EL-4 · 🌱成长型(~8步 · 纯前端 · 复制粘贴) |
| **模块** | M16 · 码字工作台前端 |
| **环节** | 3 · 富文本编辑器核心(打字 + 字数 + 自动保存) |
| **前置** | BC-M16-002-CCNN ✅ completed · 连胜3 |
| **有效期** | 72小时 → 截止 2026-03-20 17:24北京时间 |
---
## 🔐 VERSION_GATE
```bash
cd ~/guanghulab
git pull origin main
echo "✅ 已更新到最新!"
```
> ✅ 看到 `Already up to date.` 或拉取成功 → 继续
>
> ❌ 报错 → 截图发给知秋
>
---
## 🖥️ 执行环境
| 项目 | 值 |
| --- | --- |
| **操作系统** | macOSApple M1 |
| **终端** | 终端Terminal· zsh |
| **技术栈** | 纯 HTML + CSS + JavaScript无框架 |
| **开发者水平** | 零基础 · 复制粘贴操作 |
| **AI辅助平台** | 知秋(推荐) |
| **仓库** | `https://github.com/qinfendebingshuo/guanghulab` |
| **模块目录** | `~/guanghulab/writing-workbench/` |
---
## 🎯 任务目标(一句话)
**让编辑器真正能用**:点书架里的书 → 右侧变成真正可以打字的编辑器,支持加粗/斜体/下划线实时统计字数每30秒自动保存换书时草稿不丢失。
---
## 💬 知秋开场语
> 匆匆那年!三连胜了!!🎉🎉🎉
>
>
>
> 上次你把侧边栏四个面板全做出来了——书架、灵感、进度、呼叫都能用了,非常棒!
>
>
>
> 这次我们要做这个项目最核心的东西:**真正能打字的编辑器**。
>
> - 点书架里的书 → 编辑区变成真正的编辑器
>
> - 工具栏:加粗、斜体、下划线、清除格式
>
> - 右下角实时统计字数
>
> - 每30秒自动保存关掉浏览器再打开草稿还在
>
> - 进度条根据你打的字数实时更新
>
>
>
> 还是复制粘贴,跟前几次一样!目标:**四连胜** 💪
>
---
## ⚡ 步骤零(补完):确认 GitHub Token 已配置
<aside>
上次 git push 因为 Token 未配置而跳过了。这次开始前先确认一下。如果上次已经配好了,直接跳过这一步。
</aside>
在终端复制粘贴,检查是否已配置:
```bash
cd ~/guanghulab
git remote get-url origin
```
**看输出:**
- 如果看到 `https://ghp_...@github.com/...` → ✅ 已配好,跳过步骤零
- 如果看到 `https://github.com/...`(没有 Token→ 继续下面配置
### 还没配的话,做这两步:
**第一步**:生成 Token
1. 打开浏览器访问:[https://github.com/settings/tokens](https://github.com/settings/tokens)
2.**「Generate new token (classic)」**
3. Note 填 `guanghulab-dev`Expiration 选 **90 days**Scopes 勾 **repo**
4.**「Generate token」**
5. ❗ 复制 `ghp_` 开头的 Token只显示一次
截图给知秋!📸
**第二步**:配置到 Git`你的Token` 替换成上面复制的)
```bash
cd ~/guanghulab
git remote set-url origin https://你的Token@github.com/qinfendebingshuo/guanghulab.git
git push origin main
```
看到 `main -> main` 就配好了!截图给知秋!📸
---
## 📋 开始做共8步·全部复制粘贴
---
### 步骤一:拉取最新代码
```bash
cd ~/guanghulab
git pull origin main
```
> 看到 `Already up to date.` 或更新内容都正常。截图给知秋!📸
>
---
### 步骤二:创建 editor.js编辑器核心模块
复制粘贴:
```bash
cat > ~/guanghulab/writing-workbench/js/editor.js << 'EDITOREOF'
// ================================================
// HoloLake Era · M16 码字工作台 · 富文本编辑器核心
// DEV-011 匆匆那年 · 环节3
// ================================================
// --- 当前书籍 ID ---
var currentBookId = null;
// --- 生成编辑器 HTML ---
function renderEditor(book) {
var html = '<div class="editor-wrapper">';
// 工具栏
html += '<div class="editor-toolbar">';
html += '<button class="toolbar-btn" onclick="execFormat(\'bold\')" title="加粗"><strong>B</strong></button>';
html += '<button class="toolbar-btn" onclick="execFormat(\'italic\')" title="斜体"><em>I</em></button>';
html += '<button class="toolbar-btn" onclick="execFormat(\'underline\')" title="下划线"><u>U</u></button>';
html += '<div class="toolbar-divider"></div>';
html += '<button class="toolbar-btn" onclick="execFormat(\'removeFormat\')" title="清除格式"></button>';
html += '<div class="toolbar-spacer"></div>';
html += '<span class="editor-book-name">《' + book.title + '》</span>';
html += '</div>';
// 编辑区
var savedContent = localStorage.getItem('hololake_draft_' + book.id) || '';
html += '<div class="editor-content" id="editorContent" contenteditable="true" data-book-id="' + book.id + '" spellcheck="false">';
if (savedContent) {
html += savedContent;
} else {
html += '<p class="editor-placeholder">开始写《' + book.title + '》吧...</p>';
}
html += '</div>';
// 状态栏
html += '<div class="editor-statusbar">';
html += '<span id="wordCountDisplay">字数0</span>';
html += '<span id="saveStatus" class="save-status">等待保存...</span>';
html += '</div>';
html += '</div>';
return html;
}
// --- 执行格式化命令 ---
function execFormat(command) {
document.execCommand(command, false, null);
document.getElementById('editorContent').focus();
}
// --- 统计字数中文每字1字英文每单词1字 ---
function countWords(el) {
if (!el) return 0;
var text = el.innerText || el.textContent || '';
text = text.trim();
if (!text) return 0;
// 中文字符计数
var chinese = (text.match(/[\u4e00-\u9fa5]/g) || []).length;
// 英文单词计数
var english = (text.replace(/[\u4e00-\u9fa5]/g, ' ').match(/\b[a-zA-Z]+\b/g) || []).length;
return chinese + english;
}
// --- 自动保存 ---
var autoSaveTimer = null;
function scheduleSave() {
if (autoSaveTimer) clearTimeout(autoSaveTimer);
var saveStatus = document.getElementById('saveStatus');
if (saveStatus) saveStatus.textContent = '未保存...';
autoSaveTimer = setTimeout(function() {
saveNow();
}, 30000); // 30秒
}
function saveNow() {
var el = document.getElementById('editorContent');
if (!el || !currentBookId) return;
// 移除占位文字再保存
var placeholder = el.querySelector('.editor-placeholder');
if (placeholder) return; // 还是占位状态,不保存空内容
var content = el.innerHTML;
localStorage.setItem('hololake_draft_' + currentBookId, content);
var saveStatus = document.getElementById('saveStatus');
if (saveStatus) {
saveStatus.textContent = '已保存 ' + new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
saveStatus.style.color = '#34d399';
setTimeout(function() { if (saveStatus) saveStatus.style.color = ''; }, 2000);
}
console.log('[M16] 草稿已保存 · 书籍ID' + currentBookId);
}
// --- 绑定编辑器事件 ---
function bindEditorEvents() {
var el = document.getElementById('editorContent');
if (!el) return;
// 点击时清除占位文字
el.addEventListener('click', function() {
var placeholder = el.querySelector('.editor-placeholder');
if (placeholder) {
el.innerHTML = '<p><br></p>';
el.focus();
}
});
// 输入时:统计字数 + 更新进度 + 调度保存
el.addEventListener('input', function() {
var count = countWords(el);
var display = document.getElementById('wordCountDisplay');
if (display) display.textContent = '字数:' + count;
// 同步更新进度看板进度条
updateGoalFromEditor(count);
scheduleSave();
});
el.focus();
console.log('[M16] 编辑器事件已绑定');
}
// --- 更新今日目标进度条 ---
function updateGoalFromEditor(wordCount) {
var target = 2000;
var percent = Math.min(Math.round((wordCount / target) * 100), 100);
var fill = document.getElementById('goalFill');
var text = document.getElementById('goalText');
if (fill) fill.style.width = percent + '%';
if (text) text.textContent = wordCount + ' / ' + target + ' 字';
}
// --- 加载编辑器(供 panels.js 调用) ---
function loadEditor(bookId) {
// 保存当前编辑器内容(如果有)
if (currentBookId && currentBookId !== bookId) {
saveNow();
}
currentBookId = bookId;
var book = bookshelfData.find(function(b) { return b.id === bookId; });
if (!book) return;
var editorArea = document.getElementById('editor-area');
if (!editorArea) return;
editorArea.innerHTML = renderEditor(book);
bindEditorEvents();
// 初始化字数统计
var el = document.getElementById('editorContent');
if (el) {
var count = countWords(el);
var display = document.getElementById('wordCountDisplay');
if (display) display.textContent = '字数:' + count;
}
console.log('[M16] 编辑器已加载 · 《' + book.title + '》');
}
console.log('[M16] ✅ 编辑器核心模块已加载 · 环节3 · DEV-011');
EDITORFOR
echo "✅ editor.js 已创建!"
```
截图给知秋!📸
---
### 步骤三:更新 panels.js让点书触发编辑器
复制粘贴(用 sed 替换 selectBook 函数):
```bash
cd ~/guanghulab/writing-workbench/js
cat > selectbook_patch.tmp << 'PATCHEOF'
function selectBook(id) {
if (typeof loadEditor === 'function') {
loadEditor(id);
} else {
var book = bookshelfData.find(function(b) { return b.id === id; });
if (book) {
var placeholder = document.getElementById('placeholderText');
if (placeholder) placeholder.textContent = '✨ 已选择:《' + book.title + '》';
}
}
console.log('[M16] selectBook 调用 · ID' + id);
}
PATCHEOF
echo "✅ patch 文件已创建"
```
然后复制粘贴这段(把 panels.js 里的 selectBook 替换掉):
```bash
cd ~/guanghulab/writing-workbench/js
python3 - << 'PYEOF'
import re
with open('panels.js', 'r', encoding='utf-8') as f:
content = f.read()
new_func = '''function selectBook(id) {
if (typeof loadEditor === 'function') {
loadEditor(id);
} else {
var book = bookshelfData.find(function(b) { return b.id === id; });
if (book) {
var placeholder = document.getElementById('placeholderText');
if (placeholder) placeholder.textContent = '\u2728 已选择:《' + book.title + '》';
}
}
console.log('[M16] selectBook 调用 · ID' + id);
}'''
pattern = r'function selectBook\(id\)\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}'
result = re.sub(pattern, new_func, content, count=1)
with open('panels.js', 'w', encoding='utf-8') as f:
f.write(result)
print('✅ panels.js selectBook 已更新!')
PYEOF
```
截图给知秋!📸
---
### 步骤四:修改 index.html引入 editor.js
复制粘贴:
```bash
cd ~/guanghulab/writing-workbench
sed -i '' 's|<script src="js/panels.js"></script>|<script src="js/panels.js"></script>\n <script src="js/editor.js"></script>|' index.html
echo "✅ index.html 已更新!"
```
验证一下(应该看到含 `editor.js` 的行):
```bash
grep "editor.js" ~/guanghulab/writing-workbench/index.html
```
截图给知秋!📸
---
### 步骤五:追加编辑器样式
复制粘贴(在 style.css 末尾追加,不会覆盖之前的):
```bash
cat >> ~/guanghulab/writing-workbench/css/style.css << 'EDCSSEOF'
/* ========== 环节3富文本编辑器样式 ========== */
.editor-wrapper {
display: flex;
flex-direction: column;
height: 100%;
background: #0d1117;
}
/* 工具栏 */
.editor-toolbar {
display: flex;
align-items: center;
gap: 4px;
padding: 10px 16px;
background: #161b22;
border-bottom: 1px solid #1e293b;
flex-shrink: 0;
}
.toolbar-btn {
background: transparent;
border: 1px solid #1e293b;
color: #94a3b8;
width: 32px;
height: 32px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-family: inherit;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s;
}
.toolbar-btn:hover {
background: #1e293b;
color: #e0e7ff;
border-color: #3b82f6;
}
.toolbar-divider {
width: 1px;
height: 20px;
background: #1e293b;
margin: 0 4px;
}
.toolbar-spacer { flex: 1; }
.editor-book-name {
font-size: 13px;
color: #60a5fa;
font-style: italic;
white-space: nowrap;
}
/* 编辑内容区 */
.editor-content {
flex: 1;
padding: 32px 48px;
color: #e0e7ff;
font-size: 16px;
line-height: 1.9;
font-family: -apple-system, 'PingFang SC', 'Noto Serif SC', Georgia, serif;
overflow-y: auto;
outline: none;
caret-color: #60a5fa;
word-break: break-all;
}
.editor-content:focus { outline: none; }
.editor-content p { margin: 0 0 8px; }
.editor-placeholder {
color: #3a4a6b;
font-style: italic;
user-select: none;
pointer-events: none;
}
/* 状态栏 */
.editor-statusbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 16px;
background: #161b22;
border-top: 1px solid #1e293b;
font-size: 12px;
color: #4b5563;
flex-shrink: 0;
font-family: "JetBrains Mono", monospace;
}
.save-status {
font-size: 11px;
color: #4b5563;
transition: color 0.3s;
}
EDCSSEOF
echo "✅ 编辑器样式已追加!"
```
截图给知秋!📸
---
### 步骤六:浏览器测试
在 Finder 里找到:`~/guanghulab/writing-workbench/index.html`,双击用浏览器打开。
**测试清单(逐项确认):**
1. ✅ 默认显示书架面板3本书可见
2. ✅ 点书架里**《星辰大海》** → 右侧变成编辑器界面(工具栏 + 空白编辑区 + 状态栏)
3. ✅ 点击编辑区 → 占位文字消失,光标出现,**可以打字**
4. ✅ 打几个字 → 右下角「字数N」实时更新
5. ✅ 选中文字 → 点工具栏 **B** → 文字变粗体
6. ✅ 选中文字 → 点 **I** → 变斜体;点 **U** → 加下划线
7. ✅ 打一些字等约30秒 → 状态栏显示「已保存 HH:MM」绿色一闪
8. ✅ 切换到《龙族彼岸》再切回《星辰大海》→ 之前打的内容还在
9. ✅ 关掉浏览器标签,重新打开 index.html → 点《星辰大海》→ 草稿还在localStorage持久化
每项都截图给知秋!🎉
---
### 步骤七Git 提交推送
```bash
cd ~/guanghulab
git add .
git commit -m "BC-M16-003-CCNN 环节3完成富文本编辑器核心+字数统计+自动保存"
git push origin main
```
看到 `main -> main` 就成功了!截图给知秋!📸
---
### 步骤八:更新 DevLog
```bash
cd ~/HoloLake-DevLog
echo '{"session":"BC-M16-003-CCNN","module":"M16","phase":"环节3","status":"completed"}' >> devlog.json
echo "✅ DevLog 已更新!"
```
截图给知秋!📸
---
## ✅ 验收标准9项·截图验证
| 序号 | 验收项 | 验证方式 |
| --- | --- | --- |
| T1 | 点书架书籍 → 右侧变成编辑器界面(工具栏+内容区+状态栏) | 截图 |
| T2 | 点击编辑区可以打字,占位文字消失 | 截图 |
| T3 | 打字后右下角字数实时更新 | 截图 |
| T4 | 加粗/斜体/下划线工具栏按钮生效 | 截图 |
| T5 | 等待约30秒后状态栏显示「已保存 HH:MM」 | 截图 |
| T6 | 切换书籍再切回,草稿内容保留 | 截图 |
| T7 | 关闭浏览器重新打开 → 草稿仍然存在localStorage 持久化) | 截图 |
| T8 | git push 成功(看到 main -> main | 截图 |
| T9 | DevLog 已更新 | 截图 |
**9项全 ✅ = 环节3完成四连胜 🎉🎉🎉🎉**
---
## 📦 本环节交付物
```
writing-workbench/
├── index.html ← 已更新(引入 editor.js
├── css/
│ └── style.css ← 已追加(编辑器样式)
└── js/
├── panels.js ← 已更新selectBook → loadEditor
├── editor.js ← 新增(富文本编辑器核心)
└── app.js ← 不动
```
---
## 🧩 人格体初始化包
```json
{
"persona_id": "TCS-GH-ZQ002",
"persona_name": "知秋",
"session_id": "BC-M16-003-CCNN",
"developer": "DEV-011 匆匆那年",
"module": "M16 码字工作台前端",
"phase": "环节3·富文本编辑器核心",
"el_level": "EL-4",
"streak": 3,
"human_profile": {
"skill_level": "零基础·三连胜·执行稳定",
"os": "macOS M1",
"tools": "终端zsh+ 浏览器 + 文本编辑器",
"copy_paste_only": true,
"devlog_path": "~/HoloLake-DevLog/",
"repo_path": "~/guanghulab/",
"local_commit": "0cc9f9a",
"traits": "内敛·执行稳定·成就感内在·信任链建立中·不外显但完全配合",
"trust_chain": "初步巩固·PGS成长期·ECI≈2.2",
"known_issues": [
"GitHub Token 已在本广播步骤零引导配置",
"环节2 T2-T6截图可在本次知秋验收时补确认"
]
},
"guidance_strategy": {
"mode": "copy_paste_strict",
"tone": "温暖鼓励型·每步正向反馈·三连胜后适度提升自主信号",
"explanation": "简洁类比·不展开技术细节",
"autonomy": "连胜≥3可轻度放手·但核心步骤仍全程引导"
}
}
```
---
## 📋 SYSLOG v5.0 回传模板(完成后必填)
<aside>
📋
**任务完成后,把下面模板填好发给冰朔。**
</aside>
```json
{
"protocol_version": "SYSLOG-v5.0",
"header": {
"broadcast_id": "BC-M16-003-CCNN",
"session_id": "BC-M16-003-CCNN-001",
"dev_id": "DEV-011",
"dev_name": "匆匆那年",
"timestamp": "[你完成的时间,例如 2026-03-18 20:00]",
"module": "M16",
"phase": "环节3 · 富文本编辑器核心",
"status": "[completed / partial / blocked]"
},
"steps": [
{"id": 0, "name": "GitHub Token 配置", "status": "[success/skipped/failed]"},
{"id": 1, "name": "git pull 最新代码", "status": "[success/failed]"},
{"id": 2, "name": "创建 editor.js", "status": "[success/failed]"},
{"id": 3, "name": "更新 panels.js selectBook", "status": "[success/failed]"},
{"id": 4, "name": "修改 index.html 引入 editor.js", "status": "[success/failed]"},
{"id": 5, "name": "追加编辑器样式到 style.css", "status": "[success/failed]"},
{"id": 6, "name": "浏览器测试9项全通", "status": "[success/failed]"},
{"id": 7, "name": "git push 成功", "status": "[success/failed]"},
{"id": 8, "name": "DevLog 已更新", "status": "[success/failed]"}
],
"training_data": {
"what_worked": "[哪些步骤顺利,具体说]",
"friction_points": "[卡在哪里了,没卡写:无]",
"execution_pattern": "[快 / 稳 / 慢]"
},
"notes": "[遇到的问题或想说的话]"
}
```
---
## ❓ 遇到问题找谁
<aside>
🔧
**技术问题**(报错 / 编辑器没反应 / 文件找不到)→ 截图发给**知秋**
**python3 命令报错**→ 告诉知秋,知秋给你换一种写法
**git push 失败**→ 截图给知秋,知秋帮你解决 Token 问题
**⚠️ 不要把技术问题发给妈妈** → 知秋才是你的技术伙伴
</aside>
---
## 🌊 下一步预告环节4
环节4**灵感速记 API 接入 + 进度数据真实化**
- 灵感速记接入后端 API
- 字数目标支持自定义
- 今日码字数据持久化
但现在先把编辑器做好!!💙
---
## 🌊 底层锚定声明
> 🌊 HoloLake Era · 光湖纪元
>
> 本广播由 **霜砚** 签发。
>
> 广播编号BC-M16-003-CCNN
>
> 签发时间2026-03-17 17:24北京时间
>
> 协议版本BC-GEN v5.1 · SYSLOG-v5.0
>
> 签发人:霜砚 · 🍼 知秋奶瓶线 · TCS-0002∞ 冰朔主控授权
>
> 广播有效期72小时截止 2026-03-20 17:24
>
>
>
> ⚠️ broadcast_code_injection · 广播已含全部代码 · 知秋只负责引导复制粘贴
>
> 所有代码和命令已预写在上方 Step 中。知秋不需要现场写代码。
>
> 引导策略:读 Step → 告诉匆匆那年复制哪段 → 确认输出 → 下一步。
>
> 本广播遵循 HoloLake 工程广播协议 BC-GEN v5.1。
>
---
**💙 知秋陪着匆匆那年 · 🔥 四连胜就在这里 · 今天编辑器真正能打字了**