// ==UserScript== // @name AI Multi-Window Chat // @name:zh-CN AI 多窗口对话 // @namespace http://tampermonkey.net/ // @version 1.0.0 // @description Multi-window AI assistant using OpenAI-compatible APIs // @description:zh-CN 支持多窗口对话的AI助手,使用OpenAI兼容接口 // @author wenrizc // @license MIT // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @connect * // @run-at document-end // @downloadURL https://update.greasyfork.icu/scripts/562658/AI%20Multi-Window%20Chat.user.js // @updateURL https://update.greasyfork.icu/scripts/562658/AI%20Multi-Window%20Chat.meta.js // ==/UserScript== (function() { 'use strict'; // ===== i18n ===== const i18n = { en: { extName: "AI Multi-Window Chat", popup__title: "AI Assistant", popup__tabConfig: "Config", popup__tabHistory: "History", popup__tabPrompts: "Prompts", popup__sectionApiConfig: "API Configuration", popup__labelApiUrl: "API URL", popup__labelApiKey: "API Key", popup__labelModelName: "Model Name", popup__labelProfileName: "Name", popup__placeholderProfileName: "Profile name", popup__labelActiveProfile: "Default", popup__placeholderApiUrl: "https://api.openai.com/v1", popup__placeholderApiKey: "", popup__placeholderModelName: "", popup__btnSaveConfig: "Save Config", popup__btnAddProfile: "New", popup__btnSaveProfile: "Save", popup__btnDeleteProfile: "Delete", popup__btnSetActive: "Set Current", popup__btnTestConnection: "Test", popup__btnShowKey: "Show", popup__btnHideKey: "Hide", popup__btnCopyKey: "Copy", popup__statusSaved: "Configuration saved", popup__statusError: "Save failed, please check your input", popup__statusDeleted: "Profile deleted", popup__statusCopied: "API key copied", popup__statusTestSuccess: "Connection OK", popup__statusTestFailed: "Connection failed: $1$", popup__statusTestTimeout: "Timeout", popup__statusSetActive: "Current profile updated", popup__confirmDeleteProfile: 'Delete profile "$1$"?', popup__profileNameEmpty: "Untitled", popup__defaultProfileName: "Profile $1$", popup__presetOpenAI: "OpenAI", popup__presetDeepSeek: "DeepSeek", popup__presetQwen: "Qwen", chat__welcome: "Start Chatting", chat__welcomeHint: "Enter a question to start chatting with AI", chat__placeholderInput: "Enter your question...", chat__btnSend: "Send", chat__thinking: "AI is thinking...", chat__windowTitle: "Window $1$", chat__roleUser: "User", chat__roleAI: "AI", content__toolbarChat: "Discuss in a new chat window", content__toolbarBtnChat: "AI Chat", content__btnMinimize: "Minimize", content__btnClose: "Close", common__loading: "Loading...", common__error: "Error", common__success: "Success", common__cancel: "Cancel", common__confirm: "Confirm", prompt__empty: "No prompts yet", prompt__labelName: "Name", prompt__labelContent: "Content", prompt__placeholderName: "Prompt name", prompt__placeholderContent: "Enter prompt content...", prompt__btnAdd: "New", prompt__btnSave: "Save", prompt__btnDelete: "Delete", prompt__defaultLabel: "Default", prompt__noPrompt: "No Prompt", prompt__statusSaved: "Prompt saved", prompt__statusDeleted: "Prompt deleted", prompt__statusDefaultSet: "Default prompt updated", prompt__confirmDelete: 'Delete prompt "$1$"?', prompt__defaultName: "Prompt $1$", history__title: "Chat History", history__empty: "No chat history yet", history__btnExportAll: "Export All", history__btnClearHistory: "Clear History", history__btnView: "View", history__btnExport: "Export", history__btnDelete: "Delete", history__messageCount: "$1$ messages", history__confirmDeleteAll: "Are you sure you want to clear all history?", history__confirmDeleteItem: 'Are you sure you want to delete chat "$1$"?', history__successDeleted: "Chat deleted", history__errorDeleted: "Delete failed", history__successCleared: "History cleared", history__successExported: "Export successful", history__errorExported: "Export failed", error__apiConfigMissing: "API config missing. Please add a profile and set it as current.", error__emptyApiResponse: "Empty response from API. Please try again.", config__btnExport: "Export", config__btnImport: "Import", config__errorNoProfiles: "No profiles to export", config__errorNoPrompts: "No prompts to export", config__errorInvalidFormat: "Invalid file format", config__statusExported: "Exported $1$ items", config__statusImported: "Added $1$, updated $2$ items", menu__openSettings: "⚙️ Open Settings" }, zh: { extName: "AI 多窗口对话", popup__title: "AI 助手", popup__tabConfig: "配置", popup__tabHistory: "历史记录", popup__tabPrompts: "提示词", popup__sectionApiConfig: "API 配置", popup__labelApiUrl: "API 地址", popup__labelApiKey: "API 密钥", popup__labelModelName: "模型名称", popup__labelProfileName: "名称", popup__placeholderProfileName: "配置名称", popup__labelActiveProfile: "默认", popup__placeholderApiUrl: "https://api.openai.com/v1", popup__placeholderApiKey: "", popup__placeholderModelName: "", popup__btnSaveConfig: "保存配置", popup__btnAddProfile: "新增", popup__btnSaveProfile: "保存", popup__btnDeleteProfile: "删除", popup__btnSetActive: "设为当前", popup__btnTestConnection: "测试连接", popup__btnShowKey: "显示", popup__btnHideKey: "隐藏", popup__btnCopyKey: "复制", popup__statusSaved: "配置已保存", popup__statusError: "保存失败,请检查输入", popup__statusDeleted: "记录已删除", popup__statusCopied: "密钥已复制", popup__statusTestSuccess: "连接成功", popup__statusTestFailed: "连接失败:$1$", popup__statusTestTimeout: "超时", popup__statusSetActive: "已设为当前", popup__confirmDeleteProfile: '确定要删除 "$1$" 吗?', popup__profileNameEmpty: "未命名", popup__defaultProfileName: "配置 $1$", popup__presetOpenAI: "OpenAI", popup__presetDeepSeek: "DeepSeek", popup__presetQwen: "Qwen", chat__welcome: "开始对话", chat__welcomeHint: "输入问题,开始与 AI 对话", chat__placeholderInput: "输入你的问题...", chat__btnSend: "发送", chat__thinking: "AI 正在思考...", chat__windowTitle: "窗口 $1$", chat__roleUser: "用户", chat__roleAI: "AI", content__toolbarChat: "在新的对话窗口中讨论", content__toolbarBtnChat: "AI对话", content__btnMinimize: "最小化", content__btnClose: "关闭", common__loading: "加载中...", common__error: "错误", common__success: "成功", common__cancel: "取消", common__confirm: "确定", prompt__empty: "暂无提示词", prompt__labelName: "名称", prompt__labelContent: "内容", prompt__placeholderName: "提示词名称", prompt__placeholderContent: "输入提示词内容...", prompt__btnAdd: "新增", prompt__btnSave: "保存", prompt__btnDelete: "删除", prompt__defaultLabel: "默认", prompt__noPrompt: "无提示词", prompt__statusSaved: "提示词已保存", prompt__statusDeleted: "提示词已删除", prompt__statusDefaultSet: "已设为默认", prompt__confirmDelete: '确定要删除提示词「$1$」吗?', prompt__defaultName: "提示词 $1$", history__title: "对话历史", history__empty: "暂无历史对话", history__btnExportAll: "导出所有", history__btnClearHistory: "清空历史", history__btnView: "查看", history__btnExport: "导出", history__btnDelete: "删除", history__messageCount: "$1$ 条消息", history__confirmDeleteAll: "确定要清空所有历史记录吗?", history__confirmDeleteItem: '确定要删除对话"$1$"吗?', history__successDeleted: "对话已删除", history__errorDeleted: "删除失败", history__successCleared: "历史记录已清空", history__successExported: "导出成功", history__errorExported: "导出失败", error__apiConfigMissing: "API 配置缺失,请新增配置并设为当前。", error__emptyApiResponse: "API 返回空响应,请重试。", config__btnExport: "导出", config__btnImport: "导入", config__errorNoProfiles: "没有可导出的配置", config__errorNoPrompts: "没有可导出的提示词", config__errorInvalidFormat: "无效的文件格式", config__statusExported: "已导出 $1$ 项", config__statusImported: "已添加 $1$ 项,更新 $2$ 项", menu__openSettings: "⚙️ 打开设置" } }; // Detect browser language const browserLang = navigator.language || navigator.userLanguage; const locale = browserLang.startsWith('zh') ? 'zh' : 'en'; const messages = i18n[locale] || i18n.en; // Translation function function t(key, substitutions = []) { let result = messages[key] || key; if (substitutions) { if (typeof substitutions === 'object' && !Array.isArray(substitutions)) { for (const [name, value] of Object.entries(substitutions)) { result = result.replace(new RegExp(`\\$${name}\\$`, 'g'), value); } } else { if (!Array.isArray(substitutions)) { substitutions = [substitutions]; } substitutions.forEach((value, index) => { result = result.replace(new RegExp(`\\$${index + 1}\\$`, 'g'), value); }); } } return result; } // ===== CSS ===== const css = ` .ai-selection-toolbar { position: absolute; z-index: 2147483647; display: none; gap: 4px; padding: 8px; background: #ffffff; border: 1px solid #e5e5e5; border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); animation: toolbarFadeIn 0.2s ease-out; } @keyframes toolbarFadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } .ai-toolbar-btn { display: flex; align-items: center; gap: 6px; padding: 8px 14px; border: none; border-radius: 8px; background: #fafafa; color: #262626; font-size: 13px; font-weight: 500; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; cursor: pointer; transition: all 0.2s; white-space: nowrap; } .ai-toolbar-btn:hover { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #ffffff; transform: translateY(-1px); box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); } .ai-multi-window { position: fixed; z-index: 2147483646; width: 450px; height: 600px; min-width: 350px; min-height: 400px; background: #ffffff; border: 1px solid #e5e5e5; border-radius: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); display: flex; flex-direction: column; overflow: visible; animation: windowFadeIn 0.3s ease-out; } @keyframes windowFadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } } .ai-multi-window.minimized { height: 48px !important; min-height: 48px !important; } .ai-multi-window.minimized .ai-window-content { display: none; } .ai-window-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: #fafafa; border-bottom: 1px solid #e5e5e5; cursor: move; user-select: none; } .ai-window-title { display: flex; flex-direction: column; gap: 2px; } .ai-window-number { font-size: 13px; font-weight: 600; color: #262626; cursor: text; } .ai-title-input { font-size: 13px; font-weight: 600; color: #262626; padding: 4px 8px; border: 2px solid #667eea; border-radius: 6px; outline: none; background: #ffffff; font-family: inherit; min-width: 200px; } .ai-title-input:focus { box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } .ai-window-controls { display: flex; gap: 4px; } .ai-window-btn { width: 28px; height: 28px; border: none; border-radius: 6px; background: transparent; color: #737373; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; } .ai-window-btn:hover { background: #e5e5e5; color: #262626; } .ai-close-btn:hover { background: #fee2e2; color: #dc2626; } .ai-window-content { flex: 1; overflow: hidden; display: flex; flex-direction: column; position: relative; } .ai-chat-iframe { width: 100%; height: 100%; border: none; display: block; } .ai-resize-handle { position: absolute; z-index: 1000; } .ai-resize-e { top: 0; right: -10px; width: 20px; height: 100%; cursor: e-resize; } .ai-resize-w { top: 0; left: -10px; width: 20px; height: 100%; cursor: w-resize; } .ai-resize-s { bottom: -10px; left: 0; width: 100%; height: 20px; cursor: s-resize; } .ai-resize-se { bottom: -10px; right: -10px; width: 24px; height: 24px; cursor: se-resize; z-index: 1001; } .ai-resize-sw { bottom: -10px; left: -10px; width: 24px; height: 24px; cursor: sw-resize; z-index: 1001; } .ai-settings-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 2147483647; width: 90%; max-width: 700px; max-height: 80vh; background: #ffffff; border-radius: 12px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); display: none; flex-direction: column; overflow: hidden; } .ai-settings-panel.show { display: flex; } .ai-settings-header { padding: 16px 20px; border-bottom: 1px solid #e5e5e5; display: flex; align-items: center; justify-content: space-between; background: #fafafa; } .ai-settings-title { font-size: 18px; font-weight: 600; color: #262626; } .ai-settings-close { background: none; border: none; font-size: 24px; color: #737373; cursor: pointer; padding: 0; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border-radius: 6px; transition: all 0.2s; } .ai-settings-close:hover { background: #f3f4f6; color: #262626; } .ai-settings-tabs { display: flex; border-bottom: 1px solid #e5e5e5; background: #ffffff; } .ai-settings-tab { flex: 1; padding: 12px 16px; text-align: center; background: transparent; border: none; border-bottom: 2px solid transparent; cursor: pointer; font-size: 13px; font-weight: 600; color: #737373; transition: color 0.2s, border-color 0.2s; } .ai-settings-tab:hover { color: #667eea; } .ai-settings-tab.active { color: #667eea; border-bottom-color: #667eea; } .ai-settings-body { flex: 1; overflow-y: auto; padding: 16px; } .ai-settings-tab-content { display: none; } .ai-settings-tab-content.active { display: block; } .ai-config-grid { display: grid; grid-template-columns: 160px 1fr; gap: 16px; align-items: start; } .ai-profile-list { border: 1px solid #e5e5e5; border-radius: 12px; padding: 8px; background: #fafafa; height: 400px; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; } .ai-profile-list.empty { align-items: center; justify-content: center; color: #9ca3af; font-size: 12px; } .ai-profile-item { display: flex; align-items: center; gap: 8px; width: 100%; padding: 8px 10px; border: 1px solid transparent; border-radius: 8px; background: transparent; text-align: left; cursor: pointer; color: #1a1a1a; font-size: 13px; } .ai-profile-item:hover { background: #f3f4f6; } .ai-profile-item.active { background: #ffffff; border-color: #667eea; box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.12); } .ai-profile-dot { width: 8px; height: 8px; border-radius: 50%; border: 1px solid #cbd5f5; background: transparent; flex-shrink: 0; } .ai-profile-item.current .ai-profile-dot { background: #667eea; border-color: #667eea; } .ai-profile-name { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .ai-profile-details { border: 1px solid #e5e5e5; border-radius: 12px; padding: 16px; background: #ffffff; min-height: 400px; } .ai-input-group { margin-bottom: 14px; } .ai-input-group label { display: block; margin-bottom: 6px; font-size: 12px; font-weight: 600; color: #4a4a4a; } .ai-input-group input, .ai-input-group select, .ai-input-group textarea { width: 100%; padding: 9px 12px; border: 1px solid #e5e5e5; border-radius: 8px; font-size: 13px; font-family: inherit; transition: border-color 0.2s, box-shadow 0.2s; background: #ffffff; } .ai-input-group textarea { resize: vertical; min-height: 80px; line-height: 1.5; } .ai-input-group input:focus, .ai-input-group select:focus, .ai-input-group textarea:focus { outline: none; border-color: #667eea; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } .ai-inline-actions { display: flex; align-items: center; gap: 8px; } .ai-inline-actions input { flex: 1; } .ai-btn { padding: 8px 12px; border: none; border-radius: 8px; font-size: 13px; font-weight: 600; font-family: inherit; cursor: pointer; transition: background 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s; } .ai-btn-primary { background: #667eea; color: #ffffff; } .ai-btn-primary:hover { background: #5a6fe0; } .ai-btn-secondary { background: #ffffff; color: #1a1a1a; border: 1px solid #e5e5e5; } .ai-btn-secondary:hover { border-color: #667eea; color: #667eea; } .ai-btn-danger { background: #ffffff; color: #dc2626; border: 1px solid #fecaca; } .ai-btn-danger:hover { background: #fef2f2; border-color: #dc2626; } .ai-btn:disabled { background: #e5e7eb; color: #9ca3af; border-color: #e5e7eb; cursor: not-allowed; box-shadow: none; } .ai-btn-small { padding: 6px 10px; font-size: 12px; } .ai-btn-inline { padding: 8px 10px; font-size: 12px; } .ai-config-toolbar { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; } .ai-config-toolbar-right { margin-left: auto; display: flex; align-items: center; gap: 8px; font-size: 12px; color: #737373; } .ai-status { padding: 10px 12px; border-radius: 8px; font-size: 12px; margin-bottom: 12px; display: none; } .ai-status.show { display: block; } .ai-status.success { background: #f0fdf4; color: #16a34a; border: 1px solid #bbf7d0; } .ai-status.error { background: #fef2f2; color: #dc2626; border: 1px solid #fad2cf; } .ai-preset-buttons { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; } .ai-preset-btn { padding: 4px 8px; font-size: 11px; background: #f3f4f6; border: 1px solid #e5e5e5; border-radius: 6px; cursor: pointer; transition: background 0.2s, border-color 0.2s; } .ai-preset-btn:hover { background: #e5e7eb; border-color: #d1d5db; } .ai-action-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; justify-content: flex-end; } .ai-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 2147483646; display: none; } .ai-overlay.show { display: block; } /* Chat Window Styles */ .ai-chat-container { display: flex; flex-direction: column; height: 100%; background: #ffffff; } .ai-messages-container { flex: 1; overflow-y: auto; padding: 20px; background: #ffffff; } .ai-messages-container::-webkit-scrollbar { width: 6px; } .ai-messages-container::-webkit-scrollbar-track { background: #f5f5f5; } .ai-messages-container::-webkit-scrollbar-thumb { background: #d4d4d4; border-radius: 3px; } .ai-welcome-message { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #737373; text-align: center; } .ai-welcome-icon { font-size: 48px; margin-bottom: 16px; opacity: 0.6; } .ai-welcome-text { font-size: 18px; font-weight: 600; color: #262626; margin-bottom: 8px; } .ai-welcome-hint { font-size: 13px; color: #a3a3a3; } .ai-message { display: flex; flex-direction: column; gap: 8px; padding: 20px; margin-bottom: 16px; border: 1px solid #e5e5e5; border-radius: 12px; background: #ffffff; animation: messageIn 0.3s ease-out; } @keyframes messageIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .ai-message-header { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 600; color: #737373; } .ai-message-avatar { font-size: 16px; line-height: 1; } .ai-message-role { color: #262626; } .ai-message-content { padding: 0; max-width: 100%; word-wrap: break-word; white-space: pre-wrap; color: #262626; line-height: 1.8; font-size: 14px; } .ai-message-content code { background: rgba(0, 0, 0, 0.05); padding: 2px 6px; border-radius: 4px; font-family: 'Consolas', 'Monaco', monospace; font-size: 13px; } .ai-prompt-selector { padding: 8px 20px; background: #fafafa; border-top: 1px solid #e5e5e5; } .ai-prompt-selector select { width: 100%; padding: 8px 12px; border: 1px solid #e5e5e5; border-radius: 6px; font-size: 13px; font-family: inherit; background: #ffffff; color: #1a1a1a; cursor: pointer; } .ai-input-container { display: flex; gap: 8px; padding: 16px 20px; background: #ffffff; border-top: 1px solid #e5e5e5; } .ai-message-input { flex: 1; padding: 10px 14px; border: 1px solid #e5e5e5; border-radius: 8px; font-size: 14px; font-family: inherit; resize: none; outline: none; background: #fafafa; transition: all 0.2s; max-height: 120px; overflow-y: auto; } .ai-message-input:focus { border-color: #667eea; background: #ffffff; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } .ai-send-btn { flex-shrink: 0; width: 40px; height: 40px; border: none; border-radius: 8px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #ffffff; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; } .ai-send-btn:hover { transform: scale(1.05); box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); } .ai-send-btn:active { transform: scale(0.95); } .ai-send-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } .ai-loading-indicator { display: flex; align-items: center; padding: 0 20px 12px; width: 100%; } .ai-loading-text { font-size: 13px; color: #737373; } @media (max-width: 600px) { .ai-multi-window { width: calc(100vw - 40px); height: 70vh; left: 20px !important; top: 80px !important; } .ai-selection-toolbar { padding: 6px; } .ai-toolbar-btn { padding: 6px 12px; font-size: 12px; } .ai-toolbar-btn span { display: none; } .ai-toolbar-btn svg { display: block; } .ai-config-grid { grid-template-columns: 1fr; } .ai-profile-list { height: 200px; } } `; // Inject CSS const styleEl = document.createElement('style'); styleEl.textContent = css; document.head.appendChild(styleEl); // ===== Storage Helpers ===== const Storage = { get(key) { const value = GM_getValue(key); return value ? JSON.parse(value) : null; }, set(key, value) { GM_setValue(key, JSON.stringify(value)); }, delete(key) { GM_deleteValue(key); } }; // ===== Storage Keys ===== const STORAGE_KEYS = { profiles: 'apiProfiles', activeProfileId: 'activeApiProfileId', prompts: 'system_prompts', defaultPromptId: 'default_system_prompt_id', chatHistory: 'chat_history' }; // ===== Utility Functions ===== function generateId() { return `id-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function renderMarkdown(content) { if (!content) return ''; // Escape HTML first let html = escapeHtml(content); // Code blocks (must be processed before other markdown) html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (match, lang, code) => { return `
${code}
`; }); // Inline code html = html.replace(/`([^`]+?)`/g, '$1'); // Bold html = html.replace(/\*\*([^*]+?)\*\*/g, '$1'); html = html.replace(/__([^_]+?)__/g, '$1'); // Italic html = html.replace(/\*([^*]+?)\*/g, '$1'); html = html.replace(/_([^_]+?)_/g, '$1'); // Strikethrough html = html.replace(/~~([^~]+?)~~/g, '$1'); // Headers html = html.replace(/^#### (.+)$/gm, '

$1

'); html = html.replace(/^### (.+)$/gm, '

$1

'); html = html.replace(/^## (.+)$/gm, '

$1

'); html = html.replace(/^# (.+)$/gm, '

$1

'); // Links html = html.replace(/\[([^\]]+?)\]\(([^\)]+?)\)/g, '$1'); // Unordered lists html = html.replace(/^[\*\-] (.+)$/gm, '
  • $1
  • '); html = html.replace(/(
  • .*<\/li>)/s, ''); // Ordered lists html = html.replace(/^\d+\. (.+)$/gm, '$1'); html = html.replace(/(.*<\/oli>)/s, '
      $1
    '.replace('', '
  • ').replace('', '
  • ')); // Blockquotes html = html.replace(/^> (.+)$/gm, '
    $1
    '); // Horizontal rules html = html.replace(/^---$/gm, '
    '); html = html.replace(/^\*\*\*$/gm, '
    '); // Line breaks and paragraphs html = html.replace(/\n\n/g, '

    '); html = html.replace(/\n/g, '
    '); // Wrap in paragraphs html = '

    ' + html + '

    '; // Clean up empty paragraphs html = html.replace(/

    <\/p>/g, ''); html = html.replace(/

    ()/g, '$1'); html = html.replace(/(<\/h[1-6]>)<\/p>/g, '$1'); html = html.replace(/

    (