Add standalone site builder using repository images (#1473)

This commit is contained in:
CyC2018
2026-10-28 01:42:39 +08:00
committed by GitHub
parent 851c086b93
commit 8412b98577
30 changed files with 1625 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
/.source-git/
/upstream/
/dist/
/.cache/
/.snapshot-*/
/.site-build-*/
/.update-check-*/
/source-version.json
/*-report.json
/*-validation.json
/validation.json
/*.log
/*.jpg
/*.png
__pycache__/
!/requirements.txt
+44
View File
@@ -0,0 +1,44 @@
# CS-Notes 静态网站构建
只读获取 GitHub 最新 master 的文章和图片,生成可独立部署的静态目录。文章和原图仍由仓库统一维护,构建不下载另一套图床备份,也不向 GitHub 写入数据。
## 生成与预览
需要 Python 3.12+。在仓库根目录运行:
```bash
python3 -m pip install -r website/requirements.txt
python3 website/refresh.py
python3 website/builder/serve.py
```
打开 http://127.0.0.1:8769/。预览服务只监听本机,只提供 website/dist 中的生成文件。
refresh.py 使用独立浅层 Git 缓存读取最新 master,并生成源码快照。缓存的推送地址已停用。临时目录内完成生成和校验后才替换 website/dist;缺图或站内链接校验失败时保留上一版产物。
源码缓存、upstream、dist、图片优化缓存、版本记录和验证报告均已忽略,不加入提交。
## 图片来源与发布
旧 COS 图片 URL 如果能在当前源码中唯一匹配原文件名,构建时就改用对应仓库文件;同名不唯一或没有匹配时保留原外链。当前内容的 246 个旧图床配图均能匹配,不复制另一套图片到源码中。
生成时只收集页面实际引用的文件。无损 WebP 经逐像素校验,只有能节省至少 10% 才使用;原图仍保留在生成目录,仓库原文件不改动。搜索索引在构建时生成,访客首次搜索只加载一个索引文件。
发布时应把整个 website/dist(或内容位于 ZIP 根目录的压缩包)上传到静态托管,网页和图片一起上传。访客从网站域名读取图片,无需另建 COS/TOS 图床或替换一套对象存储链接。
原文的头像、徽章、许可证图标和 GitHub 附件等 18 个外链继续沿用,依赖原服务可用性。构建生成的是静态文件;目前不包含每日定时配置、腾讯云上传代码或部署凭据。
## 验证
Node.js 22+ 用于搜索与更新回归检查。生成后,在仓库根目录运行:
```bash
python3 website/builder/check.py
python3 website/builder/test_site_assets.py
node website/builder/test_search.cjs
python3 website/builder/verify_updates.py
```
verify_updates.py 在临时副本中修改文章、替换配图、新增文章与图片,检查搜索索引更新,并模拟缺图、链接校验失败。测试结束自动清理副本,原始快照不改动。
页面模板保留手机分类导航、折叠目录、搜索和旧文章入口兼容;固定版本的浏览器依赖及许可证保存在 templates/vendor 中。
+228
View File
@@ -0,0 +1,228 @@
#!/usr/bin/env python3
"""Build the standalone CS-Notes website from the published Markdown source."""
from __future__ import annotations
import argparse
import hashlib
import html
import json
import posixpath
import re
import shutil
import tempfile
import urllib.parse
from pathlib import Path
from site_assets import copy_referenced_assets, optimize_images, write_manifest, write_search_index
from check import check
BASE = Path(__file__).resolve().parents[1]
ROOT = BASE / 'upstream'
SITE = BASE / 'templates'
DIST = BASE / 'dist'
CACHE = BASE / '.cache/site-assets'
VERSION_FILE = BASE / 'source-version.json'
SOURCE_COMMIT = json.loads(VERSION_FILE.read_text())['commit'] if VERSION_FILE.is_file() else 'unknown'
IMAGE_BY_NAME = {}
SOURCE_IMAGE_HOST = 'cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com'
IMAGE = re.compile(r'(<img\b[^>]*?\bsrc=["\'])([^"\']+)(["\'])', re.I)
MD_IMAGE = re.compile(r'(!\[[^\]]*\]\()((?:[^()\s]|\([^()\n]*\))*)(\))')
MD_LINK = re.compile(r'(?<!!)\[([^\]]+)\]\(([^\s)]+)\)')
HTML_LINK = re.compile(r'(<a\b[^>]*?\bhref=["\'])([^"\']+)(["\'])', re.I)
def sources():
return [ROOT / 'README.md', ROOT / 'CONTRIBUTING.md',
*sorted((ROOT / 'notes').glob('*.md')),
*sorted((ROOT / 'assets').glob('*.md'))]
def image_urls():
found = set()
for path in sources():
text = re.sub(r'```[^\n]*\n[\s\S]*?```|<!--[\s\S]*?-->', '', path.read_text())
for pattern in (IMAGE, MD_IMAGE):
for match in pattern.finditer(text):
url = html.unescape(match[2].strip())
if url.startswith(('https://', 'http://')):
found.add(url)
return sorted(found)
def repository_images():
result = {}
for directory in ('notes/pics', 'assets', 'docs/_media'):
for path in (ROOT / directory).rglob('*'):
if path.is_file():
result.setdefault(path.name, []).append(path)
return result
def local_target(url, source, html_link=False):
url = html.unescape(url)
parsed = urllib.parse.urlsplit(url)
path = urllib.parse.unquote(parsed.path)
if parsed.netloc.lower() in ('github.com', 'www.github.com'):
prefix = '/CyC2018/CS-Notes/blob/master/'
if path.startswith(prefix):
path = path[len(prefix):]
else:
return url
elif parsed.hostname == SOURCE_IMAGE_HOST:
candidates = IMAGE_BY_NAME.get(Path(path).name, [])
if len(candidates) != 1:
return url
path = candidates[0].relative_to(ROOT).as_posix()
elif parsed.netloc:
return url
elif not path or url.startswith(('#', 'mailto:', 'data:', '//')):
return url
elif not path.startswith('/'):
path = posixpath.normpath(str(source.relative_to(ROOT).parent / path))
path = path.lstrip('/')
if not (ROOT / path).resolve().is_relative_to(ROOT.resolve()) or not (ROOT / path).is_file():
return url
if path.endswith('.md'):
if not html_link:
result = '/' + urllib.parse.quote(path, safe='/')
if parsed.fragment:
result += '#' + urllib.parse.quote(urllib.parse.unquote(parsed.fragment), safe='')
return result
if path == 'README.md':
route = '/'
else:
route = '/' + urllib.parse.quote(path[:-3], safe='/')
if parsed.fragment:
route += '?id=' + urllib.parse.quote(urllib.parse.unquote(parsed.fragment), safe='')
return '/#' + route
return '/' + urllib.parse.quote(path, safe='/')
def render_markdown(path):
content = path.read_text()
def image(match):
url = html.unescape(match[2].strip())
parsed = urllib.parse.urlsplit(url)
if parsed.path and not parsed.scheme and not parsed.netloc:
candidate = (ROOT / urllib.parse.unquote(parsed.path).lstrip('/')) if parsed.path.startswith('/') else (path.parent / urllib.parse.unquote(parsed.path))
if not candidate.resolve().is_relative_to(ROOT.resolve()) or not candidate.is_file():
raise ValueError('Missing source image: ' + parsed.path)
url = local_target(url, path)
return match[1] + url + match[3]
def convert(part):
part = IMAGE.sub(image, part)
part = MD_IMAGE.sub(image, part)
part = MD_LINK.sub(lambda m: f'[{m[1]}]({local_target(m[2], path)})', part)
return HTML_LINK.sub(lambda m: m[1] + local_target(m[2], path, html_link=True) + m[3], part)
parts = re.split(r'(```[^\n]*\n[\s\S]*?```|<!--[\s\S]*?-->)', content)
return ''.join(part if i % 2 else convert(part) for i, part in enumerate(parts))
def build(optimize=True):
global IMAGE_BY_NAME
IMAGE_BY_NAME = repository_images()
external = image_urls()
replacements = {url: local_target(url, ROOT / 'README.md') for url in external}
reused = {url: path for url, path in replacements.items() if path.startswith('/')}
unchanged_external = [url for url, path in replacements.items() if not path.startswith('/')]
with tempfile.TemporaryDirectory(prefix='.site-build-', dir=DIST.parent) as temporary:
dist = Path(temporary) / 'dist'
dist.mkdir()
shutil.copytree(SITE / 'vendor', dist / 'vendor')
for name in ('index.html', 'site.css', 'site.js', 'search.js'):
shutil.copyfile(SITE / name, dist / name)
for path in sources():
target = dist / path.relative_to(ROOT)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(render_markdown(path))
homepage = (dist / 'README.md').read_text()
emojis = {'pencil2': '✏️', 'computer': '💻', 'cloud': '☁️', 'floppy_disk': '💾',
'coffee': '☕', 'bulb': '💡', 'art': '🎨', 'wrench': '🔧',
'watermelon': '🍉', 'memo': '📝'}
for name, symbol in emojis.items():
homepage = re.sub(r'^(## ):' + name + r':\s*', r'\1', homepage, flags=re.M)
homepage = homepage.replace('#' + name + '-', '#')
homepage = homepage.replace(':' + name + ':', symbol)
topics = [('算法', '✏️'), ('操作系统', '💻'), ('网络', '☁️'), ('面向对象', '🎨'),
('数据库', '💾'), ('Java', '☕'), ('系统设计', '💡'), ('工具', '🔧'),
('编码实践', '🍉'), ('后记', '📝')]
cards = '<nav class="topic-grid" aria-label="笔记分类">' + ''.join(
f'<a href="/#/?id={urllib.parse.quote(label.lower())}"><span aria-hidden="true">{symbol}</span>{label}</a>'
for label, symbol in topics) + '</nav>'
homepage = re.sub(r'^\| 算法[^\n]*\n\|[^\n]*\n\|[^\n]*', cards, homepage, count=1, flags=re.M)
homepage = homepage.replace('width="200px"', 'class="site-logo" width="200px"', 1)
(dist / 'README.md').write_text('# CS-Notes\n\n' + homepage)
note_paths = ['/' + str(p.relative_to(ROOT).with_suffix('')) for p in sorted((ROOT / 'notes').glob('*.md'))]
sidebar = ['- [首页](/)', '- [面试高频问题](/notes/面试高频问题)', '']
for line in homepage.splitlines():
if line.startswith('## '):
label = re.sub(r':[a-z0-9_]+:', '', line[3:]).strip()
if label == '后记':
break
sidebar.extend(['', '- **' + label + '**'])
elif line.startswith('- ['):
sidebar.append(' ' + line)
(dist / '_sidebar.md').write_text('\n'.join(sidebar) + '\n')
(dist / '_404.md').write_text('# 页面暂未找到\n\n请从目录或搜索查找笔记。\n\n[返回首页](/)\n')
(dist / '.nojekyll').touch()
shutil.copyfile(ROOT / 'docs/_media/LogoMakr_1J56bI.png', dist / 'favicon.png')
copy_referenced_assets(dist, ROOT)
image_report = optimize_images(dist, CACHE) if optimize else {'converted': 0}
index_name, search_report = write_search_index(dist, sorted((dist / 'notes').glob('*.md')))
legacy = json.loads((SITE / 'legacy-routes.json').read_text())
(dist / 'search-paths.js').write_text(
'window.CS_NOTES_PATHS = ' + json.dumps(note_paths, ensure_ascii=False) + ';\n'
'window.CS_NOTES_LEGACY_ROUTES = ' + json.dumps(legacy, ensure_ascii=False, separators=(',', ':')) + ';\n'
'window.CS_NOTES_SEARCH_INDEX = "/' + index_name + '";\n')
generator = hashlib.sha256()
for resource in sorted([*Path(__file__).parent.glob('*.py'), *SITE.rglob('*')]):
if resource.is_file():
generator.update(resource.relative_to(BASE).as_posix().encode())
generator.update(resource.read_bytes())
build_info = {'sourceCommit': SOURCE_COMMIT, 'generatorHash': generator.hexdigest(), 'notes': len(note_paths),
'searchIndex': index_name, 'legacyRoutes': len(legacy)}
(dist / 'build-info.json').write_text(json.dumps(build_info, indent=2) + '\n')
entry = (dist / 'index.html').read_text()
for name in ('site.css', 'site.js', 'search.js', 'search-paths.js'):
digest = hashlib.sha256((dist / name).read_bytes()).hexdigest()[:12]
entry = entry.replace('/' + name + '"', '/' + name + '?v=' + digest + '"')
(dist / 'index.html').write_text(entry)
write_manifest(dist, build_info)
files = [p for p in dist.rglob('*') if p.is_file()]
report = {'notes': len(note_paths), 'files': len(files), 'bytes': sum(p.stat().st_size for p in files),
'oversized': [str(p.relative_to(dist)) for p in files if p.stat().st_size > 25*1024*1024],
'repositoryImagesUsedForOldUrls': len(reused), 'externalImages': len(unchanged_external), 'search': search_report, 'images': image_report, 'legacyRoutes': len(legacy)}
if report['oversized'] or len(files) > 20000:
raise ValueError('Static hosting quota exceeded')
validation = check(ROOT, dist)
if validation['errors']:
raise ValueError('Static site validation failed: ' + '; '.join(validation['errors']))
previous = Path(temporary) / 'previous'
if DIST.exists():
DIST.rename(previous)
try:
dist.rename(DIST)
except BaseException:
if previous.exists():
previous.rename(DIST)
raise
report.update({'sourceCommit': SOURCE_COMMIT, 'imageReplacements': reused, 'externalImageUrls': unchanged_external})
DIST.with_name(DIST.name + '-report.json').write_text(json.dumps(report, ensure_ascii=False, indent=2) + '\n')
print(json.dumps({k: v for k, v in report.items() if k not in ('imageReplacements', 'externalImageUrls')}, ensure_ascii=False, indent=2))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='仅在本机从最新上游源码生成静态站,不下载图片、不发布。')
parser.add_argument('--source', type=Path, default=ROOT)
parser.add_argument('--output', type=Path, default=DIST)
parser.add_argument('--source-commit', default=SOURCE_COMMIT)
parser.add_argument('--no-optimize-images', action='store_true')
args = parser.parse_args()
ROOT, DIST, SOURCE_COMMIT = args.source.resolve(), args.output.resolve(), args.source_commit
if not ROOT.is_dir():
parser.error("源码目录不存在,请先运行 website/refresh.py。")
if ROOT == DIST or ROOT.is_relative_to(DIST) or DIST.is_relative_to(ROOT):
parser.error('源码和输出目录必须相互独立。')
if not DIST.is_relative_to(BASE):
parser.error('本地验证输出必须保存在当前验证目录中。')
build(optimize=not args.no_optimize_images)
+80
View File
@@ -0,0 +1,80 @@
"""Check only local build output and its provenance in the GitHub snapshot."""
import argparse
import hashlib
import json
import re
from pathlib import Path
from urllib.parse import unquote, urlsplit
BASE = Path(__file__).resolve().parents[1]
def check(source, output):
manifest = json.loads((output / 'asset-manifest.json').read_text())
info = json.loads((output / 'build-info.json').read_text())
files = {p.relative_to(output).as_posix(): p for p in output.rglob('*') if p.is_file()}
errors = []
expected = {item['path']: item for item in manifest['files']}
if set(files) != set(expected) | {'asset-manifest.json'}:
errors.append('发布文件与清单不一致')
for name, record in expected.items():
path = output / name
if not path.is_file() or path.stat().st_size != record['bytes'] or hashlib.sha256(path.read_bytes()).hexdigest() != record['sha256']:
errors.append('文件清单校验失败:' + name)
references = 0
for document in output.rglob('*.md'):
content = re.sub(r'```[^\n]*\n[\s\S]*?```|<!--[\s\S]*?-->', '', document.read_text())
for match in re.finditer(r'''\]\((/[^\s)]+)\)|(?:src|href)=["'](/[^"']+)''', content):
url = match[1] or match[2]
if url.startswith(('/#', '//')):
continue
name = unquote(urlsplit(url).path).lstrip('/')
if not name:
continue
references += 1
if name not in files and name + '.md' not in files:
errors.append('缺失引用:' + name)
for name in re.findall(r'(?:src|href)="(/[^"#]+)"', (output / 'index.html').read_text()):
if name.split('?')[0].lstrip('/') not in files:
errors.append('缺失页面资源:' + name)
forbidden = [name for name in files if set(Path(name).parts) & {
'.git', '.cache', 'node_modules', 'builder', 'templates', 'upstream', 'remote',
'tools', 'backups', '__pycache__', 'CLAUDE.md', 'AGENTS.md', '.env',
}]
if forbidden:
errors.append('混入维护文件:' + ', '.join(forbidden))
images = 0
for name, path in files.items():
if name.startswith(('notes/pics/', 'assets/', 'docs/_media/')) and path.suffix.lower() in {'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico'}:
images += 1
original = source / name
if not original.is_file() or original.read_bytes() != path.read_bytes():
errors.append('图片与当前源码不一致:' + name)
index = json.loads((output / info['searchIndex']).read_text())
note_names = {p.stem for p in (source / 'notes').glob('*.md')}
if {Path(doc['path']).name for doc in index['documents']} != note_names:
errors.append('搜索索引未覆盖当前全部文章')
legacy = json.loads((BASE / 'templates/legacy-routes.json').read_text())
for target in legacy.values():
if not (output / (target.lstrip('/') + '.md')).is_file():
errors.append('旧入口目标缺失:' + target)
variants = json.loads((output / 'image-variants.json').read_text()) if (output / 'image-variants.json').exists() else {}
for name, variant in variants.items():
for path, key in [(output / name.lstrip('/'), 'sourceSha256'), (output / variant['path'].lstrip('/'), 'sha256')]:
if not path.is_file() or hashlib.sha256(path.read_bytes()).hexdigest() != variant[key]:
errors.append('图片优化清单不一致:' + name)
report = {'sourceCommit': info['sourceCommit'], 'notes': len(note_names),
'files': len(files), 'localReferences': references, 'repositoryImages': images,
'imageVariants': len(variants), 'legacyRoutes': len(legacy), 'errors': errors}
return report
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--source', type=Path, default=BASE / 'upstream')
parser.add_argument('--output', type=Path, default=BASE / 'dist')
args = parser.parse_args()
result = check(args.source.resolve(), args.output.resolve())
print(json.dumps(result, ensure_ascii=False, indent=2))
(BASE / 'validation.json').write_text(json.dumps(result, ensure_ascii=False, indent=2) + '\n')
raise SystemExit(bool(result['errors']))
+30
View File
@@ -0,0 +1,30 @@
"""Serve only generated files on the loopback interface."""
import argparse
import functools
import json
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import unquote, urlsplit
BASE = Path(__file__).resolve().parents[1]
LEGACY = json.loads((BASE / 'templates/legacy-routes.json').read_text())
class Handler(SimpleHTTPRequestHandler):
def send_head(self):
if unquote(urlsplit(self.path).path) in LEGACY and not Path(self.translate_path(self.path)).exists():
self.path = '/index.html'
return super().send_head()
parser = argparse.ArgumentParser()
parser.add_argument('--port', type=int, default=8769)
args = parser.parse_args()
server = ThreadingHTTPServer(('127.0.0.1', args.port), functools.partial(Handler, directory=str(BASE / 'dist')))
print(f'本地预览:http://127.0.0.1:{args.port}/', flush=True)
try:
server.serve_forever()
except KeyboardInterrupt:
pass
finally:
server.server_close()
+174
View File
@@ -0,0 +1,174 @@
"""Deterministic search, legacy routes and lossless delivery assets for the site."""
from __future__ import annotations
import concurrent.futures
import hashlib
import gzip
import html
import io
import json
import re
import shutil
import tempfile
import urllib.parse
from collections import Counter
from pathlib import Path
IMAGE_SUFFIXES = {'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico', '.avif'}
PUNCTUATION = re.compile(r'''[\u2000-\u206f\u2e00-\u2e7f\\'!"#$%&()*+,./:;<=>?@\[\]^`{|}~]''')
def plain_text(text):
text = re.sub(r'<!--[\s\S]*?-->', '', text)
text = re.sub(r'<(?:script|style)\b[^>]*>[\s\S]*?</(?:script|style)>', '', text, flags=re.I)
text = re.sub(r'<[^>]+>', ' ', text)
text = re.sub(r'!\[[^\]]*\]\([^\n]*?\)', ' ', text)
text = re.sub(r'\[([^\]]+)\]\([^\n]*?\)', r'\1', text)
text = re.sub(r'^\s*```[^\n]*$', '', text, flags=re.M)
text = text.replace('`', '').replace('**', '').replace('__', '')
return re.sub(r'\s+', ' ', html.unescape(text)).strip()
def heading_slug(title, counts):
# Match the pinned Docsify 4.13.1 slugger, including numeric and duplicate headings.
title = title.replace('{docsify-ignore}', '').replace('{docsify-ignore-all}', '')
slug = PUNCTUATION.sub('', re.sub(r'<[^>]+>', '', title.strip()).lower())
slug = re.sub(r'-+', '-', re.sub(r'\s', '-', slug))
slug = re.sub(r'^(\d)', r'_\1', slug)
count = counts[slug]
counts[slug] += 1
return slug + (f'-{count}' if count else '')
def search_document(path, content):
counts = Counter()
sections = []
current = {'title': path.stem, 'id': '', 'text': ''}
lines = []
fence = None
def finish():
current['text'] = plain_text('\n'.join(lines))
if current['text'] or current['title']:
sections.append(dict(current))
lines.clear()
for line in re.sub(r'<!--[\s\S]*?-->', '', content).splitlines():
marker = re.match(r'^\s*(`{3,}|~{3,})', line)
if marker:
if fence is None:
fence = marker[1][0]
elif marker[1][0] == fence:
fence = None
continue
heading = re.match(r'^(#{1,6})\s+(.+?)(?:\s+#+)?\s*$', line) if fence is None else None
if heading:
finish()
raw_title = heading[2]
current = {'title': plain_text(raw_title), 'id': heading_slug(raw_title, counts), 'text': ''}
elif not re.match(r'^\s*[-*+]\s+\[[^]]+\]\(#[^)]*\)\s*$', line):
lines.append(line)
finish()
return {'path': '/notes/' + path.stem, 'title': path.stem, 'sections': sections}
def write_search_index(dist, paths):
documents = [search_document(path, path.read_text()) for path in paths]
data = json.dumps({'version': 1, 'documents': documents}, ensure_ascii=False, separators=(',', ':')).encode()
name = 'search-index-' + hashlib.sha256(data).hexdigest()[:16] + '.json'
(dist / name).write_bytes(data)
return name, {'documents': len(documents), 'sections': sum(len(d['sections']) for d in documents),
'bytes': len(data), 'gzipBytes': len(gzip.compress(data, mtime=0))}
def referenced_paths(content):
content = re.sub(r'```[^\n]*\n[\s\S]*?```|<!--[\s\S]*?-->', '', content)
for match in re.finditer(r'''\]\((/[^\s)]+)\)|(?:src|href)=["'](/[^"']+)''', content):
url = match[1] or match[2]
if url.startswith(('/#', '//')):
continue
path = urllib.parse.unquote(urllib.parse.urlsplit(url).path).lstrip('/')
if path and '..' not in Path(path).parts:
yield path
def copy_referenced_assets(dist, root):
references = set()
for doc in dist.rglob('*.md'):
references.update(referenced_paths(doc.read_text()))
for relative in sorted(references):
destination = dist / relative
if destination.is_file() or Path(str(destination) + '.md').is_file() or destination.suffix == '.md':
continue
source = root / relative
if not source.resolve().is_relative_to(root.resolve()) or not source.is_file():
raise ValueError(f'Missing published resource: {relative}')
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source, destination)
def optimize_images(dist, cache):
"""Add pixel-identical WebP alternatives; retain each referenced original URL."""
from PIL import Image
cache.mkdir(parents=True, exist_ok=True)
candidates = [p for p in dist.rglob('*') if p.suffix.lower() in {'.png', '.jpg', '.jpeg'} and p.stat().st_size >= 4096]
def convert(path):
original = path.read_bytes()
digest = hashlib.sha256(original).hexdigest()
cached = cache / (digest + '.webp')
with Image.open(io.BytesIO(original)) as image:
if getattr(image, 'is_animated', False):
return None
rgba = image.convert('RGBA')
if cached.exists():
data = cached.read_bytes()
else:
stream = io.BytesIO()
rgba.save(stream, format='WEBP', lossless=True, exact=True, method=4)
data = stream.getvalue()
if len(data) >= len(original) * .9:
return None
with Image.open(io.BytesIO(data)) as decoded:
if decoded.convert('RGBA').tobytes() != rgba.tobytes():
raise ValueError(f'Lossless image verification failed: {path}')
if not cached.exists():
with tempfile.NamedTemporaryFile(dir=cache, delete=False) as temporary:
temporary.write(data)
temporary_path = Path(temporary.name)
temporary_path.replace(cached)
relative = 'optimized/' + digest[:24] + '.webp'
target = dist / relative
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(data)
return '/' + path.relative_to(dist).as_posix(), {
'path': '/' + relative, 'originalBytes': len(original), 'deliveryBytes': len(data),
'width': rgba.width, 'height': rgba.height, 'sourceSha256': digest,
'sha256': hashlib.sha256(data).hexdigest(), 'pixelIdentical': True,
}
mapping = {}
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
for result in pool.map(convert, candidates):
if result:
mapping[result[0]] = result[1]
image_pattern = re.compile(r'''(<img\b[^>]*?\bsrc=["'])([^"']+)(["'])|(!\[[^\]]*\]\()([^\s)]+)(\))''', re.I)
for path in dist.rglob('*.md'):
def replace(match):
before, url, after = (match[1], match[2], match[3]) if match[1] else (match[4], match[5], match[6])
return before + mapping.get(url, {}).get('path', url) + after
parts = re.split(r'(```[^\n]*\n[\s\S]*?```|<!--[\s\S]*?-->)', path.read_text())
path.write_text(''.join(part if i % 2 else image_pattern.sub(replace, part) for i, part in enumerate(parts)))
(dist / 'image-variants.json').write_text(json.dumps(mapping, ensure_ascii=False, indent=2) + '\n')
return {'converted': len(mapping), 'originalBytes': sum(v['originalBytes'] for v in mapping.values()),
'deliveryBytes': sum(v['deliveryBytes'] for v in mapping.values()), 'pixelIdentical': True}
def write_manifest(dist, build_info):
records = []
for path in sorted(p for p in dist.rglob('*') if p.is_file() and p.name != 'asset-manifest.json'):
data = path.read_bytes()
records.append({'path': path.relative_to(dist).as_posix(), 'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()})
manifest = {'version': 1, **build_info, 'files': records}
(dist / 'asset-manifest.json').write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + '\n')
return records
+15
View File
@@ -0,0 +1,15 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { search } = require('../templates/search.js');
const dist = process.argv[2] || path.join(__dirname, '../dist');
const info = JSON.parse(fs.readFileSync(path.join(dist, 'build-info.json')));
const index = JSON.parse(fs.readFileSync(path.join(dist, info.searchIndex)));
assert(search(index, '拥塞控制').some(r => r.path.includes('计算机网络 - 传输层') && r.id === 'tcp-拥塞控制'));
assert(search(index, '检索增强生成').some(r => r.path === '/notes/大模型应用' && r.id === '三、检索增强生成'));
assert(search(index, 'Token 分词').some(r => r.path === '/notes/大模型应用' && r.id === 'token-与分词'));
assert.equal(search(index, '').length, 0);
if (process.argv.includes('--updated')) {
assert(search(index, '本地同步验证示例词').some(r => r.id === '本地验证更新'));
}
console.log(`搜索与锚点验证通过:${index.documents.length} 篇文章。`);
+31
View File
@@ -0,0 +1,31 @@
"""Regression checks for generated search anchors and asset collection."""
import sys
import unittest
from collections import Counter
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from site_assets import heading_slug, referenced_paths, search_document
class SearchIndexTests(unittest.TestCase):
def test_docsify_numeric_duplicate_and_chinese_punctuation(self):
counts = Counter()
self.assertEqual(heading_slug('1. 慢开始与拥塞避免', counts), '_1-慢开始与拥塞避免')
self.assertEqual(heading_slug('三、检索增强生成', counts), '三、检索增强生成')
self.assertEqual(heading_slug('TCP 拥塞控制', counts), 'tcp-拥塞控制')
self.assertEqual(heading_slug('TCP 拥塞控制', counts), 'tcp-拥塞控制-1')
def test_headings_in_code_do_not_change_real_anchors(self):
doc = search_document(Path('示例.md'), '# 示例\n\n```python\n# TCP 拥塞控制\n```\n\n## TCP 拥塞控制\n慢开始\n')
sections = [s for s in doc['sections'] if s['id'] == 'tcp-拥塞控制']
self.assertEqual(len(sections), 1)
self.assertEqual(sections[0]['text'], '慢开始')
def test_comments_and_example_asset_urls_are_not_published(self):
content = '<!-- <img src="/secret.png"> -->\n```html\n<img src="/example.png">\n```\n<img src="/notes/pics/actual.png">\n[正文](/notes/示例.md)\n'
self.assertEqual(set(referenced_paths(content)), {'notes/pics/actual.png', 'notes/示例.md'})
if __name__ == '__main__':
unittest.main()
+84
View File
@@ -0,0 +1,84 @@
"""Exercise repeat builds, article/image updates, and failed-build preservation locally."""
import hashlib
import json
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from urllib.parse import unquote
from check import check
BASE = Path(__file__).resolve().parents[1]
SOURCE = BASE / 'upstream'
OUTPUT = BASE / 'dist'
def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def build(source, output, *extra):
return subprocess.run([sys.executable, str(BASE / 'builder/build.py'),
'--source', str(source), '--output', str(output), *extra], capture_output=True, text=True)
first_manifest = digest(OUTPUT / 'asset-manifest.json')
result = build(SOURCE, OUTPUT)
assert result.returncode == 0, result.stderr
assert digest(OUTPUT / 'asset-manifest.json') == first_manifest, '相同输入的两次构建不一致'
report = json.loads((BASE / 'dist-report.json').read_text())
url, image = next((url, path) for url, path in report['imageReplacements'].items()
if path.startswith('/notes/pics/') and path.endswith('.png'))
image_name = unquote(image).lstrip('/')
note = next(path for path in (SOURCE / 'notes').glob('*.md') if url in path.read_text())
note_name = note.relative_to(SOURCE)
original_note = digest(note)
original_image = digest(SOURCE / image_name)
with tempfile.TemporaryDirectory(prefix='.update-check-', dir=BASE) as temp:
root = Path(temp)
source, output = root / 'source', root / 'dist'
shutil.copytree(SOURCE, source)
with (source / note_name).open('a') as handle:
handle.write('\n\n## 本地验证更新\n\n本地同步验证示例词:这是未上传的更新测试。\n')
replacement = next(path for path in (SOURCE / 'notes/pics').glob('*.png') if digest(path) != original_image)
shutil.copyfile(replacement, source / image_name)
shutil.copyfile(replacement, source / 'notes/pics/local-validation-new.png')
(source / 'notes/本地新增验证.md').write_text('# 本地新增验证\n\n仅在测试副本中生成。\n\n![新图片](pics/local-validation-new.png)\n')
result = build(source, output, '--no-optimize-images', '--source-commit', 'local-update-check')
assert result.returncode == 0, result.stderr
assert '本地同步验证示例词' in (output / note_name).read_text()
assert digest(output / image_name) == digest(replacement)
assert digest(output / image_name) != original_image
assert (output / 'notes/本地新增验证.md').is_file()
assert (output / 'notes/pics/local-validation-new.png').is_file()
validation = check(source, output)
assert not validation['errors'], validation['errors']
subprocess.run(['node', str(BASE / 'builder/test_search.cjs'), str(output), '--updated'], check=True)
successful_manifest = digest(output / 'asset-manifest.json')
with (source / note_name).open('a') as handle:
handle.write('\n\n![故障模拟](pics/local-validation-missing.png)\n')
failed = build(source, output, '--no-optimize-images', '--source-commit', 'local-failure-check')
assert failed.returncode != 0, '缺少必须图片时构建应失败'
assert digest(output / 'asset-manifest.json') == successful_manifest, '失败的构建覆盖了上次产物'
text = (source / note_name).read_text()
(source / note_name).write_text(text.replace('![故障模拟](pics/local-validation-missing.png)',
'[故障模拟](/notes/local-validation-missing.md)'))
failed = build(source, output, '--no-optimize-images', '--source-commit', 'local-link-failure-check')
assert failed.returncode != 0, '站内链接校验失败时构建应失败'
assert digest(output / 'asset-manifest.json') == successful_manifest, '校验失败覆盖了上次产物'
assert digest(note) == original_note and digest(SOURCE / image_name) == original_image
result = {'sameInputRebuildIdentical': True, 'articleUpdatePickedUp': True,
'repositoryImageUpdatePickedUp': True, 'newArticleAndImageIncluded': True,
'updatedSearchIndexPassed': True, 'failedBuildPreservesPreviousOutput': True,
'failedValidationPreservesPreviousOutput': True,
'upstreamSnapshotUnchanged': True, 'testFixtureRemoved': True,
'sourceCommit': report['sourceCommit'], 'manifestSha256': first_manifest}
(BASE / 'update-validation.json').write_text(json.dumps(result, ensure_ascii=False, indent=2) + '\n')
print(json.dumps(result, ensure_ascii=False, indent=2))
+52
View File
@@ -0,0 +1,52 @@
"""Read the latest public master and build locally; no write operation to GitHub or cloud."""
import json
import subprocess
import sys
import tarfile
import tempfile
from pathlib import Path
BASE = Path(__file__).resolve().parent
CACHE = BASE / '.source-git'
REPOSITORY = 'https://github.com/CyC2018/CS-Notes.git'
def git(*args):
return subprocess.check_output(['git', '--git-dir=' + str(CACHE), *args], text=True).strip()
if not CACHE.exists():
subprocess.run(['git', 'init', '--bare', str(CACHE)], check=True, stdout=subprocess.DEVNULL)
git('remote', 'add', 'origin', REPOSITORY)
git('config', 'credential.helper', '')
git('remote', 'set-url', '--push', 'origin', 'disabled://local-validation-read-only')
subprocess.run(['git', '--git-dir=' + str(CACHE), 'fetch', '--depth=1', 'origin',
'+refs/heads/master:refs/heads/master'], check=True)
commit = git('rev-parse', 'refs/heads/master')
with tempfile.TemporaryDirectory(prefix='.snapshot-', dir=BASE) as temp:
snapshot = Path(temp) / 'source'
snapshot.mkdir()
process = subprocess.Popen(['git', '--git-dir=' + str(CACHE), 'archive', commit], stdout=subprocess.PIPE)
try:
with tarfile.open(fileobj=process.stdout, mode='r|') as archive:
archive.extractall(snapshot, filter='data')
finally:
process.stdout.close()
if process.wait():
raise RuntimeError('无法生成源码快照')
previous = Path(temp) / 'previous'
destination = BASE / 'upstream'
if destination.exists():
destination.rename(previous)
try:
snapshot.rename(destination)
except BaseException:
if previous.exists():
previous.rename(destination)
raise
(BASE / 'source-version.json').write_text(json.dumps({
'repository': REPOSITORY, 'ref': 'master', 'commit': commit,
}, indent=2) + '\n')
print('已读取 GitHub 最新主分支:' + commit, flush=True)
subprocess.run([sys.executable, str(BASE / 'builder/build.py')], check=True)
subprocess.run([sys.executable, str(BASE / 'builder/check.py')], check=True)
+1
View File
@@ -0,0 +1 @@
Pillow==11.3.0
+29
View File
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="CS-Notes:技术面试必备的计算机基础知识,涵盖算法、操作系统、计算机网络、数据库、Java 与系统设计。">
<meta name="theme-color" content="#42b983">
<title>CS-Notes · CyC2018</title>
<link rel="icon" href="/favicon.png">
<link rel="stylesheet" href="/vendor/vue.css">
<link rel="stylesheet" href="/site.css">
</head>
<body>
<div id="app">正在载入 CS-Notes…</div>
<noscript>请启用 JavaScript 阅读网站,或访问 <a href="https://github.com/CyC2018/CS-Notes">GitHub 仓库</a>。</noscript>
<script src="/search-paths.js"></script>
<script src="/site.js"></script>
<script src="/search.js"></script>
<script src="/vendor/docsify.min.js"></script>
<script src="/vendor/docsify-copy-code.min.js"></script>
<script src="/vendor/zoom-image.min.js"></script>
<script src="/vendor/prism-bash.min.js"></script>
<script src="/vendor/prism-c.min.js"></script>
<script src="/vendor/prism-java.min.js"></script>
<script src="/vendor/prism-sql.min.js"></script>
<script src="/vendor/prism-python.min.js"></script>
<script src="/vendor/prism-json.min.js"></script>
</body>
</html>
+506
View File
@@ -0,0 +1,506 @@
{
"/Java/Java IO": "/notes/Java IO",
"/Java/Java IO.html": "/notes/Java IO",
"/Java/Java IO.md": "/notes/Java IO",
"/Java/Java 基础": "/notes/Java 基础",
"/Java/Java 基础.html": "/notes/Java 基础",
"/Java/Java 基础.md": "/notes/Java 基础",
"/Java/Java 容器": "/notes/Java 容器",
"/Java/Java 容器.html": "/notes/Java 容器",
"/Java/Java 容器.md": "/notes/Java 容器",
"/Java/Java 并发": "/notes/Java 并发",
"/Java/Java 并发.html": "/notes/Java 并发",
"/Java/Java 并发.md": "/notes/Java 并发",
"/Java/Java 虚拟机": "/notes/Java 虚拟机",
"/Java/Java 虚拟机.html": "/notes/Java 虚拟机",
"/Java/Java 虚拟机.md": "/notes/Java 虚拟机",
"/其它/系统设计/分布式": "/notes/分布式",
"/其它/系统设计/分布式.html": "/notes/分布式",
"/其它/系统设计/分布式.md": "/notes/分布式",
"/其它/系统设计/攻击技术": "/notes/攻击技术",
"/其它/系统设计/攻击技术.html": "/notes/攻击技术",
"/其它/系统设计/攻击技术.md": "/notes/攻击技术",
"/其它/系统设计/消息队列": "/notes/消息队列",
"/其它/系统设计/消息队列.html": "/notes/消息队列",
"/其它/系统设计/消息队列.md": "/notes/消息队列",
"/其它/系统设计/系统设计基础": "/notes/系统设计基础",
"/其它/系统设计/系统设计基础.html": "/notes/系统设计基础",
"/其它/系统设计/系统设计基础.md": "/notes/系统设计基础",
"/其它/系统设计/缓存": "/notes/缓存",
"/其它/系统设计/缓存.html": "/notes/缓存",
"/其它/系统设计/缓存.md": "/notes/缓存",
"/其它/系统设计/集群": "/notes/集群",
"/其它/系统设计/集群.html": "/notes/集群",
"/其它/系统设计/集群.md": "/notes/集群",
"/其它/编码实践/Docker": "/notes/Docker",
"/其它/编码实践/Docker.html": "/notes/Docker",
"/其它/编码实践/Docker.md": "/notes/Docker",
"/其它/编码实践/Git": "/notes/Git",
"/其它/编码实践/Git.html": "/notes/Git",
"/其它/编码实践/Git.md": "/notes/Git",
"/其它/编码实践/代码可读性": "/notes/代码可读性",
"/其它/编码实践/代码可读性.html": "/notes/代码可读性",
"/其它/编码实践/代码可读性.md": "/notes/代码可读性",
"/其它/编码实践/代码风格规范": "/notes/代码风格规范",
"/其它/编码实践/代码风格规范.html": "/notes/代码风格规范",
"/其它/编码实践/代码风格规范.md": "/notes/代码风格规范",
"/其它/编码实践/构建工具": "/notes/构建工具",
"/其它/编码实践/构建工具.html": "/notes/构建工具",
"/其它/编码实践/构建工具.md": "/notes/构建工具",
"/其它/编码实践/正则表达式": "/notes/正则表达式",
"/其它/编码实践/正则表达式.html": "/notes/正则表达式",
"/其它/编码实践/正则表达式.md": "/notes/正则表达式",
"/其它/编码实践/面向对象思想": "/notes/面向对象思想",
"/其它/编码实践/面向对象思想.html": "/notes/面向对象思想",
"/其它/编码实践/面向对象思想.md": "/notes/面向对象思想",
"/其它/设计模式/设计模式 - 单例": "/notes/设计模式 - 单例",
"/其它/设计模式/设计模式 - 单例.html": "/notes/设计模式 - 单例",
"/其它/设计模式/设计模式 - 单例.md": "/notes/设计模式 - 单例",
"/其它/设计模式/设计模式 - 中介者": "/notes/设计模式 - 中介者",
"/其它/设计模式/设计模式 - 中介者.html": "/notes/设计模式 - 中介者",
"/其它/设计模式/设计模式 - 中介者.md": "/notes/设计模式 - 中介者",
"/其它/设计模式/设计模式 - 享元": "/notes/设计模式 - 享元",
"/其它/设计模式/设计模式 - 享元.html": "/notes/设计模式 - 享元",
"/其它/设计模式/设计模式 - 享元.md": "/notes/设计模式 - 享元",
"/其它/设计模式/设计模式 - 代理": "/notes/设计模式 - 代理",
"/其它/设计模式/设计模式 - 代理.html": "/notes/设计模式 - 代理",
"/其它/设计模式/设计模式 - 代理.md": "/notes/设计模式 - 代理",
"/其它/设计模式/设计模式 - 原型模式": "/notes/设计模式 - 原型模式",
"/其它/设计模式/设计模式 - 原型模式.html": "/notes/设计模式 - 原型模式",
"/其它/设计模式/设计模式 - 原型模式.md": "/notes/设计模式 - 原型模式",
"/其它/设计模式/设计模式 - 命令": "/notes/设计模式 - 命令",
"/其它/设计模式/设计模式 - 命令.html": "/notes/设计模式 - 命令",
"/其它/设计模式/设计模式 - 命令.md": "/notes/设计模式 - 命令",
"/其它/设计模式/设计模式 - 备忘录": "/notes/设计模式 - 备忘录",
"/其它/设计模式/设计模式 - 备忘录.html": "/notes/设计模式 - 备忘录",
"/其它/设计模式/设计模式 - 备忘录.md": "/notes/设计模式 - 备忘录",
"/其它/设计模式/设计模式 - 外观": "/notes/设计模式 - 外观",
"/其它/设计模式/设计模式 - 外观.html": "/notes/设计模式 - 外观",
"/其它/设计模式/设计模式 - 外观.md": "/notes/设计模式 - 外观",
"/其它/设计模式/设计模式 - 工厂方法": "/notes/设计模式 - 工厂方法",
"/其它/设计模式/设计模式 - 工厂方法.html": "/notes/设计模式 - 工厂方法",
"/其它/设计模式/设计模式 - 工厂方法.md": "/notes/设计模式 - 工厂方法",
"/其它/设计模式/设计模式 - 抽象工厂": "/notes/设计模式 - 抽象工厂",
"/其它/设计模式/设计模式 - 抽象工厂.html": "/notes/设计模式 - 抽象工厂",
"/其它/设计模式/设计模式 - 抽象工厂.md": "/notes/设计模式 - 抽象工厂",
"/其它/设计模式/设计模式 - 桥接": "/notes/设计模式 - 桥接",
"/其它/设计模式/设计模式 - 桥接.html": "/notes/设计模式 - 桥接",
"/其它/设计模式/设计模式 - 桥接.md": "/notes/设计模式 - 桥接",
"/其它/设计模式/设计模式 - 模板方法": "/notes/设计模式 - 模板方法",
"/其它/设计模式/设计模式 - 模板方法.html": "/notes/设计模式 - 模板方法",
"/其它/设计模式/设计模式 - 模板方法.md": "/notes/设计模式 - 模板方法",
"/其它/设计模式/设计模式 - 状态": "/notes/设计模式 - 状态",
"/其它/设计模式/设计模式 - 状态.html": "/notes/设计模式 - 状态",
"/其它/设计模式/设计模式 - 状态.md": "/notes/设计模式 - 状态",
"/其它/设计模式/设计模式 - 生成器": "/notes/设计模式 - 生成器",
"/其它/设计模式/设计模式 - 生成器.html": "/notes/设计模式 - 生成器",
"/其它/设计模式/设计模式 - 生成器.md": "/notes/设计模式 - 生成器",
"/其它/设计模式/设计模式 - 目录": "/notes/设计模式 - 目录",
"/其它/设计模式/设计模式 - 目录.html": "/notes/设计模式 - 目录",
"/其它/设计模式/设计模式 - 目录.md": "/notes/设计模式 - 目录",
"/其它/设计模式/设计模式 - 空对象": "/notes/设计模式 - 空对象",
"/其它/设计模式/设计模式 - 空对象.html": "/notes/设计模式 - 空对象",
"/其它/设计模式/设计模式 - 空对象.md": "/notes/设计模式 - 空对象",
"/其它/设计模式/设计模式 - 策略": "/notes/设计模式 - 策略",
"/其它/设计模式/设计模式 - 策略.html": "/notes/设计模式 - 策略",
"/其它/设计模式/设计模式 - 策略.md": "/notes/设计模式 - 策略",
"/其它/设计模式/设计模式 - 简单工厂": "/notes/设计模式 - 简单工厂",
"/其它/设计模式/设计模式 - 简单工厂.html": "/notes/设计模式 - 简单工厂",
"/其它/设计模式/设计模式 - 简单工厂.md": "/notes/设计模式 - 简单工厂",
"/其它/设计模式/设计模式 - 组合": "/notes/设计模式 - 组合",
"/其它/设计模式/设计模式 - 组合.html": "/notes/设计模式 - 组合",
"/其它/设计模式/设计模式 - 组合.md": "/notes/设计模式 - 组合",
"/其它/设计模式/设计模式 - 装饰": "/notes/设计模式 - 装饰",
"/其它/设计模式/设计模式 - 装饰.html": "/notes/设计模式 - 装饰",
"/其它/设计模式/设计模式 - 装饰.md": "/notes/设计模式 - 装饰",
"/其它/设计模式/设计模式 - 观察者": "/notes/设计模式 - 观察者",
"/其它/设计模式/设计模式 - 观察者.html": "/notes/设计模式 - 观察者",
"/其它/设计模式/设计模式 - 观察者.md": "/notes/设计模式 - 观察者",
"/其它/设计模式/设计模式 - 解释器": "/notes/设计模式 - 解释器",
"/其它/设计模式/设计模式 - 解释器.html": "/notes/设计模式 - 解释器",
"/其它/设计模式/设计模式 - 解释器.md": "/notes/设计模式 - 解释器",
"/其它/设计模式/设计模式 - 访问者": "/notes/设计模式 - 访问者",
"/其它/设计模式/设计模式 - 访问者.html": "/notes/设计模式 - 访问者",
"/其它/设计模式/设计模式 - 访问者.md": "/notes/设计模式 - 访问者",
"/其它/设计模式/设计模式 - 责任链": "/notes/设计模式 - 责任链",
"/其它/设计模式/设计模式 - 责任链.html": "/notes/设计模式 - 责任链",
"/其它/设计模式/设计模式 - 责任链.md": "/notes/设计模式 - 责任链",
"/其它/设计模式/设计模式 - 迭代器": "/notes/设计模式 - 迭代器",
"/其它/设计模式/设计模式 - 迭代器.html": "/notes/设计模式 - 迭代器",
"/其它/设计模式/设计模式 - 迭代器.md": "/notes/设计模式 - 迭代器",
"/其它/设计模式/设计模式 - 适配器": "/notes/设计模式 - 适配器",
"/其它/设计模式/设计模式 - 适配器.html": "/notes/设计模式 - 适配器",
"/其它/设计模式/设计模式 - 适配器.md": "/notes/设计模式 - 适配器",
"/数据库/MySQL": "/notes/MySQL",
"/数据库/MySQL.html": "/notes/MySQL",
"/数据库/MySQL.md": "/notes/MySQL",
"/数据库/Redis": "/notes/Redis",
"/数据库/Redis.html": "/notes/Redis",
"/数据库/Redis.md": "/notes/Redis",
"/数据库/SQL 练习": "/notes/SQL 练习",
"/数据库/SQL 练习.html": "/notes/SQL 练习",
"/数据库/SQL 练习.md": "/notes/SQL 练习",
"/数据库/SQL 语法": "/notes/SQL 语法",
"/数据库/SQL 语法.html": "/notes/SQL 语法",
"/数据库/SQL 语法.md": "/notes/SQL 语法",
"/数据库/数据库系统原理": "/notes/数据库系统原理",
"/数据库/数据库系统原理.html": "/notes/数据库系统原理",
"/数据库/数据库系统原理.md": "/notes/数据库系统原理",
"/算法/Leetcode 题解/Leetcode 题解 - 二分查找": "/notes/Leetcode 题解 - 二分查找",
"/算法/Leetcode 题解/Leetcode 题解 - 二分查找.html": "/notes/Leetcode 题解 - 二分查找",
"/算法/Leetcode 题解/Leetcode 题解 - 二分查找.md": "/notes/Leetcode 题解 - 二分查找",
"/算法/Leetcode 题解/Leetcode 题解 - 位运算": "/notes/Leetcode 题解 - 位运算",
"/算法/Leetcode 题解/Leetcode 题解 - 位运算.html": "/notes/Leetcode 题解 - 位运算",
"/算法/Leetcode 题解/Leetcode 题解 - 位运算.md": "/notes/Leetcode 题解 - 位运算",
"/算法/Leetcode 题解/Leetcode 题解 - 分治": "/notes/Leetcode 题解 - 分治",
"/算法/Leetcode 题解/Leetcode 题解 - 分治.html": "/notes/Leetcode 题解 - 分治",
"/算法/Leetcode 题解/Leetcode 题解 - 分治.md": "/notes/Leetcode 题解 - 分治",
"/算法/Leetcode 题解/Leetcode 题解 - 动态规划": "/notes/Leetcode 题解 - 动态规划",
"/算法/Leetcode 题解/Leetcode 题解 - 动态规划.html": "/notes/Leetcode 题解 - 动态规划",
"/算法/Leetcode 题解/Leetcode 题解 - 动态规划.md": "/notes/Leetcode 题解 - 动态规划",
"/算法/Leetcode 题解/Leetcode 题解 - 双指针": "/notes/Leetcode 题解 - 双指针",
"/算法/Leetcode 题解/Leetcode 题解 - 双指针.html": "/notes/Leetcode 题解 - 双指针",
"/算法/Leetcode 题解/Leetcode 题解 - 双指针.md": "/notes/Leetcode 题解 - 双指针",
"/算法/Leetcode 题解/Leetcode 题解 - 哈希表": "/notes/Leetcode 题解 - 哈希表",
"/算法/Leetcode 题解/Leetcode 题解 - 哈希表.html": "/notes/Leetcode 题解 - 哈希表",
"/算法/Leetcode 题解/Leetcode 题解 - 哈希表.md": "/notes/Leetcode 题解 - 哈希表",
"/算法/Leetcode 题解/Leetcode 题解 - 图": "/notes/Leetcode 题解 - 图",
"/算法/Leetcode 题解/Leetcode 题解 - 图.html": "/notes/Leetcode 题解 - 图",
"/算法/Leetcode 题解/Leetcode 题解 - 图.md": "/notes/Leetcode 题解 - 图",
"/算法/Leetcode 题解/Leetcode 题解 - 字符串": "/notes/Leetcode 题解 - 字符串",
"/算法/Leetcode 题解/Leetcode 题解 - 字符串.html": "/notes/Leetcode 题解 - 字符串",
"/算法/Leetcode 题解/Leetcode 题解 - 字符串.md": "/notes/Leetcode 题解 - 字符串",
"/算法/Leetcode 题解/Leetcode 题解 - 排序": "/notes/Leetcode 题解 - 排序",
"/算法/Leetcode 题解/Leetcode 题解 - 排序.html": "/notes/Leetcode 题解 - 排序",
"/算法/Leetcode 题解/Leetcode 题解 - 排序.md": "/notes/Leetcode 题解 - 排序",
"/算法/Leetcode 题解/Leetcode 题解 - 搜索": "/notes/Leetcode 题解 - 搜索",
"/算法/Leetcode 题解/Leetcode 题解 - 搜索.html": "/notes/Leetcode 题解 - 搜索",
"/算法/Leetcode 题解/Leetcode 题解 - 搜索.md": "/notes/Leetcode 题解 - 搜索",
"/算法/Leetcode 题解/Leetcode 题解 - 数学": "/notes/Leetcode 题解 - 数学",
"/算法/Leetcode 题解/Leetcode 题解 - 数学.html": "/notes/Leetcode 题解 - 数学",
"/算法/Leetcode 题解/Leetcode 题解 - 数学.md": "/notes/Leetcode 题解 - 数学",
"/算法/Leetcode 题解/Leetcode 题解 - 数组与矩阵": "/notes/Leetcode 题解 - 数组与矩阵",
"/算法/Leetcode 题解/Leetcode 题解 - 数组与矩阵.html": "/notes/Leetcode 题解 - 数组与矩阵",
"/算法/Leetcode 题解/Leetcode 题解 - 数组与矩阵.md": "/notes/Leetcode 题解 - 数组与矩阵",
"/算法/Leetcode 题解/Leetcode 题解 - 栈和队列": "/notes/Leetcode 题解 - 栈和队列",
"/算法/Leetcode 题解/Leetcode 题解 - 栈和队列.html": "/notes/Leetcode 题解 - 栈和队列",
"/算法/Leetcode 题解/Leetcode 题解 - 栈和队列.md": "/notes/Leetcode 题解 - 栈和队列",
"/算法/Leetcode 题解/Leetcode 题解 - 树": "/notes/Leetcode 题解 - 树",
"/算法/Leetcode 题解/Leetcode 题解 - 树.html": "/notes/Leetcode 题解 - 树",
"/算法/Leetcode 题解/Leetcode 题解 - 树.md": "/notes/Leetcode 题解 - 树",
"/算法/Leetcode 题解/Leetcode 题解 - 目录": "/notes/Leetcode 题解 - 目录",
"/算法/Leetcode 题解/Leetcode 题解 - 目录.html": "/notes/Leetcode 题解 - 目录",
"/算法/Leetcode 题解/Leetcode 题解 - 目录.md": "/notes/Leetcode 题解 - 目录",
"/算法/Leetcode 题解/Leetcode 题解 - 贪心思想": "/notes/Leetcode 题解 - 贪心思想",
"/算法/Leetcode 题解/Leetcode 题解 - 贪心思想.html": "/notes/Leetcode 题解 - 贪心思想",
"/算法/Leetcode 题解/Leetcode 题解 - 贪心思想.md": "/notes/Leetcode 题解 - 贪心思想",
"/算法/Leetcode 题解/Leetcode 题解 - 链表": "/notes/Leetcode 题解 - 链表",
"/算法/Leetcode 题解/Leetcode 题解 - 链表.html": "/notes/Leetcode 题解 - 链表",
"/算法/Leetcode 题解/Leetcode 题解 - 链表.md": "/notes/Leetcode 题解 - 链表",
"/算法/剑指 Offer 题解/10.1 斐波那契数列": "/notes/10.1 斐波那契数列",
"/算法/剑指 Offer 题解/10.1 斐波那契数列.html": "/notes/10.1 斐波那契数列",
"/算法/剑指 Offer 题解/10.1 斐波那契数列.md": "/notes/10.1 斐波那契数列",
"/算法/剑指 Offer 题解/10.2 矩形覆盖": "/notes/10.2 矩形覆盖",
"/算法/剑指 Offer 题解/10.2 矩形覆盖.html": "/notes/10.2 矩形覆盖",
"/算法/剑指 Offer 题解/10.2 矩形覆盖.md": "/notes/10.2 矩形覆盖",
"/算法/剑指 Offer 题解/10.3 跳台阶": "/notes/10.3 跳台阶",
"/算法/剑指 Offer 题解/10.3 跳台阶.html": "/notes/10.3 跳台阶",
"/算法/剑指 Offer 题解/10.3 跳台阶.md": "/notes/10.3 跳台阶",
"/算法/剑指 Offer 题解/10.4 变态跳台阶": "/notes/10.4 变态跳台阶",
"/算法/剑指 Offer 题解/10.4 变态跳台阶.html": "/notes/10.4 变态跳台阶",
"/算法/剑指 Offer 题解/10.4 变态跳台阶.md": "/notes/10.4 变态跳台阶",
"/算法/剑指 Offer 题解/11. 旋转数组的最小数字": "/notes/11. 旋转数组的最小数字",
"/算法/剑指 Offer 题解/11. 旋转数组的最小数字.html": "/notes/11. 旋转数组的最小数字",
"/算法/剑指 Offer 题解/11. 旋转数组的最小数字.md": "/notes/11. 旋转数组的最小数字",
"/算法/剑指 Offer 题解/12. 矩阵中的路径": "/notes/12. 矩阵中的路径",
"/算法/剑指 Offer 题解/12. 矩阵中的路径.html": "/notes/12. 矩阵中的路径",
"/算法/剑指 Offer 题解/12. 矩阵中的路径.md": "/notes/12. 矩阵中的路径",
"/算法/剑指 Offer 题解/13. 机器人的运动范围": "/notes/13. 机器人的运动范围",
"/算法/剑指 Offer 题解/13. 机器人的运动范围.html": "/notes/13. 机器人的运动范围",
"/算法/剑指 Offer 题解/13. 机器人的运动范围.md": "/notes/13. 机器人的运动范围",
"/算法/剑指 Offer 题解/14. 剪绳子": "/notes/14. 剪绳子",
"/算法/剑指 Offer 题解/14. 剪绳子.html": "/notes/14. 剪绳子",
"/算法/剑指 Offer 题解/14. 剪绳子.md": "/notes/14. 剪绳子",
"/算法/剑指 Offer 题解/15. 二进制中 1 的个数": "/notes/15. 二进制中 1 的个数",
"/算法/剑指 Offer 题解/15. 二进制中 1 的个数.html": "/notes/15. 二进制中 1 的个数",
"/算法/剑指 Offer 题解/15. 二进制中 1 的个数.md": "/notes/15. 二进制中 1 的个数",
"/算法/剑指 Offer 题解/16. 数值的整数次方": "/notes/16. 数值的整数次方",
"/算法/剑指 Offer 题解/16. 数值的整数次方.html": "/notes/16. 数值的整数次方",
"/算法/剑指 Offer 题解/16. 数值的整数次方.md": "/notes/16. 数值的整数次方",
"/算法/剑指 Offer 题解/17. 打印从 1 到最大的 n 位数": "/notes/17. 打印从 1 到最大的 n 位数",
"/算法/剑指 Offer 题解/17. 打印从 1 到最大的 n 位数.html": "/notes/17. 打印从 1 到最大的 n 位数",
"/算法/剑指 Offer 题解/17. 打印从 1 到最大的 n 位数.md": "/notes/17. 打印从 1 到最大的 n 位数",
"/算法/剑指 Offer 题解/18.1 在 O(1) 时间内删除链表节点": "/notes/18.1 在 O(1) 时间内删除链表节点",
"/算法/剑指 Offer 题解/18.1 在 O(1) 时间内删除链表节点.html": "/notes/18.1 在 O(1) 时间内删除链表节点",
"/算法/剑指 Offer 题解/18.1 在 O(1) 时间内删除链表节点.md": "/notes/18.1 在 O(1) 时间内删除链表节点",
"/算法/剑指 Offer 题解/18.2 删除链表中重复的结点": "/notes/18.2 删除链表中重复的结点",
"/算法/剑指 Offer 题解/18.2 删除链表中重复的结点.html": "/notes/18.2 删除链表中重复的结点",
"/算法/剑指 Offer 题解/18.2 删除链表中重复的结点.md": "/notes/18.2 删除链表中重复的结点",
"/算法/剑指 Offer 题解/19. 正则表达式匹配": "/notes/19. 正则表达式匹配",
"/算法/剑指 Offer 题解/19. 正则表达式匹配.html": "/notes/19. 正则表达式匹配",
"/算法/剑指 Offer 题解/19. 正则表达式匹配.md": "/notes/19. 正则表达式匹配",
"/算法/剑指 Offer 题解/20. 表示数值的字符串": "/notes/20. 表示数值的字符串",
"/算法/剑指 Offer 题解/20. 表示数值的字符串.html": "/notes/20. 表示数值的字符串",
"/算法/剑指 Offer 题解/20. 表示数值的字符串.md": "/notes/20. 表示数值的字符串",
"/算法/剑指 Offer 题解/21. 调整数组顺序使奇数位于偶数前面": "/notes/21. 调整数组顺序使奇数位于偶数前面",
"/算法/剑指 Offer 题解/21. 调整数组顺序使奇数位于偶数前面.html": "/notes/21. 调整数组顺序使奇数位于偶数前面",
"/算法/剑指 Offer 题解/21. 调整数组顺序使奇数位于偶数前面.md": "/notes/21. 调整数组顺序使奇数位于偶数前面",
"/算法/剑指 Offer 题解/22. 链表中倒数第 K 个结点": "/notes/22. 链表中倒数第 K 个结点",
"/算法/剑指 Offer 题解/22. 链表中倒数第 K 个结点.html": "/notes/22. 链表中倒数第 K 个结点",
"/算法/剑指 Offer 题解/22. 链表中倒数第 K 个结点.md": "/notes/22. 链表中倒数第 K 个结点",
"/算法/剑指 Offer 题解/23. 链表中环的入口结点": "/notes/23. 链表中环的入口结点",
"/算法/剑指 Offer 题解/23. 链表中环的入口结点.html": "/notes/23. 链表中环的入口结点",
"/算法/剑指 Offer 题解/23. 链表中环的入口结点.md": "/notes/23. 链表中环的入口结点",
"/算法/剑指 Offer 题解/24. 反转链表": "/notes/24. 反转链表",
"/算法/剑指 Offer 题解/24. 反转链表.html": "/notes/24. 反转链表",
"/算法/剑指 Offer 题解/24. 反转链表.md": "/notes/24. 反转链表",
"/算法/剑指 Offer 题解/25. 合并两个排序的链表": "/notes/25. 合并两个排序的链表",
"/算法/剑指 Offer 题解/25. 合并两个排序的链表.html": "/notes/25. 合并两个排序的链表",
"/算法/剑指 Offer 题解/25. 合并两个排序的链表.md": "/notes/25. 合并两个排序的链表",
"/算法/剑指 Offer 题解/26. 树的子结构": "/notes/26. 树的子结构",
"/算法/剑指 Offer 题解/26. 树的子结构.html": "/notes/26. 树的子结构",
"/算法/剑指 Offer 题解/26. 树的子结构.md": "/notes/26. 树的子结构",
"/算法/剑指 Offer 题解/27. 二叉树的镜像": "/notes/27. 二叉树的镜像",
"/算法/剑指 Offer 题解/27. 二叉树的镜像.html": "/notes/27. 二叉树的镜像",
"/算法/剑指 Offer 题解/27. 二叉树的镜像.md": "/notes/27. 二叉树的镜像",
"/算法/剑指 Offer 题解/28. 对称的二叉树": "/notes/28. 对称的二叉树",
"/算法/剑指 Offer 题解/28. 对称的二叉树.html": "/notes/28. 对称的二叉树",
"/算法/剑指 Offer 题解/28. 对称的二叉树.md": "/notes/28. 对称的二叉树",
"/算法/剑指 Offer 题解/29. 顺时针打印矩阵": "/notes/29. 顺时针打印矩阵",
"/算法/剑指 Offer 题解/29. 顺时针打印矩阵.html": "/notes/29. 顺时针打印矩阵",
"/算法/剑指 Offer 题解/29. 顺时针打印矩阵.md": "/notes/29. 顺时针打印矩阵",
"/算法/剑指 Offer 题解/3. 数组中重复的数字": "/notes/3. 数组中重复的数字",
"/算法/剑指 Offer 题解/3. 数组中重复的数字.html": "/notes/3. 数组中重复的数字",
"/算法/剑指 Offer 题解/3. 数组中重复的数字.md": "/notes/3. 数组中重复的数字",
"/算法/剑指 Offer 题解/30. 包含 min 函数的栈": "/notes/30. 包含 min 函数的栈",
"/算法/剑指 Offer 题解/30. 包含 min 函数的栈.html": "/notes/30. 包含 min 函数的栈",
"/算法/剑指 Offer 题解/30. 包含 min 函数的栈.md": "/notes/30. 包含 min 函数的栈",
"/算法/剑指 Offer 题解/31. 栈的压入、弹出序列": "/notes/31. 栈的压入、弹出序列",
"/算法/剑指 Offer 题解/31. 栈的压入、弹出序列.html": "/notes/31. 栈的压入、弹出序列",
"/算法/剑指 Offer 题解/31. 栈的压入、弹出序列.md": "/notes/31. 栈的压入、弹出序列",
"/算法/剑指 Offer 题解/32.1 从上往下打印二叉树": "/notes/32.1 从上往下打印二叉树",
"/算法/剑指 Offer 题解/32.1 从上往下打印二叉树.html": "/notes/32.1 从上往下打印二叉树",
"/算法/剑指 Offer 题解/32.1 从上往下打印二叉树.md": "/notes/32.1 从上往下打印二叉树",
"/算法/剑指 Offer 题解/32.2 把二叉树打印成多行": "/notes/32.2 把二叉树打印成多行",
"/算法/剑指 Offer 题解/32.2 把二叉树打印成多行.html": "/notes/32.2 把二叉树打印成多行",
"/算法/剑指 Offer 题解/32.2 把二叉树打印成多行.md": "/notes/32.2 把二叉树打印成多行",
"/算法/剑指 Offer 题解/32.3 按之字形顺序打印二叉树": "/notes/32.3 按之字形顺序打印二叉树",
"/算法/剑指 Offer 题解/32.3 按之字形顺序打印二叉树.html": "/notes/32.3 按之字形顺序打印二叉树",
"/算法/剑指 Offer 题解/32.3 按之字形顺序打印二叉树.md": "/notes/32.3 按之字形顺序打印二叉树",
"/算法/剑指 Offer 题解/33. 二叉搜索树的后序遍历序列": "/notes/33. 二叉搜索树的后序遍历序列",
"/算法/剑指 Offer 题解/33. 二叉搜索树的后序遍历序列.html": "/notes/33. 二叉搜索树的后序遍历序列",
"/算法/剑指 Offer 题解/33. 二叉搜索树的后序遍历序列.md": "/notes/33. 二叉搜索树的后序遍历序列",
"/算法/剑指 Offer 题解/34. 二叉树中和为某一值的路径": "/notes/34. 二叉树中和为某一值的路径",
"/算法/剑指 Offer 题解/34. 二叉树中和为某一值的路径.html": "/notes/34. 二叉树中和为某一值的路径",
"/算法/剑指 Offer 题解/34. 二叉树中和为某一值的路径.md": "/notes/34. 二叉树中和为某一值的路径",
"/算法/剑指 Offer 题解/35. 复杂链表的复制": "/notes/35. 复杂链表的复制",
"/算法/剑指 Offer 题解/35. 复杂链表的复制.html": "/notes/35. 复杂链表的复制",
"/算法/剑指 Offer 题解/35. 复杂链表的复制.md": "/notes/35. 复杂链表的复制",
"/算法/剑指 Offer 题解/36. 二叉搜索树与双向链表": "/notes/36. 二叉搜索树与双向链表",
"/算法/剑指 Offer 题解/36. 二叉搜索树与双向链表.html": "/notes/36. 二叉搜索树与双向链表",
"/算法/剑指 Offer 题解/36. 二叉搜索树与双向链表.md": "/notes/36. 二叉搜索树与双向链表",
"/算法/剑指 Offer 题解/37. 序列化二叉树": "/notes/37. 序列化二叉树",
"/算法/剑指 Offer 题解/37. 序列化二叉树.html": "/notes/37. 序列化二叉树",
"/算法/剑指 Offer 题解/37. 序列化二叉树.md": "/notes/37. 序列化二叉树",
"/算法/剑指 Offer 题解/38. 字符串的排列": "/notes/38. 字符串的排列",
"/算法/剑指 Offer 题解/38. 字符串的排列.html": "/notes/38. 字符串的排列",
"/算法/剑指 Offer 题解/38. 字符串的排列.md": "/notes/38. 字符串的排列",
"/算法/剑指 Offer 题解/39. 数组中出现次数超过一半的数字": "/notes/39. 数组中出现次数超过一半的数字",
"/算法/剑指 Offer 题解/39. 数组中出现次数超过一半的数字.html": "/notes/39. 数组中出现次数超过一半的数字",
"/算法/剑指 Offer 题解/39. 数组中出现次数超过一半的数字.md": "/notes/39. 数组中出现次数超过一半的数字",
"/算法/剑指 Offer 题解/4. 二维数组中的查找": "/notes/4. 二维数组中的查找",
"/算法/剑指 Offer 题解/4. 二维数组中的查找.html": "/notes/4. 二维数组中的查找",
"/算法/剑指 Offer 题解/4. 二维数组中的查找.md": "/notes/4. 二维数组中的查找",
"/算法/剑指 Offer 题解/40. 最小的 K 个数": "/notes/40. 最小的 K 个数",
"/算法/剑指 Offer 题解/40. 最小的 K 个数.html": "/notes/40. 最小的 K 个数",
"/算法/剑指 Offer 题解/40. 最小的 K 个数.md": "/notes/40. 最小的 K 个数",
"/算法/剑指 Offer 题解/41.1 数据流中的中位数": "/notes/41.1 数据流中的中位数",
"/算法/剑指 Offer 题解/41.1 数据流中的中位数.html": "/notes/41.1 数据流中的中位数",
"/算法/剑指 Offer 题解/41.1 数据流中的中位数.md": "/notes/41.1 数据流中的中位数",
"/算法/剑指 Offer 题解/41.2 字符流中第一个不重复的字符": "/notes/41.2 字符流中第一个不重复的字符",
"/算法/剑指 Offer 题解/41.2 字符流中第一个不重复的字符.html": "/notes/41.2 字符流中第一个不重复的字符",
"/算法/剑指 Offer 题解/41.2 字符流中第一个不重复的字符.md": "/notes/41.2 字符流中第一个不重复的字符",
"/算法/剑指 Offer 题解/42. 连续子数组的最大和": "/notes/42. 连续子数组的最大和",
"/算法/剑指 Offer 题解/42. 连续子数组的最大和.html": "/notes/42. 连续子数组的最大和",
"/算法/剑指 Offer 题解/42. 连续子数组的最大和.md": "/notes/42. 连续子数组的最大和",
"/算法/剑指 Offer 题解/43. 从 1 到 n 整数中 1 出现的次数": "/notes/43. 从 1 到 n 整数中 1 出现的次数",
"/算法/剑指 Offer 题解/43. 从 1 到 n 整数中 1 出现的次数.html": "/notes/43. 从 1 到 n 整数中 1 出现的次数",
"/算法/剑指 Offer 题解/43. 从 1 到 n 整数中 1 出现的次数.md": "/notes/43. 从 1 到 n 整数中 1 出现的次数",
"/算法/剑指 Offer 题解/44. 数字序列中的某一位数字": "/notes/44. 数字序列中的某一位数字",
"/算法/剑指 Offer 题解/44. 数字序列中的某一位数字.html": "/notes/44. 数字序列中的某一位数字",
"/算法/剑指 Offer 题解/44. 数字序列中的某一位数字.md": "/notes/44. 数字序列中的某一位数字",
"/算法/剑指 Offer 题解/45. 把数组排成最小的数": "/notes/45. 把数组排成最小的数",
"/算法/剑指 Offer 题解/45. 把数组排成最小的数.html": "/notes/45. 把数组排成最小的数",
"/算法/剑指 Offer 题解/45. 把数组排成最小的数.md": "/notes/45. 把数组排成最小的数",
"/算法/剑指 Offer 题解/46. 把数字翻译成字符串": "/notes/46. 把数字翻译成字符串",
"/算法/剑指 Offer 题解/46. 把数字翻译成字符串.html": "/notes/46. 把数字翻译成字符串",
"/算法/剑指 Offer 题解/46. 把数字翻译成字符串.md": "/notes/46. 把数字翻译成字符串",
"/算法/剑指 Offer 题解/47. 礼物的最大价值": "/notes/47. 礼物的最大价值",
"/算法/剑指 Offer 题解/47. 礼物的最大价值.html": "/notes/47. 礼物的最大价值",
"/算法/剑指 Offer 题解/47. 礼物的最大价值.md": "/notes/47. 礼物的最大价值",
"/算法/剑指 Offer 题解/48. 最长不含重复字符的子字符串": "/notes/48. 最长不含重复字符的子字符串",
"/算法/剑指 Offer 题解/48. 最长不含重复字符的子字符串.html": "/notes/48. 最长不含重复字符的子字符串",
"/算法/剑指 Offer 题解/48. 最长不含重复字符的子字符串.md": "/notes/48. 最长不含重复字符的子字符串",
"/算法/剑指 Offer 题解/49. 丑数": "/notes/49. 丑数",
"/算法/剑指 Offer 题解/49. 丑数.html": "/notes/49. 丑数",
"/算法/剑指 Offer 题解/49. 丑数.md": "/notes/49. 丑数",
"/算法/剑指 Offer 题解/5. 替换空格": "/notes/5. 替换空格",
"/算法/剑指 Offer 题解/5. 替换空格.html": "/notes/5. 替换空格",
"/算法/剑指 Offer 题解/5. 替换空格.md": "/notes/5. 替换空格",
"/算法/剑指 Offer 题解/50. 第一个只出现一次的字符位置": "/notes/50. 第一个只出现一次的字符位置",
"/算法/剑指 Offer 题解/50. 第一个只出现一次的字符位置.html": "/notes/50. 第一个只出现一次的字符位置",
"/算法/剑指 Offer 题解/50. 第一个只出现一次的字符位置.md": "/notes/50. 第一个只出现一次的字符位置",
"/算法/剑指 Offer 题解/51. 数组中的逆序对": "/notes/51. 数组中的逆序对",
"/算法/剑指 Offer 题解/51. 数组中的逆序对.html": "/notes/51. 数组中的逆序对",
"/算法/剑指 Offer 题解/51. 数组中的逆序对.md": "/notes/51. 数组中的逆序对",
"/算法/剑指 Offer 题解/52. 两个链表的第一个公共结点": "/notes/52. 两个链表的第一个公共结点",
"/算法/剑指 Offer 题解/52. 两个链表的第一个公共结点.html": "/notes/52. 两个链表的第一个公共结点",
"/算法/剑指 Offer 题解/52. 两个链表的第一个公共结点.md": "/notes/52. 两个链表的第一个公共结点",
"/算法/剑指 Offer 题解/53. 数字在排序数组中出现的次数": "/notes/53. 数字在排序数组中出现的次数",
"/算法/剑指 Offer 题解/53. 数字在排序数组中出现的次数.html": "/notes/53. 数字在排序数组中出现的次数",
"/算法/剑指 Offer 题解/53. 数字在排序数组中出现的次数.md": "/notes/53. 数字在排序数组中出现的次数",
"/算法/剑指 Offer 题解/54. 二叉查找树的第 K 个结点": "/notes/54. 二叉查找树的第 K 个结点",
"/算法/剑指 Offer 题解/54. 二叉查找树的第 K 个结点.html": "/notes/54. 二叉查找树的第 K 个结点",
"/算法/剑指 Offer 题解/54. 二叉查找树的第 K 个结点.md": "/notes/54. 二叉查找树的第 K 个结点",
"/算法/剑指 Offer 题解/55.1 二叉树的深度": "/notes/55.1 二叉树的深度",
"/算法/剑指 Offer 题解/55.1 二叉树的深度.html": "/notes/55.1 二叉树的深度",
"/算法/剑指 Offer 题解/55.1 二叉树的深度.md": "/notes/55.1 二叉树的深度",
"/算法/剑指 Offer 题解/55.2 平衡二叉树": "/notes/55.2 平衡二叉树",
"/算法/剑指 Offer 题解/55.2 平衡二叉树.html": "/notes/55.2 平衡二叉树",
"/算法/剑指 Offer 题解/55.2 平衡二叉树.md": "/notes/55.2 平衡二叉树",
"/算法/剑指 Offer 题解/56. 数组中只出现一次的数字": "/notes/56. 数组中只出现一次的数字",
"/算法/剑指 Offer 题解/56. 数组中只出现一次的数字.html": "/notes/56. 数组中只出现一次的数字",
"/算法/剑指 Offer 题解/56. 数组中只出现一次的数字.md": "/notes/56. 数组中只出现一次的数字",
"/算法/剑指 Offer 题解/57.1 和为 S 的两个数字": "/notes/57.1 和为 S 的两个数字",
"/算法/剑指 Offer 题解/57.1 和为 S 的两个数字.html": "/notes/57.1 和为 S 的两个数字",
"/算法/剑指 Offer 题解/57.1 和为 S 的两个数字.md": "/notes/57.1 和为 S 的两个数字",
"/算法/剑指 Offer 题解/57.2 和为 S 的连续正数序列": "/notes/57.2 和为 S 的连续正数序列",
"/算法/剑指 Offer 题解/57.2 和为 S 的连续正数序列.html": "/notes/57.2 和为 S 的连续正数序列",
"/算法/剑指 Offer 题解/57.2 和为 S 的连续正数序列.md": "/notes/57.2 和为 S 的连续正数序列",
"/算法/剑指 Offer 题解/58.1 翻转单词顺序列": "/notes/58.1 翻转单词顺序列",
"/算法/剑指 Offer 题解/58.1 翻转单词顺序列.html": "/notes/58.1 翻转单词顺序列",
"/算法/剑指 Offer 题解/58.1 翻转单词顺序列.md": "/notes/58.1 翻转单词顺序列",
"/算法/剑指 Offer 题解/58.2 左旋转字符串": "/notes/58.2 左旋转字符串",
"/算法/剑指 Offer 题解/58.2 左旋转字符串.html": "/notes/58.2 左旋转字符串",
"/算法/剑指 Offer 题解/58.2 左旋转字符串.md": "/notes/58.2 左旋转字符串",
"/算法/剑指 Offer 题解/59. 滑动窗口的最大值": "/notes/59. 滑动窗口的最大值",
"/算法/剑指 Offer 题解/59. 滑动窗口的最大值.html": "/notes/59. 滑动窗口的最大值",
"/算法/剑指 Offer 题解/59. 滑动窗口的最大值.md": "/notes/59. 滑动窗口的最大值",
"/算法/剑指 Offer 题解/6. 从尾到头打印链表": "/notes/6. 从尾到头打印链表",
"/算法/剑指 Offer 题解/6. 从尾到头打印链表.html": "/notes/6. 从尾到头打印链表",
"/算法/剑指 Offer 题解/6. 从尾到头打印链表.md": "/notes/6. 从尾到头打印链表",
"/算法/剑指 Offer 题解/60. n 个骰子的点数": "/notes/60. n 个骰子的点数",
"/算法/剑指 Offer 题解/60. n 个骰子的点数.html": "/notes/60. n 个骰子的点数",
"/算法/剑指 Offer 题解/60. n 个骰子的点数.md": "/notes/60. n 个骰子的点数",
"/算法/剑指 Offer 题解/61. 扑克牌顺子": "/notes/61. 扑克牌顺子",
"/算法/剑指 Offer 题解/61. 扑克牌顺子.html": "/notes/61. 扑克牌顺子",
"/算法/剑指 Offer 题解/61. 扑克牌顺子.md": "/notes/61. 扑克牌顺子",
"/算法/剑指 Offer 题解/62. 圆圈中最后剩下的数": "/notes/62. 圆圈中最后剩下的数",
"/算法/剑指 Offer 题解/62. 圆圈中最后剩下的数.html": "/notes/62. 圆圈中最后剩下的数",
"/算法/剑指 Offer 题解/62. 圆圈中最后剩下的数.md": "/notes/62. 圆圈中最后剩下的数",
"/算法/剑指 Offer 题解/63. 股票的最大利润": "/notes/63. 股票的最大利润",
"/算法/剑指 Offer 题解/63. 股票的最大利润.html": "/notes/63. 股票的最大利润",
"/算法/剑指 Offer 题解/63. 股票的最大利润.md": "/notes/63. 股票的最大利润",
"/算法/剑指 Offer 题解/64. 求 1+2+3+...+n": "/notes/64. 求 1+2+3+...+n",
"/算法/剑指 Offer 题解/64. 求 1+2+3+...+n.html": "/notes/64. 求 1+2+3+...+n",
"/算法/剑指 Offer 题解/64. 求 1+2+3+...+n.md": "/notes/64. 求 1+2+3+...+n",
"/算法/剑指 Offer 题解/65. 不用加减乘除做加法": "/notes/65. 不用加减乘除做加法",
"/算法/剑指 Offer 题解/65. 不用加减乘除做加法.html": "/notes/65. 不用加减乘除做加法",
"/算法/剑指 Offer 题解/65. 不用加减乘除做加法.md": "/notes/65. 不用加减乘除做加法",
"/算法/剑指 Offer 题解/66. 构建乘积数组": "/notes/66. 构建乘积数组",
"/算法/剑指 Offer 题解/66. 构建乘积数组.html": "/notes/66. 构建乘积数组",
"/算法/剑指 Offer 题解/66. 构建乘积数组.md": "/notes/66. 构建乘积数组",
"/算法/剑指 Offer 题解/67. 把字符串转换成整数": "/notes/67. 把字符串转换成整数",
"/算法/剑指 Offer 题解/67. 把字符串转换成整数.html": "/notes/67. 把字符串转换成整数",
"/算法/剑指 Offer 题解/67. 把字符串转换成整数.md": "/notes/67. 把字符串转换成整数",
"/算法/剑指 Offer 题解/68. 树中两个节点的最低公共祖先": "/notes/68. 树中两个节点的最低公共祖先",
"/算法/剑指 Offer 题解/68. 树中两个节点的最低公共祖先.html": "/notes/68. 树中两个节点的最低公共祖先",
"/算法/剑指 Offer 题解/68. 树中两个节点的最低公共祖先.md": "/notes/68. 树中两个节点的最低公共祖先",
"/算法/剑指 Offer 题解/7. 重建二叉树": "/notes/7. 重建二叉树",
"/算法/剑指 Offer 题解/7. 重建二叉树.html": "/notes/7. 重建二叉树",
"/算法/剑指 Offer 题解/7. 重建二叉树.md": "/notes/7. 重建二叉树",
"/算法/剑指 Offer 题解/8. 二叉树的下一个结点": "/notes/8. 二叉树的下一个结点",
"/算法/剑指 Offer 题解/8. 二叉树的下一个结点.html": "/notes/8. 二叉树的下一个结点",
"/算法/剑指 Offer 题解/8. 二叉树的下一个结点.md": "/notes/8. 二叉树的下一个结点",
"/算法/剑指 Offer 题解/9. 用两个栈实现队列": "/notes/9. 用两个栈实现队列",
"/算法/剑指 Offer 题解/9. 用两个栈实现队列.html": "/notes/9. 用两个栈实现队列",
"/算法/剑指 Offer 题解/9. 用两个栈实现队列.md": "/notes/9. 用两个栈实现队列",
"/算法/剑指 Offer 题解/剑指 Offer 题解 - 目录": "/notes/剑指 Offer 题解 - 目录",
"/算法/剑指 Offer 题解/剑指 Offer 题解 - 目录.html": "/notes/剑指 Offer 题解 - 目录",
"/算法/剑指 Offer 题解/剑指 Offer 题解 - 目录.md": "/notes/剑指 Offer 题解 - 目录",
"/算法/基础/算法 - 其它": "/notes/算法 - 其它",
"/算法/基础/算法 - 其它.html": "/notes/算法 - 其它",
"/算法/基础/算法 - 其它.md": "/notes/算法 - 其它",
"/算法/基础/算法 - 并查集": "/notes/算法 - 并查集",
"/算法/基础/算法 - 并查集.html": "/notes/算法 - 并查集",
"/算法/基础/算法 - 并查集.md": "/notes/算法 - 并查集",
"/算法/基础/算法 - 排序": "/notes/算法 - 排序",
"/算法/基础/算法 - 排序.html": "/notes/算法 - 排序",
"/算法/基础/算法 - 排序.md": "/notes/算法 - 排序",
"/算法/基础/算法 - 栈和队列": "/notes/算法 - 栈和队列",
"/算法/基础/算法 - 栈和队列.html": "/notes/算法 - 栈和队列",
"/算法/基础/算法 - 栈和队列.md": "/notes/算法 - 栈和队列",
"/算法/基础/算法 - 目录": "/notes/算法 - 目录",
"/算法/基础/算法 - 目录.html": "/notes/算法 - 目录",
"/算法/基础/算法 - 目录.md": "/notes/算法 - 目录",
"/算法/基础/算法 - 符号表": "/notes/算法 - 符号表",
"/算法/基础/算法 - 符号表.html": "/notes/算法 - 符号表",
"/算法/基础/算法 - 符号表.md": "/notes/算法 - 符号表",
"/算法/基础/算法 - 算法分析": "/notes/算法 - 算法分析",
"/算法/基础/算法 - 算法分析.html": "/notes/算法 - 算法分析",
"/算法/基础/算法 - 算法分析.md": "/notes/算法 - 算法分析",
"/算法/基础/算法": "/notes/算法",
"/算法/基础/算法.html": "/notes/算法",
"/算法/基础/算法.md": "/notes/算法",
"/计算机基础/HTTP/HTTP": "/notes/HTTP",
"/计算机基础/HTTP/HTTP.html": "/notes/HTTP",
"/计算机基础/HTTP/HTTP.md": "/notes/HTTP",
"/计算机基础/Linux/Linux": "/notes/Linux",
"/计算机基础/Linux/Linux.html": "/notes/Linux",
"/计算机基础/Linux/Linux.md": "/notes/Linux",
"/计算机基础/Socket/Socket": "/notes/Socket",
"/计算机基础/Socket/Socket.html": "/notes/Socket",
"/计算机基础/Socket/Socket.md": "/notes/Socket",
"/计算机基础/操作系统基础/计算机操作系统 - 内存管理": "/notes/计算机操作系统 - 内存管理",
"/计算机基础/操作系统基础/计算机操作系统 - 内存管理.html": "/notes/计算机操作系统 - 内存管理",
"/计算机基础/操作系统基础/计算机操作系统 - 内存管理.md": "/notes/计算机操作系统 - 内存管理",
"/计算机基础/操作系统基础/计算机操作系统 - 概述": "/notes/计算机操作系统 - 概述",
"/计算机基础/操作系统基础/计算机操作系统 - 概述.html": "/notes/计算机操作系统 - 概述",
"/计算机基础/操作系统基础/计算机操作系统 - 概述.md": "/notes/计算机操作系统 - 概述",
"/计算机基础/操作系统基础/计算机操作系统 - 死锁": "/notes/计算机操作系统 - 死锁",
"/计算机基础/操作系统基础/计算机操作系统 - 死锁.html": "/notes/计算机操作系统 - 死锁",
"/计算机基础/操作系统基础/计算机操作系统 - 死锁.md": "/notes/计算机操作系统 - 死锁",
"/计算机基础/操作系统基础/计算机操作系统 - 目录": "/notes/计算机操作系统 - 目录",
"/计算机基础/操作系统基础/计算机操作系统 - 目录.html": "/notes/计算机操作系统 - 目录",
"/计算机基础/操作系统基础/计算机操作系统 - 目录.md": "/notes/计算机操作系统 - 目录",
"/计算机基础/操作系统基础/计算机操作系统 - 设备管理": "/notes/计算机操作系统 - 设备管理",
"/计算机基础/操作系统基础/计算机操作系统 - 设备管理.html": "/notes/计算机操作系统 - 设备管理",
"/计算机基础/操作系统基础/计算机操作系统 - 设备管理.md": "/notes/计算机操作系统 - 设备管理",
"/计算机基础/操作系统基础/计算机操作系统 - 进程管理": "/notes/计算机操作系统 - 进程管理",
"/计算机基础/操作系统基础/计算机操作系统 - 进程管理.html": "/notes/计算机操作系统 - 进程管理",
"/计算机基础/操作系统基础/计算机操作系统 - 进程管理.md": "/notes/计算机操作系统 - 进程管理",
"/计算机基础/操作系统基础/计算机操作系统 - 链接": "/notes/计算机操作系统 - 链接",
"/计算机基础/操作系统基础/计算机操作系统 - 链接.html": "/notes/计算机操作系统 - 链接",
"/计算机基础/操作系统基础/计算机操作系统 - 链接.md": "/notes/计算机操作系统 - 链接",
"/计算机基础/网络基础/计算机网络 - 传输层": "/notes/计算机网络 - 传输层",
"/计算机基础/网络基础/计算机网络 - 传输层.html": "/notes/计算机网络 - 传输层",
"/计算机基础/网络基础/计算机网络 - 传输层.md": "/notes/计算机网络 - 传输层",
"/计算机基础/网络基础/计算机网络 - 应用层": "/notes/计算机网络 - 应用层",
"/计算机基础/网络基础/计算机网络 - 应用层.html": "/notes/计算机网络 - 应用层",
"/计算机基础/网络基础/计算机网络 - 应用层.md": "/notes/计算机网络 - 应用层",
"/计算机基础/网络基础/计算机网络 - 概述": "/notes/计算机网络 - 概述",
"/计算机基础/网络基础/计算机网络 - 概述.html": "/notes/计算机网络 - 概述",
"/计算机基础/网络基础/计算机网络 - 概述.md": "/notes/计算机网络 - 概述",
"/计算机基础/网络基础/计算机网络 - 物理层": "/notes/计算机网络 - 物理层",
"/计算机基础/网络基础/计算机网络 - 物理层.html": "/notes/计算机网络 - 物理层",
"/计算机基础/网络基础/计算机网络 - 物理层.md": "/notes/计算机网络 - 物理层",
"/计算机基础/网络基础/计算机网络 - 目录": "/notes/计算机网络 - 目录",
"/计算机基础/网络基础/计算机网络 - 目录.html": "/notes/计算机网络 - 目录",
"/计算机基础/网络基础/计算机网络 - 目录.md": "/notes/计算机网络 - 目录",
"/计算机基础/网络基础/计算机网络 - 网络层": "/notes/计算机网络 - 网络层",
"/计算机基础/网络基础/计算机网络 - 网络层.html": "/notes/计算机网络 - 网络层",
"/计算机基础/网络基础/计算机网络 - 网络层.md": "/notes/计算机网络 - 网络层",
"/计算机基础/网络基础/计算机网络 - 链路层": "/notes/计算机网络 - 链路层",
"/计算机基础/网络基础/计算机网络 - 链路层.html": "/notes/计算机网络 - 链路层",
"/计算机基础/网络基础/计算机网络 - 链路层.md": "/notes/计算机网络 - 链路层"
}
+118
View File
@@ -0,0 +1,118 @@
(function () {
'use strict';
function normalize(value) {
return String(value).normalize('NFKC').toLowerCase().replace(/\s+/g, ' ').trim();
}
function search(index, query, limit) {
var terms = normalize(query).slice(0, 120).split(' ').filter(Boolean);
if (!terms.length) return [];
var results = [];
index.documents.forEach(function (doc) {
var page = normalize(doc.title);
doc.sections.forEach(function (section) {
var heading = normalize(section.title);
var body = normalize(section.text);
var text = heading + ' ' + page + ' ' + body;
if (!terms.every(function (term) { return text.includes(term); })) return;
var score = terms.reduce(function (sum, term) {
return sum + (heading.includes(term) ? 40 : 0) + (page.includes(term) ? 15 : 0) +
(body.includes(term) ? Math.min(body.split(term).length - 1, 6) : 0);
}, 0);
if (heading === normalize(query)) score += 100;
results.push({ page: doc.title, path: doc.path, title: section.title || doc.title,
id: section.id, text: section.text, score: score });
});
});
return results.sort(function (a, b) { return b.score - a.score; }).slice(0, limit || 20);
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = { search: search, normalize: normalize };
return;
}
var indexPromise;
function loadIndex() {
if (!indexPromise) {
indexPromise = fetch(window.CS_NOTES_SEARCH_INDEX, { credentials: 'omit' }).then(function (response) {
if (!response.ok) throw new Error('Search index unavailable');
return response.json();
}).then(function (index) {
if (index.version !== 1 || !Array.isArray(index.documents)) throw new Error('Invalid search index');
return index;
}).catch(function (error) { indexPromise = null; throw error; });
}
return indexPromise;
}
function escapeHtml(value) {
return String(value).replace(/[&<>"']/g, function (character) {
return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[character];
});
}
function snippet(text, query) {
var first = normalize(query).split(' ')[0];
var position = normalize(text).indexOf(first);
var start = Math.max(0, position - 45);
return (start ? '…' : '') + text.slice(start, start + 170) + (text.length > start + 170 ? '…' : '');
}
function mountSearch() {
var sidebar = document.querySelector('.sidebar');
if (!sidebar || sidebar.querySelector('.site-search')) return;
var wrapper = document.createElement('div');
wrapper.className = 'search site-search';
wrapper.innerHTML = '<input type="search" aria-label="搜索全部笔记" aria-controls="site-search-results" placeholder="搜索全部笔记" autocomplete="off">' +
'<p class="search-status" role="status" aria-live="polite"></p>' +
'<div id="site-search-results" class="results-panel" hidden></div>';
sidebar.insertBefore(wrapper, sidebar.firstChild);
var input = wrapper.querySelector('input');
var status = wrapper.querySelector('.search-status');
var panel = wrapper.querySelector('.results-panel');
var timer;
var sequence = 0;
function update() {
var query = input.value.trim();
var request = ++sequence;
panel.hidden = !query;
panel.innerHTML = '';
if (!query) { status.textContent = ''; return; }
status.textContent = '正在搜索…';
loadIndex().then(function (index) {
if (request !== sequence) return;
var results = search(index, query);
status.textContent = results.length ? '找到 ' + results.length + ' 个相关章节' : '没有找到相关内容';
panel.innerHTML = results.map(function (result) {
var url = '/#' + result.path.split('/').map(encodeURIComponent).join('/') +
(result.id ? '?id=' + encodeURIComponent(result.id) : '');
return '<a class="matching-post" href="' + escapeHtml(url) + '"><strong>' + escapeHtml(result.title) +
'</strong><small>' + escapeHtml(result.page) + '</small><p>' + escapeHtml(snippet(result.text, query)) + '</p></a>';
}).join('');
}).catch(function () {
if (request === sequence) status.textContent = '搜索暂时不可用,请稍后重试。';
});
}
input.addEventListener('focus', function () { loadIndex().catch(function () {}); });
input.addEventListener('input', function () { clearTimeout(timer); timer = setTimeout(update, 140); });
input.addEventListener('keydown', function (event) {
if (event.key === 'Escape') { input.value = ''; update(); }
var first = panel.querySelector('a');
if (first && event.key === 'ArrowDown') { event.preventDefault(); first.focus(); }
if (first && event.key === 'Enter') { event.preventDefault(); first.click(); }
});
panel.addEventListener('click', function (event) {
if (event.target.closest('a') && window.matchMedia('(max-width: 768px)').matches) {
document.body.classList.remove('close');
}
});
}
window.$docsify.plugins.push(function (hook) {
hook.mounted(mountSearch);
hook.doneEach(mountSearch);
});
})();
+54
View File
@@ -0,0 +1,54 @@
:root { --theme-color: #369b72; }
body, .sidebar, .sidebar-toggle { background: #f3f2ee; }
body { color: #34495e; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; }
.app-name { font-weight: 650 !important; letter-spacing: .02em; }
.sidebar { padding-top: 24px; border-right: 1px solid #e2e3dc; }
.sidebar .search { border-bottom: 1px solid #e2e3dc; margin-bottom: 20px; }
.search input { background: #fff !important; border-radius: 6px; }
.sidebar-nav > ul { padding-left: 18px; }
.sidebar ul li { margin: 7px 0; }
.sidebar ul li strong { font-size: 13px; letter-spacing: .08em; color: #718078; }
.sidebar ul li a { font-size: 14px; }
.markdown-section { max-width: 960px; padding: 36px 48px 100px; }
.markdown-section h1 { margin: 1rem 0 2rem; font-size: 2rem; }
.markdown-section h2 { margin: 2.8rem 0 1.2rem; padding-bottom: .45rem; border-bottom: 1px solid #dfe3da; }
.markdown-section h3 { margin-top: 2rem; }
.markdown-section p, .markdown-section ul, .markdown-section ol { line-height: 1.95; }
.markdown-section img { max-width: 100%; height: auto; border-radius: 4px; }
.markdown-section pre { background: #fafbf8; border-radius: 6px; border: 1px solid #e2e6dc; }
.markdown-section pre > code { font-family: "SFMono-Regular", Consolas, monospace; line-height: 1.65; }
.markdown-section table { display: block; overflow: auto; }
.markdown-section tr:nth-child(2n) { background: #f8f9f5; }
.markdown-section td, .markdown-section th { border-color: #dfe3da; }
.docsify-copy-code-button { font-family: inherit !important; }
.search .matching-post { border-color: #dde3d8; }
.site-footer { border-top: 1px solid #dfe3da; margin-top: 64px; padding-top: 20px; color: #7c877d; font-size: 12px; text-align: center; }
.site-footer a { color: inherit; font-weight: 400; }
.topic-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; margin: 18px 0; }
.topic-grid a { display: flex; align-items: center; justify-content: center; gap: 7px; padding: 13px 8px; border: 1px solid #dfe3da; border-radius: 7px; background: #fafbf8; text-decoration: none; font-size: 14px; white-space: nowrap; }
.topic-grid a:hover, .topic-grid a:focus-visible { background: #e8f1e9; border-color: #369b72; }
.site-search { padding: 8px 15px; }
.site-search input { box-sizing: border-box; width: 100%; min-height: 42px; padding: 10px; border: 1px solid #dfe3da; color: #34495e; font-size: 15px; }
.site-search input:focus { outline: 2px solid #369b72; outline-offset: 1px; }
.search-status { color: #718078; font-size: 12px; margin: 7px 0; }
.search-status:empty { display: none; }
.site-search .matching-post { display: block; padding: 12px 0; border-bottom: 1px solid #dde3d8; text-decoration: none; }
.matching-post strong, .matching-post small { display: block; }
.matching-post strong { color: #369b72; font-size: 14px; }
.matching-post small { color: #718078; margin: 5px 0; font-size: 12px; }
.matching-post p { color: #34495e; margin: 4px 0; font-size: 13px; line-height: 1.7; overflow-wrap: anywhere; }
.article-toc { border: 1px solid #dfe3da; border-radius: 7px; padding: 12px 18px; margin: 20px 0 28px; background: #fafbf8; }
.article-toc summary { color: #369b72; font-weight: 600; cursor: pointer; min-height: 24px; }
.article-toc > ul { margin-bottom: 0; }
@media (min-width: 769px) { .content { padding-top: 12px; } }
@media (max-width: 768px) {
.markdown-section { padding: 24px 22px 80px; }
.markdown-section h1 { font-size: 1.65rem; }
.markdown-section pre { padding: 14px; }
.topic-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.topic-grid a { min-height: 44px; padding: 8px; }
.site-logo { width: 110px; }
.sidebar-toggle { width: 48px; height: 48px; padding: 13px; background: rgba(243, 242, 238, .95); }
.sidebar ul li a { min-height: 28px; line-height: 2; }
.article-toc summary { min-height: 32px; line-height: 2; }
}
+65
View File
@@ -0,0 +1,65 @@
(function () {
function decode(value) {
try { return decodeURIComponent(value); } catch (error) { return value; }
}
function encodedRoute(path) { return path.split('/').map(encodeURIComponent).join('/'); }
function preserveLegacyLinks() {
var hash = location.hash;
var legacy = (window.CS_NOTES_LEGACY_ROUTES || {})[decode(location.pathname)];
if (legacy) {
var heading = hash && !hash.startsWith('#/') ? '?id=' + encodeURIComponent(decode(hash.slice(1))) : '';
location.replace('/#' + encodedRoute(legacy) + heading);
return;
}
var route = decode(hash.split('?')[0].replace(/^#\//, '').replace(/\.md$/, ''));
if (route && !route.includes('/') && window.CS_NOTES_PATHS.includes('/notes/' + route)) {
location.replace('#/notes/' + encodeURIComponent(route) + (hash.includes('?') ? '?' + hash.split('?').slice(1).join('?') : ''));
}
}
preserveLegacyLinks();
window.addEventListener('hashchange', preserveLegacyLinks);
window.$docsify = {
name: 'CS-Notes',
repo: 'https://github.com/CyC2018/CS-Notes',
homepage: 'README.md',
loadSidebar: true,
alias: { '/.*/_sidebar.md': '/_sidebar.md' },
subMaxLevel: 0,
auto2top: true,
notFoundPage: true,
relativePath: false,
copyCode: { buttonText: '复制', errorText: '复制失败', successText: '已复制' },
plugins: [function (hook, vm) {
hook.afterEach(function (content) {
content = content.replace(/<img\b[^>]*>/gi, function (tag) {
if (!/\bloading\s*=/i.test(tag)) tag = tag.replace(/<img\b/i, '<img loading="lazy"');
if (!/\bdecoding\s*=/i.test(tag)) tag = tag.replace(/<img\b/i, '<img decoding="async"');
return tag;
});
var document = new DOMParser().parseFromString(content, 'text/html');
var heading = document.querySelector('h1');
var list = heading && heading.nextElementSibling;
if (list && list.tagName === 'UL' && list.querySelectorAll('a').length > 5 &&
Array.from(list.querySelectorAll('a')).every(function (link) { return link.getAttribute('href').includes('?id='); })) {
var details = document.createElement('details');
details.className = 'article-toc';
details.innerHTML = '<summary>本文目录</summary>';
list.replaceWith(details);
details.appendChild(list);
}
return document.body.innerHTML + '<footer class="site-footer"><a href="https://github.com/CyC2018/CS-Notes" target="_blank" rel="noopener">CyC2018 · CS-Notes</a><span> · </span><a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">粤ICP备19071404号-1</a></footer>';
});
hook.doneEach(function () {
var heading = document.querySelector('.markdown-section h1, .markdown-section h2');
document.title = (vm.route.path === '/' || !heading ? 'CS-Notes' : heading.textContent.trim() + ' · CS-Notes') + ' · CyC2018';
document.querySelectorAll('.markdown-section img').forEach(function (img) {
img.loading = 'lazy';
img.decoding = 'async';
});
document.querySelectorAll('.article-toc').forEach(function (toc) {
toc.open = window.matchMedia('(min-width: 769px)').matches;
});
});
}]
};
})();
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 - present Docsify Contributors (https://github.com/docsifyjs/docsify/graphs/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 JP Erasmus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+9
View File
@@ -0,0 +1,9 @@
/*!
* docsify-copy-code
* v3.0.2
* https://github.com/jperasmus/docsify-copy-code
* (c) 2017-2025 JP Erasmus <jperasmus11@gmail.com>
* MIT license
*/
!function(){"use strict";function e(o){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e(o)}!function(e,o){void 0===o&&(o={});var t=o.insertAt;if(e&&"undefined"!=typeof document){var n=document.head||document.getElementsByTagName("head")[0],c=document.createElement("style");c.type="text/css","top"===t&&n.firstChild?n.insertBefore(c,n.firstChild):n.appendChild(c),c.styleSheet?c.styleSheet.cssText=e:c.appendChild(document.createTextNode(e))}}(".docsify-copy-code-button,.docsify-copy-code-button>span{cursor:pointer;transition:all .25s ease}.docsify-copy-code-button{background:grey;background:var(--theme-color,grey);border:0;border-radius:0;color:#fff;font-size:1em;opacity:0;outline:0;overflow:visible;padding:.65em .8em;position:absolute;right:0;top:0;z-index:1}.docsify-copy-code-button>span{background:inherit;border-radius:3px;pointer-events:none}.docsify-copy-code-button>.error,.docsify-copy-code-button>.success{font-size:.825em;opacity:0;padding:.5em .65em;position:absolute;right:0;top:50%;transform:translateY(-50%);z-index:-100}.docsify-copy-code-button.error>.error,.docsify-copy-code-button.success>.success{opacity:1;right:100%;transform:translate(-25%,-50%)}.docsify-copy-code-button:focus,pre:hover .docsify-copy-code-button{opacity:1}.docsify-copy-code-button>[aria-live]{height:1px;left:-10000px;overflow:hidden;position:absolute;top:auto;width:1px}"),document.querySelector('link[href*="docsify-copy-code"]')&&console.warn("[Deprecation] Link to external docsify-copy-code stylesheet is no longer necessary."),window.DocsifyCopyCodePlugin={init:function(){return function(e,o){e.ready((function(){console.warn("[Deprecation] Manually initializing docsify-copy-code using window.DocsifyCopyCodePlugin.init() is no longer necessary.")}))}}},window.$docsify=window.$docsify||{},window.$docsify.plugins=[function(o,t){var n={buttonText:"Copy to clipboard",errorText:"Error",successText:"Copied"};o.doneEach((function(){var o=Array.from(document.querySelectorAll("pre[data-lang]"));t.config.copyCode&&Object.keys(n).forEach((function(o){var c=t.config.copyCode[o];"string"==typeof c?n[o]=c:"object"===e(c)&&Object.keys(c).some((function(e){var t=location.href.indexOf(e)>-1;return n[o]=t?c[e]:n[o],t}))}));var c=['<button class="docsify-copy-code-button">','<span class="label">'.concat(n.buttonText,"</span>"),'<span class="error" aria-hidden="hidden">'.concat(n.errorText,"</span>"),'<span class="success" aria-hidden="hidden">'.concat(n.successText,"</span>"),'<span aria-live="polite"></span>',"</button>"].join("");o.forEach((function(e){e.insertAdjacentHTML("beforeend",c)}))})),o.mounted((function(){var e=document.querySelector(".content");e&&e.addEventListener("click",(function(e){if(e.target.classList.contains("docsify-copy-code-button")){var o="BUTTON"===e.target.tagName?e.target:e.target.parentNode,t=document.createRange(),c=o.parentNode.querySelector("code"),i=o.querySelector("[aria-live]"),r=window.getSelection();t.selectNode(c),r&&(r.removeAllRanges(),r.addRange(t));try{document.execCommand("copy")&&(o.classList.add("success"),i.innerText=n.successText,setTimeout((function(){o.classList.remove("success"),i.innerText=""}),1e3))}catch(e){console.error("docsify-copy-code: ".concat(e)),o.classList.add("error"),i.innerText=n.errorText,setTimeout((function(){o.classList.remove("error"),i.innerText=""}),1e3)}setTimeout((function(){e.target.blur()}),1500),(r=window.getSelection())&&("function"==typeof r.removeRange?r.removeRange(t):"function"==typeof r.removeAllRanges&&r.removeAllRanges())}}))}))}].concat(window.$docsify.plugins||[])}();
//# sourceMappingURL=docsify-copy-code.min.js.map
File diff suppressed because one or more lines are too long
+17
View File
@@ -0,0 +1,17 @@
[
{
"package": "docsify",
"version": "4.13.1",
"source": "https://registry.npmjs.org/docsify/-/docsify-4.13.1.tgz"
},
{
"package": "prismjs",
"version": "1.30.0",
"source": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz"
},
{
"package": "docsify-copy-code",
"version": "3.0.2",
"source": "https://registry.npmjs.org/docsify-copy-code/-/docsify-copy-code-3.0.2.tgz"
}
]
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
Prism.languages.c=Prism.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/,function:/\b[a-z_]\w*(?=\s*\()/i,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),Prism.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),Prism.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},Prism.languages.c.string],char:Prism.languages.c.char,comment:Prism.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:Prism.languages.c}}}}),Prism.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete Prism.languages.c.boolean;
+1
View File
@@ -0,0 +1 @@
!function(e){var n=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record(?!\s*[(){}[\]<>=%~.:,;?+\-*/&|^])|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,t="(?:[a-z]\\w*\\s*\\.\\s*)*(?:[A-Z]\\w*\\s*\\.\\s*)*",s={pattern:RegExp("(^|[^\\w.])"+t+"[A-Z](?:[\\d_A-Z]*[a-z]\\w*)?\\b"),lookbehind:!0,inside:{namespace:{pattern:/^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/,inside:{punctuation:/\./}},punctuation:/\./}};e.languages.java=e.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"/,lookbehind:!0,greedy:!0},"class-name":[s,{pattern:RegExp("(^|[^\\w.])"+t+"[A-Z]\\w*(?=\\s+\\w+\\s*[;,=()]|\\s*(?:\\[[\\s,]*\\]\\s*)?::\\s*new\\b)"),lookbehind:!0,inside:s.inside},{pattern:RegExp("(\\b(?:class|enum|extends|implements|instanceof|interface|new|record|throws)\\s+)"+t+"[A-Z]\\w*\\b"),lookbehind:!0,inside:s.inside}],keyword:n,function:[e.languages.clike.function,{pattern:/(::\s*)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x(?:\.[\da-f_p+-]+|[\da-f_]+(?:\.[\da-f_p+-]+)?)\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0},constant:/\b[A-Z][A-Z_\d]+\b/}),e.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"},char:{pattern:/'(?:\\.|[^'\\\r\n]){1,6}'/,greedy:!0}}),e.languages.insertBefore("java","class-name",{annotation:{pattern:/(^|[^.])@\w+(?:\s*\.\s*\w+)*/,lookbehind:!0,alias:"punctuation"},generics:{pattern:/<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&))*>)*>)*>)*>/,inside:{"class-name":s,keyword:n,punctuation:/[<>(),.:]/,operator:/[?&|]/}},import:[{pattern:RegExp("(\\bimport\\s+)"+t+"(?:[A-Z]\\w*|\\*)(?=\\s*;)"),lookbehind:!0,inside:{namespace:s.inside.namespace,punctuation:/\./,operator:/\*/,"class-name":/\w+/}},{pattern:RegExp("(\\bimport\\s+static\\s+)"+t+"(?:\\w+|\\*)(?=\\s*;)"),lookbehind:!0,alias:"static",inside:{namespace:s.inside.namespace,static:/\b\w+$/,punctuation:/\./,operator:/\*/,"class-name":/\w+/}}],namespace:{pattern:RegExp("(\\b(?:exports|import(?:\\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\\s+)(?!<keyword>)[a-z]\\w*(?:\\.[a-z]\\w*)*\\.?".replace(/<keyword>/g,(function(){return n.source}))),lookbehind:!0,inside:{punctuation:/\./}}})}(Prism);
+1
View File
@@ -0,0 +1 @@
Prism.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},Prism.languages.webmanifest=Prism.languages.json;
+1
View File
@@ -0,0 +1 @@
Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python;
+1
View File
@@ -0,0 +1 @@
Prism.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},identifier:{pattern:/(^|[^@\\])`(?:\\[\s\S]|[^`\\]|``)*`/,greedy:!0,lookbehind:!0,inside:{punctuation:/^`|`$/}},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:COL|_INSERT)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:ING|S)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:FALSE|NULL|TRUE)\b/i,number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/};
+21
View File
@@ -0,0 +1,21 @@
MIT LICENSE
Copyright (c) 2012 Lea Verou
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long