add note
This commit is contained in:
@@ -21,7 +21,7 @@ export function setCurrentLang(lang: string): void {
|
|||||||
localStorage.setItem('preferred-lang', lang)
|
localStorage.setItem('preferred-lang', lang)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderModeTabs(handle: string, activeTab: 'blog' | 'browser' | 'post' | 'chat' | 'link' = 'blog', isLocalUser: boolean = false): string {
|
export function renderModeTabs(handle: string, activeTab: 'blog' | 'browser' | 'post' | 'chat' | 'link' | 'note' = 'blog', isLocalUser: boolean = false): string {
|
||||||
let tabs = `
|
let tabs = `
|
||||||
<a href="/" class="tab">/</a>
|
<a href="/" class="tab">/</a>
|
||||||
<a href="/@${handle}" class="tab ${activeTab === 'blog' ? 'active' : ''}">${handle}</a>
|
<a href="/@${handle}" class="tab ${activeTab === 'blog' ? 'active' : ''}">${handle}</a>
|
||||||
@@ -33,6 +33,11 @@ export function renderModeTabs(handle: string, activeTab: 'blog' | 'browser' | '
|
|||||||
tabs += `<a href="/@${handle}/at/chat" class="tab ${activeTab === 'chat' ? 'active' : ''}">chat</a>`
|
tabs += `<a href="/@${handle}/at/chat" class="tab ${activeTab === 'chat' ? 'active' : ''}">chat</a>`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note tab only for local user (admin)
|
||||||
|
if (isLocalUser) {
|
||||||
|
tabs += `<a href="/@${handle}/at/note" class="tab ${activeTab === 'note' ? 'active' : ''}">note</a>`
|
||||||
|
}
|
||||||
|
|
||||||
if (isLoggedIn()) {
|
if (isLoggedIn()) {
|
||||||
tabs += `<a href="/@${handle}/at/post" class="tab ${activeTab === 'post' ? 'active' : ''}">post</a>`
|
tabs += `<a href="/@${handle}/at/post" class="tab ${activeTab === 'post' ? 'active' : ''}">post</a>`
|
||||||
tabs += `<a href="/@${handle}/at/link" class="tab ${activeTab === 'link' ? 'active' : ''}">link</a>`
|
tabs += `<a href="/@${handle}/at/link" class="tab ${activeTab === 'link' ? 'active' : ''}">link</a>`
|
||||||
|
|||||||
232
src/web/components/note.ts
Normal file
232
src/web/components/note.ts
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
import { renderMarkdown } from '../lib/markdown'
|
||||||
|
import type { Post } from '../types'
|
||||||
|
|
||||||
|
// Note post has extra fields for member content
|
||||||
|
interface NotePost extends Post {
|
||||||
|
value: Post['value'] & {
|
||||||
|
member?: {
|
||||||
|
text: string
|
||||||
|
bonus?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render note list page
|
||||||
|
export function renderNoteListPage(posts: NotePost[], handle: string): string {
|
||||||
|
if (posts.length === 0) {
|
||||||
|
return `<div class="note-empty">No note articles yet.</div>`
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = posts.map(post => {
|
||||||
|
const rkey = post.uri.split('/').pop() || ''
|
||||||
|
const date = new Date(post.value.publishedAt).toLocaleDateString('ja-JP', {
|
||||||
|
year: 'numeric', month: '2-digit', day: '2-digit'
|
||||||
|
})
|
||||||
|
const tags = post.value.tags?.map(t => `<span class="note-tag">${t}</span>`).join('') || ''
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="note-item">
|
||||||
|
<a href="/@${handle}/at/note/${rkey}" class="note-link">
|
||||||
|
<span class="note-date">${date}</span>
|
||||||
|
<span class="note-title">${escapeHtml(post.value.title)}</span>
|
||||||
|
</a>
|
||||||
|
${tags ? `<div class="note-tags">${tags}</div>` : ''}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}).join('')
|
||||||
|
|
||||||
|
return `<div class="note-list">${items}</div>`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render single note detail with preview + copy
|
||||||
|
export function renderNoteDetailPage(
|
||||||
|
post: NotePost,
|
||||||
|
_handle: string,
|
||||||
|
localOnly: boolean
|
||||||
|
): string {
|
||||||
|
const rkey = post.uri.split('/').pop() || ''
|
||||||
|
const date = new Date(post.value.publishedAt).toLocaleDateString('ja-JP', {
|
||||||
|
year: 'numeric', month: '2-digit', day: '2-digit'
|
||||||
|
})
|
||||||
|
const freeText = post.value.content?.text || ''
|
||||||
|
const memberText = post.value.member?.text || ''
|
||||||
|
const bonusText = post.value.member?.bonus || ''
|
||||||
|
|
||||||
|
const freeHtml = renderMarkdown(freeText)
|
||||||
|
const memberHtml = memberText ? renderMarkdown(memberText) : ''
|
||||||
|
const bonusHtml = bonusText ? renderMarkdown(bonusText) : ''
|
||||||
|
|
||||||
|
let html = ''
|
||||||
|
|
||||||
|
// Action buttons at top
|
||||||
|
if (localOnly) {
|
||||||
|
html += `
|
||||||
|
<div class="note-actions">
|
||||||
|
<button type="button" class="note-copy-btn" id="note-copy-title">Copy Title</button>
|
||||||
|
<button type="button" class="note-copy-btn" id="note-copy-all">Copy 全文</button>
|
||||||
|
<button type="button" class="note-copy-btn" id="note-edit-btn">Edit</button>
|
||||||
|
<span id="note-copy-status" class="note-copy-status"></span>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<div class="note-detail" id="note-display">
|
||||||
|
<h2 class="note-detail-title">${escapeHtml(post.value.title)}</h2>
|
||||||
|
<div class="note-detail-meta">${date}</div>
|
||||||
|
|
||||||
|
<div class="note-section">
|
||||||
|
<div class="note-section-label">本文(無料)</div>
|
||||||
|
<div class="note-content">${freeHtml}</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
if (memberText) {
|
||||||
|
html += `
|
||||||
|
<div class="note-paywall">── 有料ライン ──</div>
|
||||||
|
<div class="note-section">
|
||||||
|
<div class="note-section-label">答えと核心(有料)</div>
|
||||||
|
<div class="note-content">${memberHtml}</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bonusText) {
|
||||||
|
html += `
|
||||||
|
<div class="note-section">
|
||||||
|
<div class="note-section-label">今日のひとこま(おまけ)</div>
|
||||||
|
<div class="note-content">${bonusHtml}</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `</div>`
|
||||||
|
|
||||||
|
// Edit form (below content)
|
||||||
|
if (localOnly) {
|
||||||
|
html += `
|
||||||
|
<div class="note-edit" id="note-edit-form" style="display:none">
|
||||||
|
<input type="text" id="note-edit-title" class="note-edit-input" value="${escapeAttr(post.value.title)}" placeholder="Title">
|
||||||
|
<label class="note-edit-label">本文(無料)</label>
|
||||||
|
<textarea id="note-edit-free" class="note-edit-textarea" rows="10">${escapeHtml(freeText)}</textarea>
|
||||||
|
<label class="note-edit-label">答えと核心(有料)</label>
|
||||||
|
<textarea id="note-edit-member" class="note-edit-textarea" rows="8">${escapeHtml(memberText)}</textarea>
|
||||||
|
<label class="note-edit-label">今日のひとこま(おまけ)</label>
|
||||||
|
<textarea id="note-edit-bonus" class="note-edit-textarea" rows="5">${escapeHtml(bonusText)}</textarea>
|
||||||
|
<div class="note-edit-actions">
|
||||||
|
<button type="button" id="note-edit-save" data-rkey="${rkey}">Copy JSON</button>
|
||||||
|
<button type="button" id="note-edit-cancel">Cancel</button>
|
||||||
|
</div>
|
||||||
|
<div id="note-edit-status"></div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup note detail page (copy + edit handlers)
|
||||||
|
export function setupNoteDetail(
|
||||||
|
post: NotePost,
|
||||||
|
_onSave?: (rkey: string, record: Record<string, unknown>) => Promise<void>
|
||||||
|
): void {
|
||||||
|
const freeText = post.value.content?.text || ''
|
||||||
|
const memberText = post.value.member?.text || ''
|
||||||
|
const bonusText = post.value.member?.bonus || ''
|
||||||
|
|
||||||
|
// Copy buttons
|
||||||
|
const copyTitle = document.getElementById('note-copy-title')
|
||||||
|
const copyAll = document.getElementById('note-copy-all')
|
||||||
|
const copyStatus = document.getElementById('note-copy-status')
|
||||||
|
|
||||||
|
function copyToClipboard(text: string) {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
if (copyStatus) {
|
||||||
|
copyStatus.textContent = 'Copied!'
|
||||||
|
setTimeout(() => { copyStatus.textContent = '' }, 2000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
copyTitle?.addEventListener('click', () => copyToClipboard(post.value.title))
|
||||||
|
copyAll?.addEventListener('click', () => {
|
||||||
|
const parts = [freeText, memberText, bonusText].filter(Boolean)
|
||||||
|
copyToClipboard(parts.join('\n\n'))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Edit toggle
|
||||||
|
const editBtn = document.getElementById('note-edit-btn')
|
||||||
|
const editForm = document.getElementById('note-edit-form')
|
||||||
|
const display = document.getElementById('note-display')
|
||||||
|
const cancelBtn = document.getElementById('note-edit-cancel')
|
||||||
|
const saveBtn = document.getElementById('note-edit-save')
|
||||||
|
|
||||||
|
editBtn?.addEventListener('click', () => {
|
||||||
|
if (display) display.style.display = 'none'
|
||||||
|
if (editForm) editForm.style.display = 'block'
|
||||||
|
if (editBtn) editBtn.style.display = 'none'
|
||||||
|
})
|
||||||
|
|
||||||
|
cancelBtn?.addEventListener('click', () => {
|
||||||
|
if (editForm) editForm.style.display = 'none'
|
||||||
|
if (display) display.style.display = ''
|
||||||
|
if (editBtn) editBtn.style.display = ''
|
||||||
|
})
|
||||||
|
|
||||||
|
saveBtn?.addEventListener('click', () => {
|
||||||
|
const rkey = saveBtn.getAttribute('data-rkey')
|
||||||
|
if (!rkey) return
|
||||||
|
|
||||||
|
const title = (document.getElementById('note-edit-title') as HTMLInputElement)?.value.trim()
|
||||||
|
const free = (document.getElementById('note-edit-free') as HTMLTextAreaElement)?.value.trim()
|
||||||
|
const member = (document.getElementById('note-edit-member') as HTMLTextAreaElement)?.value.trim()
|
||||||
|
const bonus = (document.getElementById('note-edit-bonus') as HTMLTextAreaElement)?.value.trim()
|
||||||
|
|
||||||
|
if (!title || !free) {
|
||||||
|
alert('Title and content are required')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const did = post.uri.split('/')[2]
|
||||||
|
const record: Record<string, unknown> = {
|
||||||
|
cid: '',
|
||||||
|
uri: post.uri,
|
||||||
|
value: {
|
||||||
|
$type: 'ai.syui.note.post',
|
||||||
|
site: post.value.site,
|
||||||
|
title,
|
||||||
|
content: {
|
||||||
|
$type: 'ai.syui.note.post#markdown',
|
||||||
|
text: free,
|
||||||
|
},
|
||||||
|
publishedAt: post.value.publishedAt,
|
||||||
|
langs: post.value.langs || ['ja'],
|
||||||
|
tags: post.value.tags || [],
|
||||||
|
} as Record<string, unknown>,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (member || bonus) {
|
||||||
|
const memberObj: Record<string, string> = {}
|
||||||
|
if (member) memberObj.text = member
|
||||||
|
if (bonus) memberObj.bonus = bonus
|
||||||
|
;(record.value as Record<string, unknown>).member = memberObj
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = JSON.stringify(record, null, 2)
|
||||||
|
navigator.clipboard.writeText(json).then(() => {
|
||||||
|
const statusEl = document.getElementById('note-edit-status')
|
||||||
|
const filePath = `public/at/${did}/ai.syui.note.post/${rkey}.json`
|
||||||
|
if (statusEl) {
|
||||||
|
statusEl.innerHTML = `<span class="note-copy-status">JSON copied! Paste to: <code>${filePath}</code></span>`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(s: string): string {
|
||||||
|
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeAttr(s: string): string {
|
||||||
|
return s.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>')
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
export interface Route {
|
export interface Route {
|
||||||
type: 'home' | 'user' | 'post' | 'postpage' | 'atbrowser' | 'service' | 'collection' | 'record' | 'chat' | 'chat-thread' | 'chat-edit' | 'card' | 'card-old' | 'rse' | 'link' | 'vrm'
|
type: 'home' | 'user' | 'post' | 'postpage' | 'atbrowser' | 'service' | 'collection' | 'record' | 'chat' | 'chat-thread' | 'chat-edit' | 'card' | 'card-old' | 'rse' | 'link' | 'vrm' | 'note' | 'note-detail'
|
||||||
handle?: string
|
handle?: string
|
||||||
rkey?: string
|
rkey?: string
|
||||||
service?: string
|
service?: string
|
||||||
@@ -81,6 +81,18 @@ export function parseRoute(): Route {
|
|||||||
return { type: 'vrm', handle: vrmMatch[1] }
|
return { type: 'vrm', handle: vrmMatch[1] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note detail: /@handle/at/note/{rkey}
|
||||||
|
const noteDetailMatch = path.match(/^\/@([^/]+)\/at\/note\/([^/]+)$/)
|
||||||
|
if (noteDetailMatch) {
|
||||||
|
return { type: 'note-detail', handle: noteDetailMatch[1], rkey: noteDetailMatch[2] }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note list: /@handle/at/note
|
||||||
|
const noteMatch = path.match(/^\/@([^/]+)\/at\/note\/?$/)
|
||||||
|
if (noteMatch) {
|
||||||
|
return { type: 'note', handle: noteMatch[1] }
|
||||||
|
}
|
||||||
|
|
||||||
// Chat edit: /@handle/at/chat/{type}/{rkey}/edit
|
// Chat edit: /@handle/at/chat/{type}/{rkey}/edit
|
||||||
const chatEditMatch = path.match(/^\/@([^/]+)\/at\/chat\/([^/]+)\/([^/]+)\/edit$/)
|
const chatEditMatch = path.match(/^\/@([^/]+)\/at\/chat\/([^/]+)\/([^/]+)\/edit$/)
|
||||||
if (chatEditMatch) {
|
if (chatEditMatch) {
|
||||||
@@ -141,6 +153,10 @@ export function navigate(route: Route): void {
|
|||||||
path = `/@${route.handle}/at/link`
|
path = `/@${route.handle}/at/link`
|
||||||
} else if (route.type === 'vrm' && route.handle) {
|
} else if (route.type === 'vrm' && route.handle) {
|
||||||
path = `/@${route.handle}/at/vrm`
|
path = `/@${route.handle}/at/vrm`
|
||||||
|
} else if (route.type === 'note' && route.handle) {
|
||||||
|
path = `/@${route.handle}/at/note`
|
||||||
|
} else if (route.type === 'note-detail' && route.handle && route.rkey) {
|
||||||
|
path = `/@${route.handle}/at/note/${route.rkey}`
|
||||||
}
|
}
|
||||||
|
|
||||||
window.history.pushState({}, '', path)
|
window.history.pushState({}, '', path)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { renderRsePage } from './components/rse'
|
|||||||
import { renderLinkPage, renderLinkButtons } from './components/link'
|
import { renderLinkPage, renderLinkButtons } from './components/link'
|
||||||
import { renderVrmPage, setupVrmPage } from './components/vrm'
|
import { renderVrmPage, setupVrmPage } from './components/vrm'
|
||||||
import { checkMigrationStatus, renderMigrationPage, setupMigrationButton } from './components/card-migrate'
|
import { checkMigrationStatus, renderMigrationPage, setupMigrationButton } from './components/card-migrate'
|
||||||
|
import { renderNoteListPage, renderNoteDetailPage, setupNoteDetail } from './components/note'
|
||||||
import { showLoading, hideLoading } from './components/loading'
|
import { showLoading, hideLoading } from './components/loading'
|
||||||
|
|
||||||
const app = document.getElementById('app')!
|
const app = document.getElementById('app')!
|
||||||
@@ -185,6 +186,7 @@ async function render(route: Route): Promise<void> {
|
|||||||
const activeTab = route.type === 'postpage' ? 'post' :
|
const activeTab = route.type === 'postpage' ? 'post' :
|
||||||
(route.type === 'chat' || route.type === 'chat-thread' || route.type === 'chat-edit') ? 'chat' :
|
(route.type === 'chat' || route.type === 'chat-thread' || route.type === 'chat-edit') ? 'chat' :
|
||||||
route.type === 'link' ? 'link' :
|
route.type === 'link' ? 'link' :
|
||||||
|
(route.type === 'note' || route.type === 'note-detail') ? 'note' :
|
||||||
(route.type === 'atbrowser' || route.type === 'service' || route.type === 'collection' || route.type === 'record' ? 'browser' : 'blog')
|
(route.type === 'atbrowser' || route.type === 'service' || route.type === 'collection' || route.type === 'record' ? 'browser' : 'blog')
|
||||||
html += renderModeTabs(handle, activeTab, localOnly)
|
html += renderModeTabs(handle, activeTab, localOnly)
|
||||||
|
|
||||||
@@ -290,6 +292,24 @@ async function render(route: Route): Promise<void> {
|
|||||||
html += `<div id="content">${renderVrmPage(vrmData, handle)}</div>`
|
html += `<div id="content">${renderVrmPage(vrmData, handle)}</div>`
|
||||||
html += `<nav class="back-nav"><a href="/@${handle}">${handle}</a></nav>`
|
html += `<nav class="back-nav"><a href="/@${handle}">${handle}</a></nav>`
|
||||||
|
|
||||||
|
} else if (route.type === 'note') {
|
||||||
|
// Note list page
|
||||||
|
const noteCollection = 'ai.syui.note.post'
|
||||||
|
const notePosts = await getPosts(did, noteCollection, localOnly)
|
||||||
|
html += `<div id="content">${renderNoteListPage(notePosts, handle)}</div>`
|
||||||
|
html += `<nav class="back-nav"><a href="/@${handle}">${handle}</a></nav>`
|
||||||
|
|
||||||
|
} else if (route.type === 'note-detail' && route.rkey) {
|
||||||
|
// Note detail page
|
||||||
|
const noteCollection = 'ai.syui.note.post'
|
||||||
|
const notePost = await getPost(did, noteCollection, route.rkey, localOnly)
|
||||||
|
if (notePost) {
|
||||||
|
html += `<div id="content">${renderNoteDetailPage(notePost, handle, localOnly)}</div>`
|
||||||
|
} else {
|
||||||
|
html += `<div id="content" class="error">Note not found</div>`
|
||||||
|
}
|
||||||
|
html += `<nav class="back-nav"><a href="/@${handle}/at/note">note</a></nav>`
|
||||||
|
|
||||||
} else if (route.type === 'chat') {
|
} else if (route.type === 'chat') {
|
||||||
// Chat list page - show all chat collections
|
// Chat list page - show all chat collections
|
||||||
if (!config.bot) {
|
if (!config.bot) {
|
||||||
@@ -464,6 +484,15 @@ async function render(route: Route): Promise<void> {
|
|||||||
setupRecordMerge()
|
setupRecordMerge()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup note detail page
|
||||||
|
if (route.type === 'note-detail' && route.rkey) {
|
||||||
|
const noteCollection = 'ai.syui.note.post'
|
||||||
|
const notePost = await getPost(did, noteCollection, route.rkey, localOnly)
|
||||||
|
if (notePost && localOnly) {
|
||||||
|
setupNoteDetail(notePost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Setup VRM page audio controls
|
// Setup VRM page audio controls
|
||||||
if (route.type === 'vrm') {
|
if (route.type === 'vrm') {
|
||||||
setupVrmPage()
|
setupVrmPage()
|
||||||
|
|||||||
@@ -3053,3 +3053,44 @@ button.tab {
|
|||||||
border-top-color: #444;
|
border-top-color: #444;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Note styles */
|
||||||
|
.note-list { margin: 1rem 0; }
|
||||||
|
.note-item { padding: 0.5rem 0; border-bottom: 1px solid #eee; }
|
||||||
|
.note-link { display: flex; gap: 1rem; text-decoration: none; color: inherit; }
|
||||||
|
.note-link:hover { opacity: 0.7; }
|
||||||
|
.note-date { color: #888; font-size: 0.85rem; white-space: nowrap; }
|
||||||
|
.note-title { font-weight: 500; }
|
||||||
|
.note-tags { margin-top: 0.25rem; }
|
||||||
|
.note-tag { font-size: 0.75rem; color: #666; background: #f0f0f0; padding: 0.1rem 0.4rem; border-radius: 3px; margin-right: 0.25rem; }
|
||||||
|
.note-empty { color: #888; padding: 2rem 0; text-align: center; }
|
||||||
|
|
||||||
|
.note-detail { margin: 1rem 0; }
|
||||||
|
.note-detail-title { font-size: 1.4rem; margin-bottom: 0.25rem; }
|
||||||
|
.note-detail-meta { color: #888; font-size: 0.85rem; margin-bottom: 1.5rem; }
|
||||||
|
.note-section { margin-bottom: 1.5rem; }
|
||||||
|
.note-section-label { font-size: 0.8rem; color: #999; border-bottom: 1px solid #eee; padding-bottom: 0.25rem; margin-bottom: 0.75rem; }
|
||||||
|
.note-content { line-height: 1.8; }
|
||||||
|
.note-paywall { text-align: center; color: #c0a060; margin: 2rem 0; font-size: 0.9rem; }
|
||||||
|
|
||||||
|
.note-actions { display: flex; gap: 0.5rem; margin: 1rem 0; flex-wrap: wrap; align-items: center; }
|
||||||
|
.note-copy-btn { font-size: 0.8rem; padding: 0.3rem 0.8rem; background: #f5f5f5; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; }
|
||||||
|
.note-copy-btn:hover { background: #e8e8e8; }
|
||||||
|
.note-copy-status { font-size: 0.8rem; color: #4a4; }
|
||||||
|
|
||||||
|
.note-edit { margin: 1rem 0; }
|
||||||
|
.note-edit-input { width: 100%; padding: 0.5rem; font-size: 1rem; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 0.5rem; box-sizing: border-box; }
|
||||||
|
.note-edit-label { display: block; font-size: 0.8rem; color: #888; margin: 0.75rem 0 0.25rem; }
|
||||||
|
.note-edit-textarea { width: 100%; padding: 0.5rem; font-family: monospace; font-size: 0.85rem; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
|
||||||
|
.note-edit-actions { display: flex; gap: 0.5rem; margin-top: 0.75rem; }
|
||||||
|
.note-edit-actions button { padding: 0.3rem 1rem; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; }
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.note-item { border-bottom-color: #333; }
|
||||||
|
.note-tag { background: #333; color: #aaa; }
|
||||||
|
.note-section-label { border-bottom-color: #333; }
|
||||||
|
.note-copy-btn { background: #2a2a2a; border-color: #444; color: #ccc; }
|
||||||
|
.note-copy-btn:hover { background: #333; }
|
||||||
|
.note-edit-input, .note-edit-textarea { background: #1a1a1a; color: #eee; border-color: #444; }
|
||||||
|
.note-edit-actions button { background: #2a2a2a; border-color: #444; color: #ccc; }
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user