From 6e83cc3ecc4509ff11025305924121c5045b0c1b Mon Sep 17 00:00:00 2001 From: laoyang Date: Mon, 28 Sep 2026 15:48:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=AE=80=E8=88=AA=20SimpleNav=20?= =?UTF-8?q?=E4=B8=AA=E4=BA=BA=E7=BD=91=E7=AB=99=E5=AF=BC=E8=88=AA=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=EF=BC=88PHP=20+=20SQLite=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 前台:搜索引擎切换+联想下拉、网格板块(常用/工具)、合集标签页 - 后台:初始化/登录、网站/板块/引擎增删改、上下移排序 - 添加网站自动抓取名称与图标(存储于服务器),支持自定义简称 - 一键全部更新(批量刷新图标+名称,保留自定义简称) - 搜索联想服务端代理(必应/百度/谷歌/搜狗/360/DuckDuckGo) - 零依赖:原生 CSS/JS,PDO SQLite --- .gitignore | 12 ++ .htaccess | 8 + README.md | 69 ++++++- admin/_auth.php | 93 ++++++++++ admin/actions.php | 205 ++++++++++++++++++++ admin/fetch_meta.php | 36 ++++ admin/index.php | 258 ++++++++++++++++++++++++++ admin/update_all.php | 61 ++++++ apple-touch-icon.png | Bin 0 -> 4302 bytes assets/css/admin.css | 74 ++++++++ assets/css/style.css | 116 ++++++++++++ assets/icons/icon-128.png | Bin 0 -> 4455 bytes assets/icons/icon-144.png | Bin 0 -> 5106 bytes assets/icons/icon-150.png | Bin 0 -> 5320 bytes assets/icons/icon-16.png | Bin 0 -> 615 bytes assets/icons/icon-180.png | Bin 0 -> 6516 bytes assets/icons/icon-192-maskable.png | Bin 0 -> 3960 bytes assets/icons/icon-192.png | Bin 0 -> 6907 bytes assets/icons/icon-256.png | Bin 0 -> 9572 bytes assets/icons/icon-32.png | Bin 0 -> 1165 bytes assets/icons/icon-384.png | Bin 0 -> 15759 bytes assets/icons/icon-48.png | Bin 0 -> 1737 bytes assets/icons/icon-512-maskable.png | Bin 0 -> 15445 bytes assets/icons/icon-512.png | Bin 0 -> 23442 bytes assets/icons/icon-64.png | Bin 0 -> 2218 bytes assets/icons/icon-96.png | Bin 0 -> 3397 bytes assets/img/logo.svg | 14 ++ assets/js/admin.js | 106 +++++++++++ assets/js/app.js | 90 +++++++++ config.php | 60 ++++++ data/.htaccess | 8 + data/index.html | 1 + db.php | 226 ++++++++++++++++++++++ deploy/nginx-simplenav-deny-data.conf | 14 ++ deploy/setup-simplenav-user.sh | 122 ++++++++++++ favicon.ico | Bin 0 -> 11393 bytes functions.php | 237 +++++++++++++++++++++++ index.php | 140 ++++++++++++++ login.php | 64 +++++++ logout.php | 14 ++ site.webmanifest | 15 ++ suggest.php | 116 ++++++++++++ uploads/.htaccess | 12 ++ uploads/icons/.gitkeep | 0 44 files changed, 2169 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 .htaccess create mode 100644 admin/_auth.php create mode 100644 admin/actions.php create mode 100644 admin/fetch_meta.php create mode 100644 admin/index.php create mode 100644 admin/update_all.php create mode 100644 apple-touch-icon.png create mode 100644 assets/css/admin.css create mode 100644 assets/css/style.css create mode 100644 assets/icons/icon-128.png create mode 100644 assets/icons/icon-144.png create mode 100644 assets/icons/icon-150.png create mode 100644 assets/icons/icon-16.png create mode 100644 assets/icons/icon-180.png create mode 100644 assets/icons/icon-192-maskable.png create mode 100644 assets/icons/icon-192.png create mode 100644 assets/icons/icon-256.png create mode 100644 assets/icons/icon-32.png create mode 100644 assets/icons/icon-384.png create mode 100644 assets/icons/icon-48.png create mode 100644 assets/icons/icon-512-maskable.png create mode 100644 assets/icons/icon-512.png create mode 100644 assets/icons/icon-64.png create mode 100644 assets/icons/icon-96.png create mode 100644 assets/img/logo.svg create mode 100644 assets/js/admin.js create mode 100644 assets/js/app.js create mode 100644 config.php create mode 100644 data/.htaccess create mode 100644 data/index.html create mode 100644 db.php create mode 100644 deploy/nginx-simplenav-deny-data.conf create mode 100644 deploy/setup-simplenav-user.sh create mode 100644 favicon.ico create mode 100644 functions.php create mode 100644 index.php create mode 100644 login.php create mode 100644 logout.php create mode 100644 site.webmanifest create mode 100644 suggest.php create mode 100644 uploads/.htaccess create mode 100644 uploads/icons/.gitkeep diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..31bb55a --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# 运行时数据(数据库与抓取的图标不纳入版本库) +/data/* +!/data/.htaccess +!/data/index.html +/uploads/icons/* +!/uploads/icons/.gitkeep + +# 杂项 +*.db +*.log +.DS_Store +Thumbs.db diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..bb5d694 --- /dev/null +++ b/.htaccess @@ -0,0 +1,8 @@ +# SimpleNav —— 根目录安全规则 + + RewriteEngine On + RewriteRule ^data/ - [F,L] + RewriteRule ^\.git/ - [F,L] + +# 关闭目录浏览 +Options -Indexes diff --git a/README.md b/README.md index 8f77418..627be07 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,68 @@ -# SimpleNav +# SimpleNav · 简航 -简航,一个面向个人的轻量级导航系统 \ No newline at end of file +简航(SimpleNav),一个面向个人的**轻量级网站导航系统**。PHP + SQLite,零外部依赖、无需 Composer,上传即用。 + +## 特点 + +- **轻量**:纯 PHP + SQLite,不依赖 MySQL / 框架 / Composer;前端无 Bootstrap / jQuery,原生 CSS + JS。 +- **个人向**:单管理员,页面就是「你自己常用网站的起始页」。 +- **搜索引擎可切换 + 联想下拉**:后台勾选喜欢的搜索引擎即可显示在顶部;输入时下拉联想词由服务端代理(规避跨域)。 +- **添加即补全**:填网址自动抓取网站名称、图标(图标下载并规范化后存到服务器本地)。 +- **显示简称**:每个网站有「名称(自动)」和「简称(手动)」两个字段,页面展示你自定义的简称。 +- **自定义排序**:网站 / 板块 / 搜索引擎都可上移下移排序。 +- **一键全部更新**:批量刷新所有网站的图标与名称,**不会改动你自定义的简称**。 +- **合集标签页**:把「合集」做成标签页,点标签平铺该合集下的全部链接。 + +## 页面结构(复刻 n.yangyq.net 风格) + +1. 顶部:搜索引擎切换 + 搜索框(带联想下拉) +2. 中部:网格板块(如「常用」「工具」) +3. 下部:「合集」标签页 + +## 目录结构 + +``` +SimpleNav/ +├─ index.php 前台首页 +├─ suggest.php 搜索联想代理(多引擎,服务端请求) +├─ login.php / logout.php 管理员登录 / 首次初始化 +├─ db.php 数据库初始化(建表 + 默认数据) +├─ config.php 基础配置 +├─ functions.php 公共函数(抓取元信息、下载图标等) +├─ admin/ 后台(网站 / 板块 / 搜索引擎 / 设置) +│ ├─ index.php 后台页面 +│ ├─ actions.php 增删改 / 排序处理 +│ ├─ fetch_meta.php 添加网站时自动抓取名称与图标 +│ └─ update_all.php 一键“全部更新”(分批) +├─ assets/ CSS / JS / 图标 +├─ uploads/icons/ 抓取到的网站图标(需可写) +└─ data/ SQLite 数据库(需可写,禁止 Web 访问) +``` + +## 部署 + +1. 上传全部文件到站点目录(如 `/www/wwwroot/n.yangyq.net/simplenav`)。 +2. 确保 web 用户对 `data/` 与 `uploads/icons/` 有写权限。 +3. 浏览器打开站点 → 首次访问进入「初始化管理员」,创建账号后即可在后台管理。 +4. 建议在 nginx server{} 中加入 `deploy/nginx-simplenav-deny-data.conf` 里的规则(禁止访问 `data/`)。 + +服务器上创建最小权限部署账号:以 root 运行 `deploy/setup-simplenav-user.sh`。 + +## 搜索引擎联想接口说明 + +联想接口需服务端代理(浏览器直接调用会跨域)。内置解析类型: + +| 类型 | 平台 | 联想接口 | +|---|---|---| +| `bing_osjson` | 必应 | `https://api.bing.com/osjson.aspx?query={q}` | +| `baidu_sugrec` | 百度 | `https://www.baidu.com/sugrec?prod=pc&wd={q}` | +| `google_firefox` | 谷歌 | `https://suggestqueries.google.com/complete/search?client=firefox&q={q}` | +| `sogou_ajaj` | 搜狗 | `https://www.sogou.com/suggnew/ajajjson?type=web&key={q}` | +| `so360` | 360 | `https://sug.so.360.cn/suggest?encodein=utf-8&encodeout=utf-8&format=json&word={q}` | +| `ddg` | DuckDuckGo | `https://duckduckgo.com/ac/?type=list&q={q}` | + +> 注:国内服务器访问 Google / DuckDuckGo 可能不通,联想会优雅降级为空(不影响搜索跳转)。 + +## 许可 + +Apache License 2.0 diff --git a/admin/_auth.php b/admin/_auth.php new file mode 100644 index 0000000..20c788e --- /dev/null +++ b/admin/_auth.php @@ -0,0 +1,93 @@ +query('SELECT COUNT(*) FROM users')->fetchColumn(); +} + +function current_user(): ?array +{ + if (empty($_SESSION['uid'])) { + return null; + } + $st = db()->prepare('SELECT id, username FROM users WHERE id=?'); + $st->execute([$_SESSION['uid']]); + $u = $st->fetch(); + return $u ?: null; +} + +function require_login(): void +{ + if (!current_user()) { + header('Location: ' . url('login.php')); + exit; + } +} + +function csrf_token(): string +{ + if (empty($_SESSION['csrf'])) { + $_SESSION['csrf'] = bin2hex(random_bytes(16)); + } + return $_SESSION['csrf']; +} + +function csrf_field(): string +{ + return ''; +} + +function csrf_check(): void +{ + $t = isset($_POST['csrf']) ? (string)$_POST['csrf'] : ''; + if (!hash_equals(csrf_token(), $t)) { + http_response_code(419); + exit('CSRF 校验失败,请刷新页面重试。'); + } +} + +/** 统一的后台页面外壳 */ +function admin_layout(string $active, string $title, string $body): void +{ + $nav = [ + 'sites' => ['网站管理', 'admin/'], + 'groups' => ['板块管理', 'admin/?p=groups'], + 'engines' => ['搜索引擎', 'admin/?p=engines'], + 'settings' => ['系统设置', 'admin/?p=settings'], + ]; + ?> + + + + +<?= e($title) ?> · <?= e(site_name()) ?> 后台 + + + + +
+
后台
+ +
+ + 退出 +
+
+
+ + + $m, 't' => $t]; } +function post_id(): int { return (int)($_POST['id'] ?? 0); } + +try { + switch ($action) { + + /* ---------- 网站 ---------- */ + case 'site_save': { + $id = post_id(); + $gid = (int)($_POST['group_id'] ?? 0); + $url = norm_url((string)($_POST['url'] ?? '')); + $name = trim((string)($_POST['name'] ?? '')); + $short = trim((string)($_POST['short_name'] ?? '')); + $icon = trim((string)($_POST['icon'] ?? '')); + $visible = isset($_POST['is_visible']) ? 1 : 0; + if ($url === '' || $gid <= 0) { + flash('网址和板块不能为空', 'err'); + break; + } + $now = date('Y-m-d H:i:s'); + if ($id > 0) { + $st = $pdo->prepare('UPDATE sites SET group_id=?, url=?, name=?, short_name=?, icon=?, is_visible=?, updated_at=? WHERE id=?'); + $st->execute([$gid, $url, $name, $short, $icon, $visible, $now, $id]); + flash('已保存网站'); + } else { + $max = (int)$pdo->query('SELECT COALESCE(MAX(sort_order),0)+1 FROM sites WHERE group_id=' . $gid)->fetchColumn(); + $st = $pdo->prepare('INSERT INTO sites (group_id,url,name,short_name,icon,sort_order,is_visible,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?)'); + $st->execute([$gid, $url, $name, $short, $icon, $max, $visible, $now, $now]); + flash('已添加网站'); + } + break; + } + case 'site_delete': { + $pdo->prepare('DELETE FROM sites WHERE id=?')->execute([post_id()]); + flash('已删除网站'); + break; + } + case 'site_toggle': { + $pdo->prepare('UPDATE sites SET is_visible = 1 - is_visible WHERE id=?')->execute([post_id()]); + flash('已切换显示状态'); + break; + } + case 'site_move': { + move_sort('sites', post_id(), (string)($_POST['dir'] ?? 'up'), 'group_id'); + break; + } + + /* ---------- 板块 ---------- */ + case 'group_save': { + $id = post_id(); + $name = trim((string)($_POST['name'] ?? '')); + $display = ($_POST['display'] ?? 'grid') === 'tabs' ? 'tabs' : 'grid'; + $visible = isset($_POST['is_visible']) ? 1 : 0; + if ($name === '') { flash('板块名称不能为空', 'err'); break; } + if ($id > 0) { + $pdo->prepare('UPDATE groups SET name=?, display=?, is_visible=? WHERE id=?') + ->execute([$name, $display, $visible, $id]); + flash('已保存板块'); + } else { + $max = (int)$pdo->query('SELECT COALESCE(MAX(sort_order),0)+1 FROM groups')->fetchColumn(); + $pdo->prepare('INSERT INTO groups (name,slug,display,sort_order,is_visible) VALUES (?,?,?,?,?)') + ->execute([$name, slugify($name), $display, $max, $visible]); + flash('已添加板块'); + } + break; + } + case 'group_delete': { + $pdo->prepare('DELETE FROM groups WHERE id=?')->execute([post_id()]); + flash('已删除板块(含其下网站)'); + break; + } + case 'group_toggle': { + $pdo->prepare('UPDATE groups SET is_visible = 1 - is_visible WHERE id=?')->execute([post_id()]); + flash('已切换显示状态'); + break; + } + case 'group_move': { + move_sort('groups', post_id(), (string)($_POST['dir'] ?? 'up'), null); + break; + } + + /* ---------- 搜索引擎 ---------- */ + case 'engine_save': { + $id = post_id(); + $name = trim((string)($_POST['name'] ?? '')); + $slug = preg_replace('/[^a-z0-9\-_]/i', '', (string)($_POST['slug'] ?? '')); + $search = trim((string)($_POST['search_url'] ?? '')); + $param = trim((string)($_POST['param'] ?? 'q')); + $sugUrl = trim((string)($_POST['suggest_url'] ?? '')); + $sugType = preg_replace('/[^a-z0-9_]/i', '', (string)($_POST['suggest_type'] ?? '')); + $color = preg_match('/^#[0-9a-fA-F]{3,8}$/', (string)($_POST['color'] ?? '')) ? $_POST['color'] : '#2F6BF7'; + $active = isset($_POST['is_active']) ? 1 : 0; + if ($name === '' || $slug === '' || $search === '') { flash('名称/标识/搜索地址不能为空', 'err'); break; } + if ($id > 0) { + $pdo->prepare('UPDATE engines SET name=?,slug=?,search_url=?,param=?,suggest_url=?,suggest_type=?,color=?,is_active=? WHERE id=?') + ->execute([$name, $slug, $search, $param, $sugUrl, $sugType, $color, $active, $id]); + flash('已保存搜索引擎'); + } else { + $max = (int)$pdo->query('SELECT COALESCE(MAX(sort_order),0)+1 FROM engines')->fetchColumn(); + $pdo->prepare('INSERT INTO engines (name,slug,search_url,param,suggest_url,suggest_type,color,is_active,sort_order) VALUES (?,?,?,?,?,?,?,?,?)') + ->execute([$name, $slug, $search, $param, $sugUrl, $sugType, $color, $active, $max]); + flash('已添加搜索引擎'); + } + break; + } + case 'engine_delete': { + $pdo->prepare('DELETE FROM engines WHERE id=?')->execute([post_id()]); + flash('已删除搜索引擎'); + break; + } + case 'engine_toggle': { + $pdo->prepare('UPDATE engines SET is_active = 1 - is_active WHERE id=?')->execute([post_id()]); + flash('已切换启用状态'); + break; + } + case 'engine_move': { + move_sort('engines', post_id(), (string)($_POST['dir'] ?? 'up'), null); + break; + } + + /* ---------- 设置 ---------- */ + case 'settings_save': { + $map = [ + 'site_name' => trim((string)($_POST['site_name'] ?? '')), + 'subtitle' => trim((string)($_POST['subtitle'] ?? '')), + 'default_engine' => preg_replace('/[^a-z0-9\-_]/i', '', (string)($_POST['default_engine'] ?? '')), + 'primary_color' => preg_match('/^#[0-9a-fA-F]{3,8}$/', (string)($_POST['primary_color'] ?? '')) ? $_POST['primary_color'] : '#2F6BF7', + 'open_blank' => isset($_POST['open_blank']) ? '1' : '0', + ]; + $st = $pdo->prepare('INSERT INTO settings (k,v) VALUES (?,?) ON CONFLICT(k) DO UPDATE SET v=excluded.v'); + foreach ($map as $k => $v) { $st->execute([$k, $v]); } + flash('设置已保存'); + $back = 'admin/?p=settings'; + break; + } + case 'password_save': { + $old = (string)($_POST['old_password'] ?? ''); + $new = (string)($_POST['new_password'] ?? ''); + $u = current_user(); + $st = $pdo->prepare('SELECT password_hash FROM users WHERE id=?'); + $st->execute([$u['id']]); + $hash = (string)$st->fetchColumn(); + if (!password_verify($old, $hash)) { flash('原密码不正确', 'err'); $back = 'admin/?p=settings'; break; } + if (mb_strlen($new) < 4) { flash('新密码至少 4 位', 'err'); $back = 'admin/?p=settings'; break; } + $pdo->prepare('UPDATE users SET password_hash=? WHERE id=?')->execute([password_hash($new, PASSWORD_DEFAULT), $u['id']]); + flash('密码已更新'); + $back = 'admin/?p=settings'; + break; + } + + default: + flash('未知操作', 'err'); + } +} catch (Throwable $e) { + flash('操作失败:' . $e->getMessage(), 'err'); +} + +header('Location: ' . url($back)); +exit; + +/** 上/下移动排序(同组内交换 sort_order) */ +function move_sort(string $table, int $id, string $dir, ?string $groupCol): void +{ + if ($id <= 0) return; + $pdo = db(); + $st = $pdo->prepare("SELECT * FROM {$table} WHERE id=?"); + $st->execute([$id]); + $cur = $st->fetch(); + if (!$cur) return; + + $where = $groupCol ? " AND {$groupCol}=" . (int)$cur[$groupCol] : ''; + if ($dir === 'up') { + $sql = "SELECT * FROM {$table} WHERE sort_order < ?{$where} ORDER BY sort_order DESC LIMIT 1"; + } else { + $sql = "SELECT * FROM {$table} WHERE sort_order > ?{$where} ORDER BY sort_order ASC LIMIT 1"; + } + $st2 = $pdo->prepare($sql); + $st2->execute([$cur['sort_order']]); + $nb = $st2->fetch(); + if (!$nb) return; + + $upd = $pdo->prepare("UPDATE {$table} SET sort_order=? WHERE id=?"); + $upd->execute([$nb['sort_order'], $cur['id']]); + $upd->execute([$cur['sort_order'], $nb['id']]); +} diff --git a/admin/fetch_meta.php b/admin/fetch_meta.php new file mode 100644 index 0000000..c7ac78f --- /dev/null +++ b/admin/fetch_meta.php @@ -0,0 +1,36 @@ + false, 'msg' => '缺少网址'], JSON_UNESCAPED_UNICODE); + exit; +} + +$meta = fetch_site_meta($url); +$domain = domain_of($meta['final_url'] ?: $url); +$iconFile = ''; +if (!empty($meta['icon'])) { + $iconFile = download_icon($meta['icon'], $domain); +} +if ($iconFile === '' && $domain !== '') { + // 兜底再试一次根目录 favicon.ico + $iconFile = download_icon('http://' . $domain . '/favicon.ico', $domain); +} + +echo json_encode([ + 'ok' => true, + 'url' => $url, + 'name' => $meta['name'] !== '' ? $meta['name'] : ($meta['title'] !== '' ? $meta['title'] : $domain), + 'short' => $meta['name'] !== '' ? $meta['name'] : $domain, + 'icon' => $iconFile, + 'icon_url' => $iconFile !== '' ? icon_src($iconFile) : '', +], JSON_UNESCAPED_UNICODE); diff --git a/admin/index.php b/admin/index.php new file mode 100644 index 0000000..ebfe52e --- /dev/null +++ b/admin/index.php @@ -0,0 +1,258 @@ +query('SELECT * FROM groups ORDER BY sort_order, id')->fetchAll(); +$engines = $pdo->query('SELECT * FROM engines ORDER BY sort_order, id')->fetchAll(); + +$SUGGEST_TYPES = ['bing_osjson', 'baidu_sugrec', 'google_firefox', 'sogou_ajaj', 'so360', 'ddg']; + +function flash_html(?array $f): string +{ + if (!$f) return ''; + $cls = ($f['t'] ?? 'ok') === 'err' ? 'alert err' : 'alert ok'; + return '
' . e($f['m']) . '
'; +} + +function toggle_form(string $action, int $id): string +{ + return '
' + . csrf_field() . '' + . '' + . '
'; +} + +function move_form(string $action, int $id, string $dir, string $label, string $p): string +{ + return '
' + . csrf_field() . '' + . '' + . '' + . '' + . '
'; +} + +ob_start(); + +if ($p === 'sites'): + $rows = $pdo->query('SELECT s.*, g.name AS gname FROM sites s JOIN groups g ON g.id=s.group_id ORDER BY g.sort_order, g.id, s.sort_order, s.id')->fetchAll(); + ?> + + +
+
+

新增 / 编辑网站

+ +
+
+ + + + + + + + + +
+ + + +
+
+
+ +
+

网站列表()

+ + + + + + + + + + + + + + + + +
图标简称(展示)名称网址板块显示排序操作
+ +
+ + + +
+
+
+ + +
+

新增 / 编辑板块

+
+ + + + + + + +
+
+
+
+ + + + query('SELECT COUNT(*) FROM sites WHERE group_id=' . (int)$g['id'])->fetchColumn(); ?> + + + + + + + + + + +
名称展示方式网站数显示排序操作
+ +
+ + +
+
+
+ + +
+

新增 / 编辑搜索引擎

+
+ + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + +
名称搜索地址联想类型启用排序操作
■ ()已启用' : '未启用' ?> + +
+ + +
+
+

提示:联想接口需填写对应平台的地址,解析类型可选 bing_osjson / baidu_sugrec / google_firefox / sogou_ajaj / so360 / ddg。

+
+ + +
+

站点设置

+
+ + + + + + + +
+
+
+
+

修改管理员密码

+
+ + + + +
+
+
+ query('SELECT COUNT(*) FROM sites')->fetchColumn(); +$rows = $pdo->query('SELECT id, url FROM sites ORDER BY id LIMIT ' . $limit . ' OFFSET ' . $offset)->fetchAll(); + +$updated = 0; +$failed = 0; +$now = date('Y-m-d H:i:s'); + +foreach ($rows as $row) { + $url = norm_url((string)$row['url']); + $meta = fetch_site_meta($url); + $domain = domain_of($meta['final_url'] ?: $url); + $name = $meta['name'] !== '' ? $meta['name'] : $meta['title']; + $iconFile = ''; + if (!empty($meta['icon'])) { + $iconFile = download_icon($meta['icon'], $domain); + } + if ($iconFile === '' && $domain !== '') { + $iconFile = download_icon('http://' . $domain . '/favicon.ico', $domain); + } + + $sets = ['updated_at=?']; + $args = [$now]; + if ($name !== '') { $sets[] = 'name=?'; $args[] = $name; } + if ($iconFile !== '') { $sets[] = 'icon=?'; $args[] = $iconFile; } + // 注意:这里绝不修改 short_name(用户自定义简称) + + $args[] = (int)$row['id']; + try { + $pdo->prepare('UPDATE sites SET ' . implode(',', $sets) . ' WHERE id=?')->execute($args); + if ($iconFile !== '' || $name !== '') { $updated++; } else { $failed++; } + } catch (Throwable $e) { + $failed++; + } +} + +$next = $offset + count($rows); +echo json_encode([ + 'done' => $next >= $total || count($rows) === 0, + 'total' => $total, + 'offset' => $next, + 'updated' => $updated, + 'failed' => $failed, +], JSON_UNESCAPED_UNICODE); diff --git a/apple-touch-icon.png b/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..84a91bc51aa5a1a49c62e9c21a19a3e680f04f33 GIT binary patch literal 4302 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 aZ$(V(D&O6oOM!k+!0eAW9j-Y3 literal 0 HcmV?d00001 diff --git a/assets/css/admin.css b/assets/css/admin.css new file mode 100644 index 0000000..9c24339 --- /dev/null +++ b/assets/css/admin.css @@ -0,0 +1,74 @@ +/* 简航 SimpleNav —— 后台样式 */ +body.admin { background: #eef0f3; color: #333; font-size: 14px; } +.topbar { + display: flex; align-items: center; gap: 16px; padding: 0 20px; height: 56px; + background: #fff; box-shadow: 0 1px 4px rgba(0,0,0,.06); position: sticky; top: 0; z-index: 50; +} +.topbar .brand { font-size: 17px; font-weight: 700; } +.topbar .brand a { color: #2F6BF7; } +.topbar .brand span { color: #999; font-weight: 400; font-size: 13px; margin-left: 6px; } +.topnav { display: flex; gap: 4px; flex: 1; } +.topnav a { padding: 8px 14px; border-radius: 8px; color: #555; } +.topnav a:hover { background: #f2f4f7; } +.topnav a.active { background: #2F6BF7; color: #fff; } +.topright { display: flex; align-items: center; gap: 10px; color: #666; } +.wrap { max-width: 1140px; margin: 20px auto 60px; padding: 0 16px; } + +.card { background: #fff; border-radius: 12px; padding: 18px; margin-bottom: 18px; box-shadow: 0 2px 10px rgba(0,0,0,.04); } +.card-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; } +.card-head h2 { font-size: 16px; margin: 0; } + +.grid-form { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 16px; } +.grid-form label { display: flex; flex-direction: column; font-size: 13px; color: #555; gap: 5px; } +.grid-form label.chk { flex-direction: row; align-items: center; gap: 6px; } +.grid-form .span2 { grid-column: 1 / -1; } +.grid-form .row { display: flex; gap: 8px; } +.grid-form .row input { flex: 1; } +.grid-form input[type=text], .grid-form input[type=password], .grid-form select { + height: 38px; padding: 0 10px; border: 1px solid #d9dde3; border-radius: 8px; font-size: 14px; width: 100%; +} +.grid-form input:focus, .grid-form select:focus { outline: none; border-color: #2F6BF7; } +.form-actions { display: flex; align-items: center; gap: 10px; } +.hint { color: #999; font-size: 12px; } + +.btn { display: inline-flex; align-items: center; gap: 6px; height: 38px; padding: 0 16px; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; } +.btn.primary { background: #2F6BF7; color: #fff; } +.btn.primary:hover { background: #2456cc; } +.btn.ghost { background: #f2f4f7; color: #444; } +.btn.ghost:hover { background: #e6e9ee; } +.btn.block { width: 100%; justify-content: center; } + +.alert { padding: 10px 14px; border-radius: 8px; margin-bottom: 14px; font-size: 13px; } +.alert.ok { background: #e8f6ee; color: #1a7f45; } +.alert.err { background: #fdecec; color: #c0392b; } + +.tbl { width: 100%; border-collapse: collapse; font-size: 13px; } +.tbl th, .tbl td { padding: 9px 8px; text-align: left; border-bottom: 1px solid #f0f1f3; vertical-align: middle; } +.tbl th { color: #888; font-weight: 500; } +.tbl tr:hover td { background: #fafbfc; } +.tbl .muted { color: #999; } +.tbl .ell { max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.tbl .nowrap { white-space: nowrap; } +.mini { width: 26px; height: 26px; border-radius: 6px; object-fit: contain; background: #f4f5f7; } +.mini.letter { display: inline-flex; align-items: center; justify-content: center; color: #fff; font-size: 13px; } +.inline { display: inline; } +.ico-btn { border: none; background: #f2f4f7; width: 28px; height: 28px; border-radius: 6px; cursor: pointer; color: #555; margin: 0 1px; } +.ico-btn:hover { background: #e6e9ee; } +.ico-btn.danger { color: #c0392b; } +.ok-dot { color: #1a7f45; font-size: 12px; } + +/* 登录页 */ +.login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; } +.login-card { width: 340px; background: #fff; border-radius: 14px; padding: 30px 26px; box-shadow: 0 10px 30px rgba(0,0,0,.08); text-align: center; } +.login-card h1 { font-size: 20px; margin: 8px 0 2px; } +.login-card .sub { color: #999; font-size: 13px; margin: 0 0 18px; } +.login-card input { width: 100%; height: 42px; margin-bottom: 12px; padding: 0 12px; border: 1px solid #d9dde3; border-radius: 8px; font-size: 14px; } +.login-card input:focus { outline: none; border-color: #2F6BF7; } +.login-card .btn { height: 42px; } +.login-logo { width: 56px; height: 56px; } + +@media (max-width: 720px) { + .grid-form { grid-template-columns: 1fr; } + .topbar { flex-wrap: wrap; height: auto; padding: 10px 14px; } + .tbl .ell { max-width: 120px; } +} diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..2173971 --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,116 @@ +/* 简航 SimpleNav —— 样式(复刻 n.yangyq.net 的灰底白卡片风格,去 Bootstrap 依赖) */ +:root { --primary: #2F6BF7; } +* { box-sizing: border-box; } +body { + margin: 0; + font-size: 14px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; + background: #d8d9da; + color: #333; + overflow-x: hidden; +} +a { text-decoration: none; color: inherit; } +.container { max-width: 1140px; margin: 50px auto 40px; padding: 0 15px; } + +/* ---------- 搜索 ---------- */ +.s-search { position: relative; z-index: 2000; margin-bottom: 10px; } +.search-type { margin-bottom: 0; } +.s-engine { + display: inline-block; padding: 6px 14px; margin: 0 2px 0 0; + background: rgba(255,255,255,.45); border-radius: 5px 5px 0 0; cursor: pointer; + font-size: 14px; line-height: 20px; transition: background .2s; +} +.s-engine span { color: var(--ec, #555); } +.s-engine:hover { background: rgba(255,255,255,.75); } +.s-engine.active { background: #fff; } +#search-form { position: relative; } +#search-text { + width: 100%; height: 50px; line-height: 50px; text-indent: 14px; font-size: 16px; + border: none; border-radius: 5px; background: #fff; outline: 0; + box-shadow: 0 0.5rem 0.625rem rgba(212,212,212,.3); transition: box-shadow .3s; +} +#search-text:focus { box-shadow: 0 0 24px 0 rgba(50,50,50,.08); } +#search button.submit { + position: absolute; top: 7px; right: 7px; width: 60px; height: 36px; + background: none; border: 0; border-radius: 3px; cursor: pointer; outline: none; +} +#search button.submit::after { + content: ""; display: block; width: 16px; height: 16px; margin: 0 auto; + border: 2px solid #c9c9c9; border-radius: 50%; position: relative; +} +#search button.submit::before { + content: ""; position: absolute; right: 20px; bottom: 9px; width: 7px; height: 2px; + background: #c9c9c9; transform: rotate(45deg); +} +#word { + position: absolute; list-style: none; top: 55px; left: 0; width: 100%; margin: 0; + background: #fff; border-radius: 5px; z-index: 20000; padding: 10px; + box-shadow: 0 0 10px #bbb; display: none; +} +#word li { height: 35px; padding: 0 8px; line-height: 35px; cursor: pointer; font-size: 16px; border-radius: 5px; } +#word li:hover { background: #eee; } + +/* ---------- 板块卡片 ---------- */ +.mylist { + list-style: none; margin: 16px 0; padding: 12px 20px; + background: #fff; border-radius: 15px; box-shadow: 0 0.5rem 0.625rem rgba(212,212,212,.3); + display: flex; flex-wrap: wrap; +} +.mylist > li.title { + width: 100%; margin: 0 0 10px; padding: 0 0 8px 10px; height: auto; + font-size: 16px; font-weight: 600; line-height: 24px; + border-bottom: 1px solid #eee; position: relative; +} +.mylist > li.title::before { + content: ""; position: absolute; left: 0; top: 3px; width: 4px; height: 18px; + background: var(--primary); border-radius: 2px; +} +.mylist > li.site { margin: 2px; } +.mylist li.site a { + display: flex; flex-direction: column; align-items: center; width: 100px; padding: 10px 4px; + border-radius: 10px; transition: background .2s; +} +.mylist li.site a:hover { background: #f0f1f2; } +.mylist li.site .icon { width: 45px; height: 45px; border-radius: 10px; background: #fff; object-fit: contain; } +.mylist li.site .ico-letter { + width: 45px; height: 45px; border-radius: 10px; color: #fff; font-size: 20px; font-weight: 600; + display: flex; align-items: center; justify-content: center; +} +.mylist li.site .name { + width: 100%; margin-top: 6px; text-align: center; font-size: 13px; color: #555; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} + +/* ---------- 合集标签页 ---------- */ +.collections { margin: 16px 0; } +.collections-title { + font-size: 16px; font-weight: 600; padding: 0 0 8px 10px; margin: 0 0 10px; + position: relative; +} +.collections-title::before { + content: ""; position: absolute; left: 0; top: 3px; width: 4px; height: 18px; + background: var(--primary); border-radius: 2px; +} +.tabs { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; } +.tabs .tab { + border: none; background: rgba(255,255,255,.65); color: #444; cursor: pointer; + padding: 7px 16px; border-radius: 18px; font-size: 14px; transition: all .2s; +} +.tabs .tab:hover { background: #fff; } +.tabs .tab.active { background: var(--primary); color: #fff; } +.tab-panel { display: none; } +.tab-panel.active { display: flex; } + +/* ---------- 页脚 ---------- */ +.foot { text-align: center; color: #8a8a8a; font-size: 12px; margin: 24px 0 0; } +.foot a { color: #8a8a8a; } +.foot a:hover { color: var(--primary); } + +/* ---------- 响应式 ---------- */ +@media (max-width: 767px) { + .container { margin-top: 24px; } + .mylist { border-radius: 10px; padding: 10px 12px; } + .mylist li.site a { width: 25%; } + .mylist li.site .icon, .mylist li.site .ico-letter { width: 40px; height: 40px; } + .mylist li.site .name { font-size: 12px; } +} diff --git a/assets/icons/icon-128.png b/assets/icons/icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..7040d6e63b63692adf704484e1061319c0254562 GIT binary patch literal 4455 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 literal 0 HcmV?d00001 diff --git a/assets/icons/icon-144.png b/assets/icons/icon-144.png new file mode 100644 index 0000000000000000000000000000000000000000..178f52103e79378f86da6e3a9366fbeffe90f98f GIT binary patch literal 5106 zcmVz>~q)9|URCt{2ookd7<(bFpL$do}vmZ8D&1SRb?AfflF`lDNcXd@) z4>xsPFy1{j#Gs<$pZQ~z`R=gjHp0jBH6^FHtGc?4mYY2*|*ORm5u zS)&uOI=w5av7csj_Doi%>$4i&sp#}jR-;E0o%Sd?gPLhi{WbjFPUpS&GaCLZz03Kt z$}nFj<*V=EMm{-F;FzRqe5)BV{pTane+EV)d^=QKDw zd8jR-_6q#9qLXVCoh{1g^qpLV0XisXlvCh2*|cu5E31*8AW-;kjQVp*bkdNd=B_7|Zx_q(x5>kdK$qv?FLM>RNvW_Eiq6Q%(8LxV?L^CE zLb4iNp4I5r@OMgJ3Ru|+{7X(}b8|ZTBv-Ma=GKHKdy2^+StE0l5}cEO#2Xw9L`7#y zl?tPM;0z^rX#XLO$tC)1MkSXfAo)~#1uo82*t!#;8N%>rIIQ(qD$_NYk~Jm)$)!eg zfuiW_Hv@w+Jm8W0yF^!IH7lEdG>DWEd=ZDLT!q;QJoF@xh%J}tuW}mvV*=7i5^sl3 zXLXpC)7i1pgEI^xPkEqdM0%t$os6GZ0utsqI<)05fh3jbR!%4X^oZDQ~Ng|ItC`bfB+NzYy^M&D{To>Fr zc&?McBR3U=NS{kqk6fY?g`p~3s#hxPhs}YLL>}1bIm;r7CvXgu8#n08(a&2X4gI&e)KGZad*cRo<>e& znW9Q=5L!X)I$U2nx4L~U5Cr! z8hJ3{AUHv5?KT_6-xDu*`bQ#JC07cuO_+67jT=1e6$U?k-UgFEXfiBthBifu5j=g7 zs8?34Q9_)Q?Sii;Iy(|Kc&_{&gN^S5A9YTrS9jWQ`Hc9$(+{GM36Fu;v7;{(qL>qG_ke5CN;l6f3gm~WZS;Ez2J8y)Eem^hck2A;m0 zh^&uv+ap(DKZzSWILrF;F^HYIs{psoXYqlj&kLj5RZrmI1W{!0;7|q`l}-pfX^z{G zjJQUg!6Awo9s-%7)e~A$jc0L{3fmYbc<_OA_kwR(1mKO`HvIBs8)iSrVA{`X#;+fJ z*@i8; zV`jjolnUECRN&e1p$+5j^?&td{5=$QerWrKC~A26`OQGC3_16e0Pu)f9f4IQYu0t*kch%z>dE^*FgzLPR2ihzmRo`>9Wq)7jc2@_0YbGcp2* zxWZFQs8@S}r@aEB5_r7BBS!!ce|Tz^idN1wKuU!zOL886@W^$Ks1R7UEm(+xf~P(s zd)!MkRF)*n{^6184>(AO0)8wO1RlvnB*HpFUWIi5ZukKS2p-2f2Iy`aT#1$aM-q4>7f}eO

d$hw-Y9 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 UhJtZXTL1t607*qoM6N<$g7E{kdjJ3c literal 0 HcmV?d00001 diff --git a/assets/icons/icon-150.png b/assets/icons/icon-150.png new file mode 100644 index 0000000000000000000000000000000000000000..aa0f3b44865631365b54033191f061857809ed89 GIT binary patch literal 5320 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 BAAJA- literal 0 HcmV?d00001 diff --git a/assets/icons/icon-180.png b/assets/icons/icon-180.png new file mode 100644 index 0000000000000000000000000000000000000000..bd2ac317ba091178ba35af411dd8b04c894b45ac GIT binary patch literal 6516 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 literal 0 HcmV?d00001 diff --git a/assets/icons/icon-192.png b/assets/icons/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..3ca3014fe486417329c854ec273355953c4afae0 GIT binary patch literal 6907 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 literal 0 HcmV?d00001 diff --git a/assets/icons/icon-256.png b/assets/icons/icon-256.png new file mode 100644 index 0000000000000000000000000000000000000000..a4dea95526ec1f9dfd5f3ecc49ae04bafedfd9b0 GIT binary patch literal 9572 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 literal 0 HcmV?d00001 diff --git a/assets/icons/icon-48.png b/assets/icons/icon-48.png new file mode 100644 index 0000000000000000000000000000000000000000..95e7b7fd90c1eb7066aea856e737f216e1e9c8d1 GIT binary patch literal 1737 zcmV;)1~&PLP)z>-dr3q=RA_<4nQLqmRTO~dhmZf7fCPV5F+@qXJF`1mAsGE{BnU*K z2}A=(cXnrHXG@isVuGLvSeoF2#2BN16c9BU3jUx)N&&T?R0s;j81aV!CVk+%?ww=K zY?pn^o!PtXZav9KHnW}XckVs+ysjWLKqLe1lMV8$Y|vggM~@{9awVA~|HuY$Q1P?> zOsB^ZI_*v9j0ZRqFnG& zxvGv*zuV*~;e}tkk;3O^Jm8hjPJ7Uv=Y-b^JCVq`Pw}mM7qq1EbgUlu6Qlg_MLBiJ zgT-E$buLk&n7&^1%|qI*Euk@2G4

%%WmVO}avyhwOPv^jKYX7J@k92^Q7F*r&=&avSOYJ15H%oV0OsnFGD zg+AVue`1phkH1no#wT-loQ+J^VQj$92I@cora1ES*m=Dnoi-M7CYLHLXfF~zzr$T?!OIw@>K%4Ovn z{L!Z4T>@?y1Acz#9l`}Kst!E8)vfsLLqjwGUr@`cFrd5*WEoITsC<40fxUy&*XOqL z!^g8>22{gO8Blbg>K^vJ{>n3eB{2^?X3^aH1ojM2`1-u(>&M5b|4h!+hL2~%T&Nm; z%7v0Sa+TG9vkozq#8L1s^b)vsBfPo6@9;oI!6$VGey-{r*7W$9H>mz0*6A_U^Z*-h z>|v|JXz3hO!mrpy;9nbR3jWRVtu7N@R2}&AR4|N+jMbmgBLdDU!$u$2fLF`GAv7ZB z^G8Sc+sgitV1^Y{2M#dG?9LC|FnEYzBEvyz)ZC`}Fc)@$1`N14&dm=u#vuUT$4Gu( zoS~ucQ!cbpz=9~5bG@*k6DBWKC5mak+=wlG6t*5H>Fa$(B>~;CpVVCWfeRE8S~z@6 zvAl;f6LbBF6sN>dNeYd?-j!!3ctd5>$`4(nAnQ@^HngU#)1`huCrYg()t+}@@Iq}P zW&=Lv8(?mzXen{TT`SI~jA$cf3qF=MC#8!$VQ$vuvjuM5B4zivneaXR@eH)Xp_`XrI@-a@~hcqD5rh=eb>lDo{mT;Q8k`bMzaBTTS7PSm&ZB-UX)DbypP>a69PRhASR za4f#U1iy=_ZQ9r?@3kuCi*>=@-U;1aEvk-NPm;nYw5G@PYj`X-Q1LUC#1T~`gsyzr zTqn##@MZ)r+<21|YkI(HMrubJbF6N+36o^TdH^pMW({63M`9by7NL={v>WaaRVN$I z+T+cFZ&l1&F~!`BH$_6T1TPoFto;^_xEltaHYZ!t=8n(|+YG_^d_3c%;~D!54qEGn zw>&I~+Z3~7p$7NN8aPW9_p-rR`OV f)=$i7#Bcuxc6&!W+n=DM00000NkvXXu0mjfd@WDn literal 0 HcmV?d00001 diff --git a/assets/icons/icon-512-maskable.png b/assets/icons/icon-512-maskable.png new file mode 100644 index 0000000000000000000000000000000000000000..07364499d1a35ca4bd4de3c1a03fdc350f244b07 GIT binary patch literal 15445 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 literal 0 HcmV?d00001 diff --git a/assets/icons/icon-512.png b/assets/icons/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..56d7e9cdf78e6b760871d243822fc43ae12e2ba7 GIT binary patch literal 23442 zcmYhjc|4Tg8#jK>*q10m*>}k@N)lqG5JE&qXtYQrJ1OEGT99N5Wr-vep(IOaC}qo* zqOy!+?CV&Enfv~o>GSFnA3T2M&Vdl*dW7{4v;BebAqbL%EKClaxRy1W&GRtJA$5(<*8hmB zt!J12hfe=HzFDz}L9-5WUFv7-F=t|)-p}PWF~3||sn6pQtth+eg)I9g zQ))xLX?PSL`A!V`1;0VwJ-Dg%xz7<5ZyS=6e-xWG5jQ}A!7RB-Ru2gViml5!$EBXP zB0Q>U2UIR`=qhWVxAUoMewG;qjcc-(=$&!%7KM@wc@H|?+IeVwbo)lSt*&FwT;-O;q0K`Ov^rR5p*a@BAMzQnm1|_BQ_x+|8fLzW`iKD zAnS+(&5a|iw1i_0QA<;KxXtOR+b``E+xmGa#VVZ7+)K%&3>7CEjDV9!Hp$3hkOQRH6@#u&2UGz?+)&`~6GWg0XtC}-B7znD* zwKzYieNJsix5v@%NGtE0wKFnxXGT}h=lIl)Pf6&0eU2g^;ORWcuSe7BZZ%CCOm+hVXd@J<*2tKQ1eXjSGAiYQtwHAsDRcw8nqkbJ&ct~=O4atogg50;hlxMn? zwe{n@Iq)_P49{;O{q;}l=N%`|FZZt?(qVrTZ;{u0CH z^-aV-F`g(@xF82_p;BxVTTk1RjZz-%qB|B&%-q+NgP>h^UW`t8GmrY6ZlzJgW5Gb} zQHt+jTiVd}Hja^D`5-O`BG`@CPx4LjG8G+o5FoEtrcyXRDI~E1^v< z<-;Fuu1P`Ay(9eo&(!txJvb`!)gm)XzGv(XZ@b^N?%ee679G8hi)X|f(SLx`` zyV$ZdjR7~X>P`16w?8~CVehebTTptng$!+6RDTpdP<$KtG*S}PVNotD4PwYSi}VS6-_iU}EZ9;gO6Bu#?vAUU&v8S!Qs>l` zZ6=Rusd;c%M&lw*AOm)0RLZxKdal@HU13i?2r8F#cw!DtLH?^;{k@CEU_dmZzud%@ zmUaFYS^wsX|d_U2H_#614O~|HSr4LzSf>bz+l4L`!yYLgkDNH#wx&fdTfkOAZbmrP#>(o!)C!GrWfz zx+tDKSZ@FRu>Gn3-zue#8JpAGCeOP(7rEJ?d&}QH#9mT6`7QK+lmAFVFKA=iMoeMe zt6L!G&JB^$sw@?6j`?yk)&k$<;OtSdh5UkJ-YClMB*X@BDtdIUmx~;v82xXC;8Zwg zS17Kjb*SMQI~J;tJzj7e1is?Bk!VKsGXc7UEI6qcH0^~cddEx_BFs?Hqwj?nn%*3k z(N5f{oor4Um*vs;Htf~fiR|p=Z=MU>?Dd2^;mN-v%O4(yyz(Kh1B+Wq(R5$Y=JDH3 zD&H6Tasz@mlS;ZGCXX%%C9KQAJ}-@tMD#)%)~2a#?({&X5=7wJ>D5hsrq;0K#{c9n zM*1@Z%^ys9UE^lUb*{lXJV~*0w7t!sQ+J2Cw-d)J&vgH+yu?Qawbfo!Y zoc_1eVT;p?Pif<6`lAW0uz4(@8`gBZ`u7^dmMhB&HLO_gfCU~eR>T|GN*QP+ql)!Q zjZEy*wq@11@tu%+Vt!k>(|e)X*8lb-nsJ=2d#18p;!TvUw4ghrSfFi_W9i(G6{PfH zF%C=wRuZJ^R;&3L-`E=-xv9qm{k(VLk${C$R0#clPC70^ZuLen=^RIgl1;kt;;d=( zT5uqUEby^r&;zE8$flAn$q%D?r*d(Dcofn~dte&BYS1&v8RRuA1xw z;a}%?nUr#oflgc~I0Pe!cpwj|K_xpMOy1?>{~Ur8lvqbnt-*|u2V6)=fW8EDlCeeV zjt4n?u|&{%aBy#$q#31@MO8DB!4M8Gq)nP86UETAV6UZ|A#5edX;mC1+Bij}uPd=4 z7E2VvQM?u1*9v<&UM$O(fQ9%8Wrw2+irtmSqre`Kz#e7M46|KmJX=(eu*bYPH$;lq zn(}sJ)#!T1;yf;@fU9y>F#o_57rjjpjsMfzI2)BcxV*<4BCVKME`J6Zea2k_8Vx1l z;najyvIvc$IA=3>7*&VKu5t8zS4|-_X zn)kFPH8+~L0L)(t%-?N{%qGFLKe_wvi)H_<&iMqDr`{|pKUXN^`V|)f78(Gi1ELGi zeUrJrimYW4j9D8rkz<$Yjv)^Ko&14LEc;N&a?)`w%Sv3H}Ba0&v$;VjR{OUfn?q+pPAG9w%NK7iFgkAIh zOd(>l8`h&zRCpHhOxK>;BYq^qIX?AUfJrTOaqluNG|#bdX<#zWvZBZq-~!S#CwoL9Z@69T`Lh6gA7)3Q zzx^Z;6R=enky@-@lx|>{wsIY|92goB>0?O?u4dA z+TgCWKxvYOdD^A^RLa~B}xjN%1^I6 z819Km@65!=&By7P%IFr0`u8Rc z2BghuLBhpXrg$so$R;2gg`FP9`@zp5er%vyZOqg%=3vh6`|DXFfpL9WAc@A?$=3$wtL)oF0;%SoQhU@vhVx*UVL!FB!0;C=!v4F@|Zql zeSu2N7v1-~*fx468Q*+Oyj-Ss-#S90{N(XFr-YK95W23~i4-n?2;66l6kfKr~-sDZI-0gT{D|=C5lGplAy8dXX<&2KQtKz;1 zoK5e=V8Kgq>%SY58z%cW%hxOt@uQUTV+0MyAU^Pg-jw958dhwxSrU%~6LgJEJq_N! zv+4KlAHt8n`hH%+rs8*_%0XJSI{>{KTa}8W)6Q}5LX>H9nxJYX(OR^Zjq>-*;ixtb{Q|^ zQ#Rki1wv%*rCbo$UKA$PR2{KlI*fFOTU;Mm8KPqN8E1xlPY#lz3`BU@6_g#G^sK9e zo~PR=YC-Zmn2Uyi!U^I>1-gl{nqU3 z)HbO^w09iiDih5SDF?FO@dJMnh=scw*X`LhK7GsagJS#)TfRL8%)D@8>YsL zkvj~JdndcY-22pLn`h|^Ih)3d% ze)^{SaOofoKUPah1BZXVy7Qr+Wt@YX5TT#ECAL1uM<#NAa!HrXe{0*9)W?VGfBU=D zIlU;2oq)_vvRA#k)_`tPyaKiCoz1SmMib{=Fg)UjgSS>92FJ0Q)o+y}L7cR(;gjr~ zRp~k`gRrUf?N0!Urrp^Zo%nzfRSG z5i=^zZF?;xAdGKcBxo;aZBn#!Yh>i~b%@=DeEez0%S3!lkW!pUx1OkwBQq-JsQVIM zgiB;Nl8D#R<*CmgzuKl&_2XJYynh%!|IPIi8;5`Min7A{cnK)<*z{7MW+LK8xeNN@ z#Mg&eEph(thHO?2sRqRlo1OtCku2^8CFF60Joe+Q?5mfxaEKx0kaQatIfGUlP#u@{fW}ReG)Y`Wfpv9@2ufG7Y zjpo040#wI@4~h`2?-24P-d~knecPm_eln2;k~=6^Wr;!%-AfrRG#f&FedaOU8}oVa zF{h}6byEt_eOT~+Vy4k>-=nckCV|Va+?iwVFKs>$8E6A#6X-*;;!BX= z@D1olvS{{K*6(Oe(S?@F4M+1r(!Iq;I?!=$lIvHWU@fTbrx_HDmgVuK%&FcJke*SvD9CClXM&+x( z#V^5!nCHF{PZq&eS`UGGpn`le^bFEH6(l@hwC`@W$Hs#!7v%L=f)2X6gaqVeN*8sj zI&lX5{2Hl*A2@I&&F1-Ok*@ZHzKboq`D`w%y>JG07Gw`CJ?G^v0cu=Jx`!Yg6qkQN z%G9hYI)Xo_d-2f%*WITbTPDhCSYbXeB{JRu8uiU*@Wm_ESHH*>*vBeuStVdJ<9hGB z=`(nMe&gFb&SWKw;Tvkerrfb@SsBxA4ON-GJXdaoa8ycnM}_aGlzFG@kYAeruOtRW zFhW=5dMUUnom{33H9Z`r%}b2v-0gdRMQ5j}b_&EoDT>ozE0ZC*7OSKVvvaXa9qwd+ zb0deY#-dpgH$&qdxh&sIf+<@|=>rn&N7*xy*!|+{nl8pM_#~1^F4cxFl_vLwClJ?J zneVY9`QZo3+WCkUK{F)Wc;=CqR3ylFLC5~nZ8{x$w?nWh{G|v03yi!uEZ0H9Z?$>u zH(BT4vR(1B6vpfWn6f?=lrtAzazt4xOOfRAK%X<3ac;#K@^l6rp+N#?kJ&qKij9)M>E>*c3i?} zT(h2EeO+~9yvB1eF53l(KQO{ijwpMgn*C2gyH>KT4HR{ZGTB&E&b4M;G>i z{^)!&xmx{3%@MB0UvO8QSyCEar)OuZW|REg7-ioz@>Ag_Z?+GA*qUuFGrn55H-C4K z(EmO=So7k@rbTUKjwKR5ku}yMTsoc2evsKGKC5$^nDPGB%)hWuYjC^2=%`k29&G7+ zNW=?a*d6I7Uq844h8e~aXF#QCETr|L6fOAICP%BEt(n>J1K$c&CiHZ<3IYSq>fncKJytqBf9q-MMkm4fI?mOFv-C$X1Ht8?v8 zL=XF#(1s!h9;YTBA6y1Wx;Cmf;=(PS9^?h1n`Z~NB4(@^-z&klXX{VFmzo)z^rbk~ z=ha29I@dzwPY?brqc+oASj$=58>N3o+zjNCwt&*utdig>=fvCvh%Fl1!mOU`0dSQpj7&*-At#r{MGf^a z2UG|l;PV}< zLKcS~WkAQ$;Et7?cM=CMlsL;!9QPHWY{*^>JxO^+s?FqkRR5DVT;GAmsNM)>V!>8V zGv1fbp9<&B2aqONxyE_-z>aU(4@AD+V$eOd(4WqBYIrMR9`cYFgCE>T_J{R_HM+&@ z9bb#9Fc$eFQt`cv>f0c=%`_02DT7x;>z%7NW6J~0odAd}3RJlQY|*EC@V8f?nN4lU znsrJcD;e~o>VyV8!+eFsm&><%ozMgQGBjV!hpSG&O$lJnt~XPpHniLuoS%7!$$E2m z9!56UIg#~fhLzO1ggIi4WhJ*7g`eAotpq1cKn3ROy^_!>Lo=gx;?7(jO;98eS1)b~ zYV&k?i^{t3TsVX<91M2D{D8k5gg&)CI<&$wHBT`iq$)BwMzo+D-Sp^l3*w4pEc4M5^u)0Cp^BkMTPV1 zZ%V2GPjmI(!-YwO>=(HVo+Cs=08f zDe=|4cN5V7IruJz#G$gx^mF=0vmdid|3JKu2CqtH3D+m=_GU>QVBdD@i8$yqpI|Lf&jA z?s_?s?y_-lb6OP^2Gt}b)ef=S!gjy7s}JBScXVJqHgG^cf|^H~rmMm8^DUp(zjdl) za!(FA-1$S!0gL1TwLl3z?E)mOuP0iG(@Tk*&3bQ7QYpWzWh@<{*0jJ%VJ85%VMg`6 z1EOkuIegC<9>$l>>B())xV z&so+%>K7)R5My%x1Ka+MxxyO51Md-_2cF*gppZReAn-#tga92xk3l;xiGs{&8P{Nz!(dHuL_C0vA>iSvZ)Al7Fc#Z&P?M^?a^PU-<2r5TT*{M|_A&#CC*h<51T zRGk_TiWtV(gvnj#kRC}lV>g;_`c#g!QR8WQ0NUsUbP57G-&Qz>n)L{g9#BL6B+5wA zZnoyQG{0a}n}RfRPf@m2VhMw@fdma#ek+yiSwp7wM|?T!}CS(jEqYm?c*9;$!xKwHt&Mq2EaN zSOzg2D_2vuz+ z>(S75jZm^bxZdCc;F>cMuk}ROZ!X@bfVpJt!g=k=Ntz9~FnxJ<9uy_jjJEV2ySU9= zWdDhx+)R0(DEnfYbNV}Fo{R0}b!y%w0rzz)4(Is%S|Ja32mku}a0y%ZK3{{idjOoY zKnm2kmJgViWvhK~Y{u)4Xp$B90JwA_5nKW2D)DS3x}3e$&sN>PaNOd+ zK5!rS9~WTNZ%#6US#xPK=ri$l1-c+is$QGgEV%V=jli~3ob=3OcGuAHg%Hk#wmxba z-ZXIa=%3|7oA^uJ7eTc~tJEw#Zr!t{04jlGG(sipyxUxludm?5>hsIx3$xjFInh86YhslekXr7_ft2>g-W_x-WlN$FMNe21~mz?`WcLkr(3< zg0yrOhUd`Mu9~*rYnuxlW#aAcjR^9xj~HQ}p830#R9Mu1ovsKsg2*@Ac&J1h=&{rT zhC$6_ifY^1>rVJ04L|ag6nf>Gz$<#2gc-W`MQMNF;&003pqc9@#S<-%_@8gvegr7H zXIlp~Pcl2%fc;ZXAS>+kvy5%w0rl(v+~aAMDTXls*+1u+ZUbLCdH<08p4?#OP0Z}^ zKfW)Ew$3KP3PH2%01ddwSTasRH+CIJiUew)s>jwPPGS9~95(7&WMRF(ubqwR*3)i~ zeM@Uyo+@+@bP1yL>BsCRG$~cv>xtKEgq)CY`Ef~ug$-C~eb)RKSzBY(Kq*(yDanz5 z1bhKdM0vr{Z}Jtw$7&%9k~*zVk7sM=K;83zxIPCnq>iVagH0K-Xd~8ZIHZnjL@$S) zJJU|=c&#be>^OXkr%53rlp`5(9xYw6s&`o95aRynV2oUkmcJf`?5E<-U}qD{8PYy4 z3xz0#2C|(h-UKSnGl;2aJGI|p@zm!(#skx{dvEd6`rZl43cS(3k){$E<2FepM{Mu*@FpEW<`Q$xPP=47NY!{3G7BaM7t8`D{PJcaPJWSK^U_t^IJ3X=l- zbSh6e#C%i=t@XDBPH;Adtm(SuUa#rVOq@Sf&m%T5#Nn5*eO0929AbD@Q40?rtciKR zMN8McPY<@myPJtM_62+`T_N(?KB4vL0F!G+Skxr9kv|B#A3|5>3ufQ2xx3THHGBWI zm#tZh*oo0^x_p_v|A5+4G+&4sROpRdIGl~!W_KKUuY_qrk2GnLdPZOO-8x)8D2mvC zo7jQWcf4}FUmZ9>1r%NN)6D6@>F48iq*hwrCFS8aPC?Vy1J()V9Zv?|P9xXbR^b+q zjY`Uz1Uq>055VD7xwLfh20U*3P1HZm{pedwk^rsmrSL-C+u%v;0gWVb%*7AyLtq2w z*r@2M8UMDZjNi1JFO+X){qQsoVeQ9;Bf8~SwOx)gls!tR6qYTRa-iBIO!xsQ_k!w* z^eS*bl!pMWcmIgK`s#06nZZ@YeVXYK|0U-mD45}XDH6Ey-5i!u?^G{*6DL8VTovll z`|<4dy*{f1c7NB^2o-<6fmBCC{-g7_r;9Q~9qt@RPSv|WDHxL&nH7u`I0!(1M6^B{ zc~M)t?_^`&i;X`7eMcm-33*<%+!1{lIs7=}EiSUzO_fSELb`0Q!{lZS=SKh-GuHz^ zNrs;&xYt4>`%qk+CAtdV)T=Bq;J+BfkXX!imewE|w=VX6JH=dC;u}moj%;uF-Fc+| z{;lb-+UIZS*tAE8YPG3m-EtYqC95M2}=0=(nGeFXU`u z9DR0%@I0JWio^UY((Oj8E-eePG&v!OY_@9vH1Ts?GQ-6iK@W&_ zN}zd68M>PtT@(r)gREXLHjmU!d{e;nJ*bJCP{Taj2&228jMU15G%^28`^(JxIyd_tC&B^{AZhSC4846S zyD(EaB^XSR3eP)3bFmVFnB82Sfo+aQ==0qo-(4&Uwcs<>yr74vyd*0N z2vI7-4Prm&Zf@0SyN-{h)v0AO`f!-6U%Hrjo%+lcqRHteUU1lzJY!E9fU#vV7^b!4 ztb2cbWm+;J0^&mCJrGpjguh}Ya;(<2zymVlrI%88uX#}Hq4~chRURqi7zt@R6-?FW zv1*`zcZePpclL+=UV%MDA2mXo;cH7!I;FVdYpcUrOBw%gvjQ559sW9P!1g-PrM9ev z*f^q;&n{CU|FV-lo?co@6Z=#6uKf4*#y7tQ%FL+vT6XtRRn747j0eLjYv-%vx{@rB zI!{b(*P488LKRgllR-8#&Q@O=kf0VLbgEhs`O8$dPhG0N_{U; zpHxDd*Jj%a?k<`R10?jeOFc{GVw)F9kgH!&)(<>PExyCp!yeO0{K49EH&lC+E(kDpvER0!@i2US?sUR1=nYYk%kV<~6JLJ12 zNm8g|^!ai~Y8=BHB)K$}wZN!7n^MVzy2(BCRpye@(B&2xE)2*o)GE3^-Sb(L`0?@W z&G<4?tnDD~!K-yp(w%!Wip$GwM$o?<=|#*=9C2kd;VEg)!t(%0_=DMQtxH!8I@tUk zykO`mL#yy@yEN3^N3Kl~-X=zogRu)^T#cGHn&DAelLrA{H+qexhkW5(=o0Oti;E%m z#4aUxTlcHLte@BesV=R1fhi0-TTs66mTX!%N>}p@ruvQq*GI^96cebWmabRR8RXg! zqaUq@2xfY~Z+P5q+fj!-2hOHhAcd09GJd?d8v)z?8=$*~Z_cp|S8)MUq2dT~n1nyr zq`gj%HM6F#efetpjaPFd?X49}rcWeMg+3?hfAt`^$w0?|0M9~NiKaSnl%b)aB9a{1 z+d{mxPLX-_z#vlOxwo=qB7DBb4HAoC9~SpH)Fuya!>`|F2##b*kNcv)pc~D^#%-P( zM`sT1R2}hRbf^Pp%LIw9GL0OB%pU*x3IwONlJ~G*OErgl#?s2wX=2B#U6JPts!p^D zluj*52M7M>O7&+NIv4-IbLb**QzjJ-1Ka(*NYOV_==h(J3rf&^8m&|XavndD1bzK0 z`VLIuKNnBK-_k2(QqxNx)5M;9G!DwoI#_A=0|3!R^tb~-cNl??C-ccp1B|4`C^>>> z_}CjhccaQz9w4<8&6irhOEROSISf<9HtD*$6LS=D(n81HH$fcqX=U_?x{Ik@;OE+; zId;EtP#P(bxQtuMQ2xr-tgo+kwlbl|qFzbiGbqueUpQy`Tr|^vE)*LfL zzVWRErI>Wzkv6|&)53piSJA+?G*d+Wv5S?R_S(>@3dEr47$fnT^=k92Oj#)b9Jyy> z3N(En8dR?}XV)4rzxY*R^~GC>S!~1V+j;8y2S!*EH1dS))&Bc?6@<06K1sUnh#8qS zJ)S-twSEfZeXl-54n7UEXNnU)Sw>14vhWvmQA^0OnyL}UFnyKCxIZm?p%RA~PA|O< zd}c+k#m#!lZ7-RN-qT=p5ch;}%lSDuQH<<(ZOGX()*MO;y9PJ%M2~nyGFH;aG4o%S^YQ%LspOszZofz7kYk{wDS65%`7+URIal^iLx@G49OI0>|B$8up zzNa38LbG~(F{8dUfzgscVw;Yoms5!WKQ+4-$&m~I6e~zpXF}zj`@kvo7poWd7a+B3 z1nva>leqIkwIgFV=mDiiQh$>>MOeZYn#aMcQsj9n2H;zOBj9ZLSLPH{L331 zP}YV@esZ4toA#ccCbl^E76{xZYT8UWNY_0(!V%EP9@BS~K~E&V1iwuiRdJR!Dk8Jj z?>UA{dqP+bA(CSo0hbv5V;g+Ft!s%Q2BM8!@|?)mRB{Fn>0ALrf``Ow z3C$*wV=aN}PzBzB!EcJ^KwGLRz$aAtCxZ)nWhI3$Hb8Uo<*DD}gv2XBzoMaj5N?#W zU}(Sy-@$d$fhpIV(>LPgBh~J!f8l`{^69{(j80kK<;P@bpj(VddwtoiJZG3ne%A=W z%HF95007xnNVcOd=eirg7ykLdgiwC{^Jk+LQ5D0V+$_db;nX)?!7fP9v1tMQk>om7in6u#(C`2+pmeQ=x_gSc3I}!cD?mqN3 zKm}S76EMOIm)2IZ0nX}AKrE7jw~hQ#)DAf)LO`lNSdf_c2e{mgcBAp%YBwUP;n>gj zu&m1$OK=TSItOwyuP5ZXr_+p@(|?|Op#g;du}4Fz^Ah6&yFw%^{eG&S0C!1rYCM4W zm&=-ot5Q%@&Y58oRaSQU-KBTf!9Uw*`oVWj5eOZ6Ke}T2v1L8^8tdBfx+_uam7~cez2SCt+nswTD-p-O@?zBxvMb_vB)wP+E9{x-9|N|*TX2A z*+kN;*3Qk8=9_quetG$ui%#gQZ`~$JaTyBp_N92;mOzON-NlebiU@jy(T~Uf-h9*8 zv|YyKhQMMqxVds9gM^VooOzSZ+?s2$4J`ee;{Y)T>bS+wO`XhBQZzKnuGusBCmk2^ zPyzNo(78Cix%^fF`yP2hdVH<;OTe|%|EOBBr{k_!vq?$b3P(183677qk`Z(x%(x%B zBPrLEaz}}@b3~G)c>=lY&9U9}s?+>l(BM}a`_e;>tz4v;8ZnW_xe*=YuY;=oGYff2 zIvYI-80)%^&%z5U;kQdj1VVu)05eO z*BMe#CUHH8K8Vq{5q92_9;k4-W?fvfMlE+99q>KGMIx{GT<$0PZlc-yhfMTbHEVo@`GVn)g082y1-RNp zKw8P#+Gq7VD{adO#QovzWHN9vafe^GD>KUZb5}FSC%MSekHqOTF>7zL8#Z&H?~hOn z!d%+e7{wvwOi;s#F-=EezZ=CoInhk{C6Cz={xJjQ4+iC`od=%8(6xpd-_`7XeMP7H zw0-qwe!8zEV>4%@J6MqOsNnLuw{Btm!aDjB`0ZZ`_7=9^veMUpf;AQ3!|ec?=>*ZcgGG9dK5f583X@_`XrTCR~w zgWL5$uF9FsI3gwg$O`W9AFiz3r?p3ACNse)QwpY++CZld_rq--B;BD>)gF$CtS=gu zn#rXAWZWnZQd;x9vk@aab~k<9E@lU9J{3J}|NSLG3=e5rf#;?SQAs&5kAF)CW|i-s7w}npHe}CxjBxm zcKF*#qh*~aLAQeV=>ix;by>=30k7Z}>bGfYu zCXl%%z8l_oee=^P=6w#IN7Kr%f867{Dkr7GOWHJl#pG}6_*Jw&2@V&irx6ML#M!pU z4X>Nl2jW~7Hk#r2OOKOu*Is_~f{Rpx9ihqruDhsykNst_6BG|QAC>@q9TD$y*k!A> zc_A(~5Wou>_C1Ulq9;z&_f(mdeqf|{BuIzy(9*Nb8X1VMYmWsxBN6sf`6oG7IiuovZ>hQwa;o%W!| zY2N*L=862s-R1CtMa%v2{}syOdx+9u(R+2>~*rK${)Lf-u2+keQ-5D#G=aWE{7 z9lV|>eKLz*u&Wm?En;$BpDK0DhE_&#M$sqjIWN=Ji*zpC=XZQUgY6bQ`=8>TZY1!Z z<#At|c@GcCW4MERR%6R3H+4{N73dMAkSscY93zF&#P%%dufV+pfg3suOUyuk?h~4r zWQcSj+Dzgh)hTb!o2{^drraR(5e{{-LRWO%XROo}JN}99B-KS&1rq>>E zg3=vTA)2ko?EDVpGOv+o^VU#QkyXxW-)x5akRrhvWN74&VZ;~G#BZ)^S2_HgRtrub zQGYv|s?nqdC5YS~inXPIT*Z~nevo`JLHF471?=}!@`0&7hxuju*CGdVKv6a;#XXu< z!{dd7=bRaEnU*g!`BHL40kiPoW-IIm{B*piP9gVQHWd6X4;*Js->DK8rS+B4#5#x1 zMZ~{Dn`MPb7r!9Aj>4pCv51s6+JNI2SgV!x%>&nam)p?SkBsOG)HP3I1fN`zamD&IJ~FD7zVxX%1$qZo&ERr}-Kq;e ztFQ;O4eq1IdAv9F%msU)k9pk}GZ^7w4fS?n4&I3*H-unH!fkVOzc&Lt)8X+G3B4z( zVQj8LOpbSvCf`U}jfEGorQ~6>HmYF!0{P^;`J{`WXxU);a8>38_Vs*=k%pVasXfcb zNE@JI{j%}`3HBTdZpVv}T;>WKkSHa!%lk^?8*JTxV`4k+H z0*0xH_9_gVV(W7bB}?R})ux~u05%vthLn5lQ|x}0WZ#&4&WE3EE7*V=jkXfC#;;1Q z3-63%eA*fInWO^w94nLj`pJ>}M>- zC6Z?jB83y*7i98;k2@e|nfSj0(&D>g1GM78atuTGQ7gvK4*0Ike7MQ)y)2^OI@1kd z`#Rdsmc>=?_A3)~{5{BO_7_}V%tsngrYAZJQfwP)7{EG$Tgd|(>6n!R`5MNLc!tx$ z;p4UJJfxSS<5vtZU|U?{J5 zw~2^v0~LU4*y5{J@_{&?!}n9)X*JML3v5(6d3YR+dqOrB9wi%Lt|{(w74n#O^^u#F z1x~S$IOIEkq_I_T;B6;D{e*Ws40}Adk0mhRE;PJbGe(k>TVN(M?EJ|FQlHoIQJn_B z)X37n^2zpx$5`1)xC+zdy5Ykyng%FNc`jn?+uN1|f0*tAPh~L8zbKMdv5C9Cji)H# zhSS~((0ol=X_IrF{N~sKT3fi*n`^N{c)r)cL)XZP!zD$y*^}b%4c>QIhDkMgH8Yy9y}vJ1%pAr@ms9 z<-rdC6%1h~^!`WkY0wA(Y-KqA2d&fz?a;6WP#AmeM-3l0DvM24QN{s!&S~D?$gwsm zr3UdG&q#wBTRVIPXuZxXKH4)iO9GNeE+HVtTt;zc*J_z7w%QhHwWb3?;JzFTV7-dK zkCv(>l6&^kL(XTB_d6id!X%1coNEZ6!3?KsjdqfcCFsC)pyQ}$0c0vapB_M%={AUQ z8B+#K6^MNAIN&i!u)(1a-M~AQ9AjSa-WLpEJ+;akEgnmI&jYwv^*9f}D&n|*34Rv| zCTb%qoGTRv&knfXMV)U!g~99EB%n7FFpK_^wjW&CV5&YS(TF(^;~|JeEv%*T<=}Tw zVKVqt(93UAruQlIEPVnTl#vTh&bI_sDfuP?($hklcq*RtU`dBSo!4q5Pt8Qg&q`ia zDtpY^tZ`8hb74S=RDmZ?9H+POjLt8#;Jxe*xYWIS#)3CZ(dPqY;2qKgn1w~B1c6^A zk^D&=&it5C)Km`WGk{2D@@uL3nQtCg>V?FP^_z@f4qC`hDkbSwyVh0A?ECgg!$EoWZ;=O}`Z$Ok>v*-2^D}d33k%5&z$aQiXCQz0A3WJqn{`nE&g0DLk8+L3!hv9tgn*X*?IF_ z=+vn{BU`y*%R;0InAAkDdCfUtyJdQi4J_S&)1p zm8?+1@an#@LOHxQao6UOe{Jav5G<-Rvv$M)PScyGG$%G7jVrQJg;V4M2T;9ZHQgTB zvdH2o;(e;0&f@TiBGjXIp>qHAGa{{5Y zfi*f-qZyTh8%?Pn80k~ZThCOFb_OnXy6f$-G*E6GvMUGpJlGxAnVW!#Iu5R4x1}9J zR{!yu6Td394)MRD7qLifRX+s1S?uJz)IA@|TF7f7Idh3KCVb0OaI^sR^A2!;;iJS? z|ERaFkL1;#Fg`shl*%bzwUiSve^95kXSVb{7;}Xo-N;+ai##y^55C%A!8qdUy=Etm z(Br1?UPNiT+b2H9f@hJZ{&6_Z{U#E%W=l&LpUzGbhbFmSnw+zgMUBGdY|Rj56~Ik= z^TWnT7Jdf~*SmS1mq*HGq#6JwY-@ukgNG0Qg{|K-!L=`Pa@2`}*}taqN-Un5Ikuuh z+xPp$QdQuua`+7ly$FZiw+epZcgfsT$P_&pvF$0%OeJ^F0z$i_I1#M{K=ph_l}VfJ zB|i}YY;~LMb(Qqn_bLF@`Kl6zfxW{3d@y1-sJT`RzR57Ox5g&>E?QQ*J(|L+V=%b|V+8{PjHyj&wn z^Fa}Z{t4p%!W-!y?F?Mr{MvX!etsuNPXD%1=C12%>!$H6SuUvjA6XRWo<(Y`m+^Ah z<_~HgG0J?dPSlJ6R$K#!IRBGZh8`Uzi0EnLOUOU%F5+`9VZ`gJUo85+>sTWW%tDlN zF)xIZT*Yz$@Z4{mgCE4!MDl5xU{L^d-Y?ds0M}?av^ydaNGYT>@PGACLH#P;yY^3! z59N)905UL((#WsN${0>2O94&NBI%;F1#8LM@2@$a*C-3UeaQ3MLC?WU=poZCUDY0Tq3Hoh7?zVtHVPVXC|>f z9OM};nK=#=96N#hn0k0)g;>Q@;{Y}Xlzg}XKPLblb^WJcrabEBi`7c$ulWWZU*{Zq zP`hRe;<2~|>AD3dsDN)!6j?4<^HjO}*uYTgUGQ*S)EkaCq`;%4_P;qKl+ah^P-sN+ ztP35S2w_tg(+l49{-Y>@iXM2NZwA9)@9McHTfLi#T~uRJ2j0XcJADEHJ~xSdBAeY0 zFaV+%)+z7-mtxaE!@pIAYOKGUU|7JUQ|~DfD4_KHH_Ky&_i<(?b+u?Yzis))wU+4y z@a9oZf#;&M&4<%Z0mz6(j^ay3ddW`uOFz{x&)V4dx<pI zgjG;G?6e>RUSBL0pA8e*jTX0%1MgaPYJPJ;4vaRL(E9=Lg2ShDx3Q;gxccekoJKi3 z;{R*m$^)U=-}f0y zlDgwsl9DNuZW4)W38S*!MBPO8);n{PEiEL@aLbYt5*U%}SDUwn_eXex=Io%@4={CLb?ScR;E#N0S zK(+4`vOlsMwPTcOKW;zUIJ6&KdqarDI#mdyu{Vv)A=FXo#_0ArvhdLU@Ter(*h0r=PC{!pqq4S8 zG4>u2=Zh{*`k$2>UA62pm+c%m(esdI^qJJo!u9V}hA3)#fS#J=cJxOF@qRx7+sC?? z`&bhrh}YZr#Dp)AvYR%HOB_^g)bZji7@S{U0HQ8PbW(nf7molg?4VGK-#16f;vYRK zwmG(LcJStA^c5eL=y;9u{RSNh_U&HbY3fkAXQf%8L2TspP5FYh6k4kt#o~3B|BXF4 z6YWa$6Csm2Ud@4wkfpxqO`n(?8ORIKJvMaQzKU7eKiQ5hdYsWsRD)!B2 zh1HHHNQ zCk^wR6n@*gPkkeLdM88eN4|_MKS^#l!gg{JPD6&N>kKr_y-;*RM}xT~H&;otA!mVb zXA+{LLUo59KK+QHFG1(+N)F4v6zVVKS|mtC(cnBjNA9Qb-kh$dcfO?tw+hz!{J0iE z;oEq<$y=aCALF__Y<8IC5{HE1Pmb+!UQVp5%kIbO@-fI(tcwDe2Bec-SZdrE;pnpb zE8yD5Yr%0Y)l0an;zy5Yo5`>|0Hzd2V8wrYh_wcEJt#~aKt+GH?X-04!&%f(VL0_O zfIam{rn=RU>68ipbM)v7maNY^!r;MDWbKo_Uo=64|%5U91vRhcj1wOJy7}(P5xLg zeY*qSMcvqN9e1~V@hG8D+hOY=Dd@itqY)(%4{su!=3y2!=$#|oAT#1$CHegFjD`qK z0-=DVwefF5lH=RVd{9~AIeR$FO+<^hX>bOc>~wD24UBVY>^v?^uzl#6zH+IKwXLo} zpd*nI3Gp*KEiYQ;gUU?W&p%?P9ChZ%d?art_1MXEnZWt7Z2kK&!A9=%W-3wnSWTU` z=SP*D&cd+@t|}M z4o<@)X0Pj2NHYPql?3z%==b!COK3s>j+%goWBz2P>NA_Qz1b8#k0a3#w^uN|FVCm<{f`QjdEsSNDhmc_Kl0#AZJqWRpp>?B%+KWY4E-kl*MN zR|0rzY;8?B`4d*xH&8R&w*nsODi;tywOJWmd*;vJxkk!6gROssCy6Xl(W{;s-Q}88 z5~TrxKfEl`ed>Y!qzx>W&7m_u18hzUfUaQEg`ZoTFP7dEk-zD4AEZUGy$+ zHbm7-xLYnFd0}>o!S2!Zpn8kG`VPG+Mb&?x}KZa%Pqa&@dnIq-P#QUJX~Ml5}9 ze+)q!RafR0IWU$bJClODGT&=oa}qLJY5udJ_l_`zXS>IDoKhvfB-Dz9fx%>hzxbU157%E1#&mFxMD5L6&8cfkb_;VQ}rFD1SoLBLAUfUy#Rl1?jws_>k zLtS@aWv|lfh~Rp&gH6M@Q;RMjL{|5__Ek-xmD2w3rxG`trILG%ab~}ze!o!Yo~9+b zSOVigG~Z${Mu{&<{*nQ`<58}^YV28my=^}!!J+y^JpNAK-gi-2oTabcCu(ML2BP_o zX&vb0rA-C+*O}mj;8!#AHi<}oTXPX8f=#$*Z?7;@%>~>p3V>zS5e?E2!Mma|Er_EHu)x5VBb===B%-ys#YZdLn%f9Z``%7bo`Tfw};7` z(?dAW^pzX$Xh7waX4hNICV^dvuOIKlOv`fo?HiOU*yeP;^6>QNUGK>xQNTS3fG5nr zretvQ_i{QR^XmJdju52cw_Fc_Tk(u`!)qd~6YJ84fFbhuq`<{{J+{mL6Msq?avCsp z4&XFmo*s`!;H*8&mEQ>iiI}HP41@%sQKs}SdExiJ z0ghQCtOO+q-slVGR=JtY!2=dsYWR@U<5SS4EaK`d z15u0M=9-)-j3jzilCx)s8Et@GTAENQwa3bb-m;4Qs`x~VqJYp8$2Rf!5*I<7U1VTv zjh&*zOK&!}JEQLfDr+bS;D8jPSYrkKc8N4#0QS6zB!VAg{iaspIa*J0(8MvkZr1nn ztA~n5M{wM8MFfZM6A$-I(wP!Ds}GM`t=7!r!;xPP4O%U-Q%;R+IL1AfO-x*wQrf>; zlpjayO)kt*WahKxC#7--YQ~(wvdC&NUlhbx6}A}$l=rfX$<`0Hn$gMpTw2Ml5qa}D1LM%;$iyMKKuPY z1j-rNkD)e}GHg9~hD&fDDCW@p7p9}%2b<`v8f3-jXmjMKk(qA#b^m5(qQ|T_3~MS2 z1ad7s%P_x^tra{c-l1kPC?*#DCvdf(-$8P70Bv_U{li&M;4JS+LEDeRM~3&y7d5fhj;ZE+I@ z?VFLZcpN)fE1nm^2=i0Mbbmm~UeA&e>Mef?=rxys^-PP|IP|D}1;5c)xa{FEE>4-Wlvz$TX#*kB|88%Kw#a@dHQ*gdht1dwCT^I8 zUZX<at-Qi>w80wA9MZt4oIorA-T5w4(4l@E5DqGc25fuxVs0m+^)eLer+2=-%YC1; zO_Y8Bw2j`kJAK+{ZZf1I!&{n5H&c1Wk_dTwS1#4@_2|1gpm<|iKch)+dbg+{&Avl5 zct-5>S}@WoTb()-LfS$7_P1TRtCvuL0 zA})9qhom^oJ@N}SHT3z|*{BB~u0A|FlkmJ6Xjb2Swp$R*4m2Lf@1TpuQh-FMZF(sBs2Wgg7^ zCJlHbDGm%HonPW(H)e1#Fd2lJSMR~1hSx$_q=VG+GOZ1VoPKN>#I7W_*X~OX5);+~ zk{BrCJe}kQ-Q5U;v<4iw&$Zx?T~@`%1byUoW1aNtz4J24-H^RgwV$Ryk&7t`$>UzN zV`CQqu%2>>K>xlzk^(^S-U{pOv*V)T}Fs3oW={SVq)bN{` z$RtFf% z!V5x|PX{Z_W0>GFFNW0V{_K(-)rdPBvJypZ5ByO?sGS#5AR?L3<;%mljl_#`tXqbr z2%?{ir2ADxv9%hFpUmh1Sv&=MpG{th?Iwn|_L~tKtl;0a z{eq2V<9o#dn=JGhdo7`ao>v64C2&MGbg9vmKaOaz=|13Ws{BcF1le@!bE%?M@I+n+>4qRYQaeg|JL);9bBmr_mc5>oxoD!-E@96C3*XNg z+hhKy70(SJS?hC#l-v5X9zXiwJM;PZxBbg^5{rXujK12#;xxypKbtT530y`4u5>SN zbq#egVyMWs4F=_g@cPLk*PxZ21gv^Epea0G-A8oIzKI)X`^78WiS{v3tNZEwA%NOu_J*{pUGE8h-*E64- zk*PE+mb?kRa*@5;b@(OAwmqcqoyh#F{apy!bL-j-11jK=5y>1D(@^D)BA#V=d*Jw` zNZVfSISn78e7lM`ZbDFOSokSkS46*sD xMxU7$&dR~E^w8z>ki&A|dsGdhfHA+q6g(AXNmiz?q=sIgO` z$}SaZ?24!|1A5~%o}DVFd|$rC#tK#Qd-z-7^C-m{<%%lbF4oyXvCb?}w#J8NovN7U@(NRj>U4|HFCtdyt3{P> z6;*!40q+rfi{`54m0Z<)Zg`Y#TSIgj5LMbBs{HZ_c<qp|=ar&aD@_pTaAF6YDY6qun2P}H!Z&1AE^wS-jifS_Wre-U~-poe2 zxts8ZlEOdwJf}Y$x9IAB$W!p%AK$ja z?wQ!|wr0xgN0H!%RJyyU@@q-pPhIea|Fl7L^sGgb(-uu0w&>`BMgP^jjezfRc0zRc zY{jH(*|?UiH13KJ;ql|Wfaey`?iq_7dbZQs;~Cz=XS|IN1HP$c{TSE+@x?kl)eCro z5sgl{-}$ZQd(&=if7VS-p10`!O)=mrhOL>3{^Q`prp`a=_L(;s(a2;ld{Zm0bJ9(B zBh0}kj7EpIwXFa9zB1kFv-*77TEizbf+zg45sWdFq18vq(k=gvT)` z>r1Zngd&O=eFWe1_Lbj-GeQ{n6(bmm+xB1RH~6Lo8R3CVk>H`{I%|0K{)7)@go{lh zgo3vgoAffUkQ;#l`VGTp0Drf9eh*Ooy<2*7#;}&Z&12q%r&?F z9brn>Fwpe;+N^xLmS6MU2}jOwWP!f$H+F!PQRsHTX<7uY{;PlQ-pa2VW%P$57F~GX z(Wx13pW!GGUhVhZJK?tnSpdAFsj{|SDAf3KLGX`keCfWC-3raP^&A;4wQLwx_WI z`t$hrAE&+3VXT&082j<{*!*<)niXl~Av5#H0fOOElE&?H7=lqWKlrAiJ>^#rL9wFU z0K2BS?W9^2F+do_-$q&aljq?)L%_?n0ZuPhgbIqeJ#1nFBlwlXRF9pIB*+P~@~1CY zG+Yk}@8~a=4S*rUD6xH9BlHnG-B;EJKT{cQJqBSSJTxj#ycgaG{RE$q8s|DYLkd%) zhPQ~{!x*8j;AIWk*%%qB(=C|5j2hks8eu2vFL=q_21y0FqbOlTqPYjezEao8az7SR5v{@5(*j z4sYxKo)ChC1~7XY6}~m|^D+XG76d{_U)$l=ZE}hlUL!I|;RhvcHq?j=1cS%Grw~7 z*9&nZTQMi%z&8~}Kyf3kIE9g9@Pl&W7eaiYfo#Q`OcL9pQHfq%Lk|y*&%XF)r=Xc= z<>kiS#FOKhjdXLaVjM~u+kbP5LHlPpE%^*fql|@0&cmC7iy0wS{$ST8#@CJ07jji| zXE$Pdw5!mu8Qa6`x;=Dkk4YOQ>@l`b;KA_P8(o865gNeYanjh{w^x@KzF|3MKIC;5 zM>1veyV;6yrQhHOrN$>|S^t5s8j`Iv?!xSC|G;arUzZwp3acwLkSXgMFn+pb@KR$b zU21IX>6Q-?%-&-B6cd=M1wSZjA0w*^Ol($D?tPlRgbB=a$yn?eywq4oNm>>Em(b6W zmGu*$@;~VtSzNw sO^4z&p4sn}v_0t9plPvOx9_3+A6k>6!vBrBqW}N^07*qoM6N<$f>TvnY5)KL literal 0 HcmV?d00001 diff --git a/assets/icons/icon-96.png b/assets/icons/icon-96.png new file mode 100644 index 0000000000000000000000000000000000000000..cdc260ed0193dbc637c17fa1a324a9b6d5960e5b GIT binary patch literal 3397 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 + + + + + + + + + + + + + diff --git a/assets/js/admin.js b/assets/js/admin.js new file mode 100644 index 0000000..de155b1 --- /dev/null +++ b/assets/js/admin.js @@ -0,0 +1,106 @@ +/* 简航 SimpleNav —— 后台交互 */ +(function () { + 'use strict'; + var $ = function (s) { return document.querySelector(s); }; + + /* ---------- 自动获取网站信息 ---------- */ + var fetchBtn = $('#btn-fetch'); + if (fetchBtn) { + fetchBtn.addEventListener('click', function () { + var url = ($('#f-url') || {}).value || ''; + var hint = $('#fetch-hint'); + if (!url) { if (hint) hint.textContent = '请先填写网址'; return; } + if (hint) hint.textContent = '正在获取…'; + fetchBtn.disabled = true; + fetch(fetchBtn.getAttribute('data-url') + '?url=' + encodeURIComponent(url)) + .then(function (r) { return r.json(); }) + .then(function (d) { + fetchBtn.disabled = false; + if (!d.ok) { if (hint) hint.textContent = d.msg || '获取失败'; return; } + if ($('#f-name')) $('#f-name').value = d.name || ''; + if ($('#f-short') && !$('#f-short').value) $('#f-short').value = d.short || d.name || ''; + if ($('#f-icon')) $('#f-icon').value = d.icon || ''; + if (hint) hint.textContent = (d.icon ? '已获取名称与图标' : '已获取名称(未找到图标)') + (d.url ? ':' + d.url : ''); + }) + .catch(function () { fetchBtn.disabled = false; if (hint) hint.textContent = '网络错误'; }); + }); + } + var resetBtn = $('#btn-reset'); + if (resetBtn) { + resetBtn.addEventListener('click', function () { + ['f-id', 'f-url', 'f-name', 'f-short', 'f-icon'].forEach(function (id) { var el = $('#' + id); if (el) el.value = id === 'f-id' ? '0' : ''; }); + var v = $('#f-visible'); if (v) v.checked = true; + var h = $('#fetch-hint'); if (h) h.textContent = ''; + }); + } + + /* ---------- 编辑网站:回填表单 ---------- */ + document.querySelectorAll('.edit-site').forEach(function (btn) { + btn.addEventListener('click', function () { + var d = btn.dataset; + $('#f-id').value = d.id; + $('#f-group').value = d.group; + $('#f-url').value = d.url; + $('#f-name').value = d.name; + $('#f-short').value = d.short; + $('#f-icon').value = d.icon; + $('#f-visible').checked = d.visible === '1'; + window.scrollTo({ top: 0, behavior: 'smooth' }); + }); + }); + + /* ---------- 编辑板块 / 引擎 ---------- */ + document.querySelectorAll('.edit-group').forEach(function (btn) { + btn.addEventListener('click', function () { + var d = btn.dataset; + $('#g-id').value = d.id; $('#g-name').value = d.name; + $('#g-display').value = d.display; $('#g-visible').checked = d.visible === '1'; + window.scrollTo({ top: 0, behavior: 'smooth' }); + }); + }); + var gReset = $('#g-reset'); + if (gReset) gReset.addEventListener('click', function () { $('#g-id').value = '0'; $('#g-name').value = ''; $('#g-display').value = 'grid'; $('#g-visible').checked = true; }); + + document.querySelectorAll('.edit-engine').forEach(function (btn) { + btn.addEventListener('click', function () { + var d = btn.dataset; + $('#e-id').value = d.id; $('#e-name').value = d.name; $('#e-slug').value = d.slug; + $('#e-search').value = d.search; $('#e-param').value = d.param; $('#e-sug').value = d.sug; + $('#e-sugtype').value = d.sugtype; $('#e-color').value = d.color; $('#e-active').checked = d.active === '1'; + window.scrollTo({ top: 0, behavior: 'smooth' }); + }); + }); + var eReset = $('#e-reset'); + if (eReset) eReset.addEventListener('click', function () { + ['e-id', 'e-name', 'e-slug', 'e-search', 'e-sug', 'e-color'].forEach(function (id) { var el = $('#' + id); if (el) el.value = id === 'e-id' ? '0' : ''; }); + $('#e-param').value = 'q'; $('#e-active').checked = false; + }); + + /* ---------- 一键“全部更新”(分批循环) ---------- */ + var upBtn = $('#btn-update-all'); + if (upBtn) { + upBtn.addEventListener('click', function () { + if (!confirm('将批量刷新所有网站的图标与名称(用户自定义简称不受影响),确定继续?')) return; + var base = upBtn.getAttribute('data-url'); + var total = 0, updated = 0, failed = 0, offset = 0; + upBtn.disabled = true; + function step() { + fetch(base + '?offset=' + offset + '&limit=6') + .then(function (r) { return r.json(); }) + .then(function (d) { + total = d.total; updated += d.updated; failed += d.failed; offset = d.offset; + upBtn.textContent = '⟳ 更新中 ' + Math.min(offset, total) + '/' + total; + if (!d.done) { step(); } + else { + upBtn.disabled = false; + upBtn.textContent = '⟳ 全部更新(图标+名称)'; + alert('更新完成:成功 ' + updated + ',失败/无变化 ' + failed + ',共 ' + total + ' 个网站。'); + location.reload(); + } + }) + .catch(function () { upBtn.disabled = false; upBtn.textContent = '⟳ 全部更新(图标+名称)'; alert('更新中断,请重试。'); }); + } + step(); + }); + } +})(); diff --git a/assets/js/app.js b/assets/js/app.js new file mode 100644 index 0000000..a1f8428 --- /dev/null +++ b/assets/js/app.js @@ -0,0 +1,90 @@ +/* 简航 SimpleNav —— 前台交互:搜索引擎切换 + 联想下拉 + 合集标签页 */ +(function () { + 'use strict'; + var cfg = window.SIMPLENAV || {}; + + /* ---------- 搜索引擎切换 ---------- */ + var tabs = document.querySelectorAll('#engine-tabs .s-engine'); + var form = document.getElementById('search-form'); + var input = document.getElementById('search-text'); + + function activateEngine(el) { + tabs.forEach(function (t) { t.classList.remove('active'); }); + el.classList.add('active'); + var search = el.getAttribute('data-search'); + var param = el.getAttribute('data-param'); + var slug = el.getAttribute('data-slug'); + var ph = el.getAttribute('data-placeholder') || '搜索'; + if (form) { form.setAttribute('action', search); } + if (input) { input.setAttribute('name', param); input.setAttribute('placeholder', ph); } + form.setAttribute('data-slug', slug); + hideWord(); + } + tabs.forEach(function (el) { + el.addEventListener('click', function () { activateEngine(el); input && input.focus(); }); + }); + + /* ---------- 联想下拉 ---------- */ + var word = document.getElementById('word'); + var timer = null; + var lastQ = ''; + + function hideWord() { if (word) { word.innerHTML = ''; word.style.display = 'none'; } } + function showWord(items) { + if (!word) return; + if (!items || !items.length) { hideWord(); return; } + word.innerHTML = items.map(function (t) { + var d = document.createElement('div'); + d.textContent = t; + return '

  • ' + d.innerHTML + '
  • '; + }).join(''); + word.style.display = 'block'; + } + + if (input) { + input.addEventListener('input', function () { + var q = input.value.trim(); + if (q === '') { hideWord(); return; } + if (q === lastQ) return; + lastQ = q; + clearTimeout(timer); + timer = setTimeout(function () { + var slug = form ? (form.getAttribute('data-slug') || '') : ''; + fetch(cfg.suggest + '?slug=' + encodeURIComponent(slug) + '&q=' + encodeURIComponent(q)) + .then(function (r) { return r.json(); }) + .then(function (data) { showWord(data && data.items); }) + .catch(function () { hideWord(); }); + }, 180); + }); + + input.addEventListener('keydown', function (e) { + if (e.key === 'Enter' && word && word.style.display === 'block' && word.firstChild) { + // 默认走第一条建议 + input.value = word.firstChild.textContent; + hideWord(); + } + }); + } + + document.addEventListener('click', function (e) { + if (word && !word.contains(e.target) && e.target !== input) { hideWord(); } + var li = e.target.closest ? e.target.closest('#word li') : null; + if (li) { + input.value = li.textContent; + hideWord(); + if (form) { form.submit(); } + } + }); + + /* ---------- 合集标签页 ---------- */ + var collTabs = document.querySelectorAll('#coll-tabs .tab'); + collTabs.forEach(function (tab) { + tab.addEventListener('click', function () { + collTabs.forEach(function (t) { t.classList.remove('active'); }); + tab.classList.add('active'); + document.querySelectorAll('.tab-panel').forEach(function (p) { p.classList.remove('active'); }); + var panel = document.getElementById(tab.getAttribute('data-target')); + if (panel) { panel.classList.add('active'); } + }); + }); +})(); diff --git a/config.php b/config.php new file mode 100644 index 0000000..9133982 --- /dev/null +++ b/config.php @@ -0,0 +1,60 @@ +query('SELECT k, v FROM settings') as $row) { + $cache[$row['k']] = $row['v']; + } + } catch (Throwable $e) { + $cache = []; + } + } + return array_key_exists($key, $cache) && $cache[$key] !== '' ? $cache[$key] : $default; +} diff --git a/data/.htaccess b/data/.htaccess new file mode 100644 index 0000000..4628e4a --- /dev/null +++ b/data/.htaccess @@ -0,0 +1,8 @@ +# 禁止 Web 访问 data 目录(SQLite 数据库等敏感文件) + + Require all denied + + + Order allow,deny + Deny from all + diff --git a/data/index.html b/data/index.html new file mode 100644 index 0000000..7d6a4be --- /dev/null +++ b/data/index.html @@ -0,0 +1 @@ + diff --git a/db.php b/db.php new file mode 100644 index 0000000..8518f17 --- /dev/null +++ b/db.php @@ -0,0 +1,226 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + $pdo->exec('PRAGMA foreign_keys = ON'); + db_init($pdo); + } + return $pdo; +} + +function db_init(PDO $pdo): void +{ + $pdo->exec(<<<'SQL' +CREATE TABLE IF NOT EXISTS groups ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + slug TEXT DEFAULT '', + display TEXT NOT NULL DEFAULT 'grid', -- grid=普通网格板块 tabs=合集标签页 + sort_order INTEGER NOT NULL DEFAULT 0, + is_visible INTEGER NOT NULL DEFAULT 1 +); +CREATE TABLE IF NOT EXISTS sites ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + group_id INTEGER NOT NULL, + url TEXT NOT NULL, + name TEXT NOT NULL DEFAULT '', -- 自动抓取的网站名称 + short_name TEXT NOT NULL DEFAULT '', -- 用户自定义简称(页面展示用) + icon TEXT NOT NULL DEFAULT '', -- 本地图标文件名 + sort_order INTEGER NOT NULL DEFAULT 0, + is_visible INTEGER NOT NULL DEFAULT 1, + created_at TEXT DEFAULT '', + updated_at TEXT DEFAULT '', + FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE +); +CREATE TABLE IF NOT EXISTS engines ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + slug TEXT NOT NULL, + search_url TEXT NOT NULL, + param TEXT NOT NULL DEFAULT 'q', + suggest_url TEXT NOT NULL DEFAULT '', + suggest_type TEXT NOT NULL DEFAULT '', + color TEXT NOT NULL DEFAULT '#2F6BF7', + is_active INTEGER NOT NULL DEFAULT 0, + sort_order INTEGER NOT NULL DEFAULT 0 +); +CREATE TABLE IF NOT EXISTS settings ( + k TEXT PRIMARY KEY, + v TEXT NOT NULL DEFAULT '' +); +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT '' +); +SQL); + + // 是否为空库(用于判断是否写入默认数据) + $isEmpty = (int)$pdo->query('SELECT COUNT(*) FROM groups')->fetchColumn() === 0; + + seed_settings($pdo); + seed_engines($pdo); + if ($isEmpty) { + seed_groups_and_sites($pdo); + } +} + +function seed_settings(PDO $pdo): void +{ + $defaults = [ + 'site_name' => '简航', + 'subtitle' => '个人网站导航', + 'default_engine' => 'bing', + 'primary_color' => '#2F6BF7', + 'open_blank' => '1', + ]; + $stmt = $pdo->prepare('INSERT OR IGNORE INTO settings (k, v) VALUES (?, ?)'); + foreach ($defaults as $k => $v) { + $stmt->execute([$k, $v]); + } +} + +function seed_engines(PDO $pdo): void +{ + if ((int)$pdo->query('SELECT COUNT(*) FROM engines')->fetchColumn() > 0) { + return; + } + // name, slug, search_url, param, suggest_url, suggest_type, color, active, sort + $rows = [ + ['必应', 'bing', 'https://cn.bing.com/search', 'q', 'https://api.bing.com/osjson.aspx?query={q}', 'bing_osjson', '#60A504', 1, 1], + ['百度', 'baidu', 'https://www.baidu.com/s', 'wd', 'https://www.baidu.com/sugrec?prod=pc&wd={q}', 'baidu_sugrec', '#2100E0', 1, 2], + ['谷歌', 'google', 'https://www.google.com/search', 'q', 'https://suggestqueries.google.com/complete/search?client=firefox&q={q}', 'google_firefox','#3B83FA', 0, 3], + ['搜狗', 'sogou', 'https://www.sogou.com/web', 'query', 'https://www.sogou.com/suggnew/ajajjson?type=web&key={q}', 'sogou_ajaj', '#FE6B14', 0, 4], + ['360', 'so360', 'https://www.so.com/s', 'q', 'https://sug.so.360.cn/suggest?encodein=utf-8&encodeout=utf-8&format=json&word={q}', 'so360', '#1BB04A', 0, 5], + ['DuckGo','ddg', 'https://duckduckgo.com/', 'q', 'https://duckduckgo.com/ac/?type=list&q={q}', 'ddg', '#DE5833', 0, 6], + ]; + $stmt = $pdo->prepare('INSERT INTO engines (name,slug,search_url,param,suggest_url,suggest_type,color,is_active,sort_order) VALUES (?,?,?,?,?,?,?,?,?)'); + foreach ($rows as $r) { + $stmt->execute($r); + } +} + +function seed_groups_and_sites(PDO $pdo): void +{ + // 板块定义: [名称, 类型, 排序, [ [名称, 网址], ... ] ] + $data = [ + ['常用', 'grid', 1, [ + ['海大', 'http://www.ouc.edu.cn/'], + ['阿里云', 'https://www.aliyun.com/'], + ['知乎', 'https://www.zhihu.com/'], + ['天翼云盘', 'https://cloud.189.cn/'], + ['快科技', 'http://www.mydrivers.com/'], + ['轻舟', 'https://yangjh.net/'], + ['老杨', 'https://yangyq.net/'], + ['红围脖', 'https://hongbowei.com/'], + ]], + ['工具', 'grid', 2, [ + ['宝塔', 'https://bt.yangyq.net:8868/44d5b346'], + ['海大VPN', 'https://otrust.ouc.edu.cn'], + ['BB系统', 'https://wlkc.ouc.edu.cn/'], + ['教务系统', 'http://jwgl.ouc.edu.cn/'], + ['信息门户', 'http://my.ouc.edu.cn/'], + ['信息学部', 'http://it.ouc.edu.cn/'], + ['淘宝', 'https://www.taobao.com/'], + ['京东', 'https://www.jd.com/'], + ['日历', 'https://nas.yangyq.net/calendar'], + ['办公Nas', 'https://nas.yangyq.net/'], + ['家庭Nas', 'https://home.yangyq.net/'], + ['Git', 'https://git.yangyq.net/'], + ['网盘', 'https://nas.yangyq.net/drive/'], + ['电影', 'https://movie.yangyq.net/'], + ['相册', 'https://home.yangyq.net/photo'], + ['文件', 'https://nas.yangyq.net/file'], + ['bilibili', 'https://www.bilibili.com/'], + ['优酷', 'https://www.youku.com/'], + ['爱奇艺', 'https://www.iqiyi.com/'], + ['腾讯视频', 'https://v.qq.com/'], + ['芒果TV', 'https://www.mgtv.com/'], + ['Draw.IO', 'https://draw.yangyq.net'], + ['稿定PS', 'https://ps.gaoding.com/'], + ['密码生成', 'https://n.yangyq.net/password/'], + ['PDF工具', 'https://pdf.wps.cn/'], + ['微信读书', 'https://weread.qq.com/'], + ['高德地图', 'https://gaode.com/'], + ['百度地图', 'https://map.baidu.com/'], + ['建设银行', 'http://ccb.com/cn/home/index.html'], + ['海大财务', 'http://hdcw.ouc.edu.cn'], + ['MOOC', 'https://www.icourse163.org/'], + ]], + ['常用软件', 'tabs', 10, [ + ['WPS', 'https://www.wps.cn/'], + ['Chrome', 'https://www.google.cn/chrome/index.html'], + ['VS Code', 'https://code.visualstudio.com/'], + ['Draw.IO', 'https://github.com/jgraph/drawio-desktop/releases'], + ['火绒', 'https://www.huorong.cn/'], + ['7-Zip', 'https://sparanoid.com/lab/7z/'], + ['Tabby', 'https://github.com/Eugeny/tabby/'], + ['GitAhead', 'https://gitahead.github.io/gitahead.com/'], + ['Everything', 'https://www.voidtools.com/zh-cn/'], + ['Wox', 'https://github.com/Wox-launcher/Wox'], + ['BCUninstaller', 'https://github.com/Klocman/Bulk-Crap-Uninstaller'], + ['DBeaver', 'https://github.com/dbeaver/dbeaver'], + ['Thunderbird', 'https://www.thunderbird.net/zh-CN/'], + ['FDM', 'https://www.freedownloadmanager.org/zh/'], + ['GIMP', 'https://www.gimp.org/'], + ['VLC', 'https://www.videolan.org/vlc/'], + ['Zotero', 'https://www.zotero.org/'], + ['QQ音乐', 'https://y.qq.com/'], + ['微信', 'https://weixin.qq.com/'], + ['TIM', 'https://tim.qq.com/'], + ['Shotcut', 'https://github.com/mltframework/shotcut'], + ]], + ['图片资源', 'tabs', 11, [ + ['图标库', 'https://www.iconfont.cn/'], + ['插画库', 'https://undraw.co/illustrations'], + ['Pixabay', 'https://pixabay.com/zh/'], + ['Pexels', 'https://www.pexels.com/'], + ]], + ['科研论文', 'tabs', 12, [ + ['DeepL翻译', 'https://www.deepl.com/'], + ['Linggle', 'https://linggle.com/'], + ['Netspeak', 'https://netspeak.org/'], + ['Semantic Scholar', 'https://www.semanticscholar.org/'], + ['LaTex Table', 'https://www.tablesgenerator.com/'], + ]], + ]; + + $g = $pdo->prepare('INSERT INTO groups (name, slug, display, sort_order, is_visible) VALUES (?,?,?,?,1)'); + $s = $pdo->prepare('INSERT INTO sites (group_id, url, name, short_name, icon, sort_order, is_visible, created_at, updated_at) VALUES (?,?,?,?,\'\',?,1,?,?)'); + $now = date('Y-m-d H:i:s'); + foreach ($data as $row) { + [$name, $display, $order, $sites] = $row; + $g->execute([$name, slugify($name), $display, $order]); + $gid = (int)$pdo->lastInsertId(); + $i = 0; + foreach ($sites as $site) { + $s->execute([$gid, $site[1], $site[0], $site[0], $i++, $now, $now]); + } + } +} + +/** 生成 slug(中文退化为拼音不可用时用 md5 短串) */ +function slugify(string $s): string +{ + $s = trim($s); + if (preg_match('/^[a-z0-9\-]+$/i', $s)) { + return strtolower($s); + } + return 'g' . substr(md5($s), 0, 6); +} diff --git a/deploy/nginx-simplenav-deny-data.conf b/deploy/nginx-simplenav-deny-data.conf new file mode 100644 index 0000000..e990f2e --- /dev/null +++ b/deploy/nginx-simplenav-deny-data.conf @@ -0,0 +1,14 @@ +# SimpleNav 安全规则:禁止通过 Web 访问数据库等敏感目录 +# 用法:把下面几行加入宝塔站点 n.yangyq.net 的 nginx server{} 块中,然后 reload nginx。 +# (放在 /simplenav 子目录部署时用第一条;以后放根目录时用第二组。) + +# —— 子目录部署(当前测试阶段)—— +location ~ ^/simplenav/data/ { deny all; return 404; } +location ~ ^/simplenav/\.git/ { deny all; return 404; } + +# —— 以后放到根目录时启用 —— +# location ~ ^/data/ { deny all; return 404; } +# location ~ ^/\.git/ { deny all; return 404; } + +# 可选:禁止 uploads 目录执行脚本(防上传木马) +location ~* ^/simplenav/uploads/.*\.(php|phtml|phar)$ { deny all; return 404; } diff --git a/deploy/setup-simplenav-user.sh b/deploy/setup-simplenav-user.sh new file mode 100644 index 0000000..2548081 --- /dev/null +++ b/deploy/setup-simplenav-user.sh @@ -0,0 +1,122 @@ +#!/bin/bash +# +# SimpleNav(简航)部署账号初始化脚本 +# 在服务器上以 root 身份运行一次即可(可重复运行,幂等) +# +# 作用: +# 1. 创建 simplenav 部署用户(无密码,只能密钥登录,无任何特权组) +# 2. 安装本地 ssh 公钥 +# 3. 只给 /www/wwwroot/n.yangyq.net 的读写权限(POSIX ACL,不改原有属主) +# 4. 预建应用目录 /simplenav/{data,uploads/icons} 并让部署用户与 web 用户都可读写 +# +set -euo pipefail + +# ==================== 配置 ==================== +DEPLOY_USER="simplenav" +SITE="/www/wwwroot/n.yangyq.net" +APP_SUBDIR="simplenav" # 先部署到子目录测试,通过后再放根目录 +PUBKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOCvCGMMkyf9CHaedSdZe4XfXrS0MCW15REibldEF6U4 deploy@n.yangyq.net" +# ============================================== + +[ "$(id -u)" -eq 0 ] || { echo "错误:请用 root 运行"; exit 1; } +[ -d "$SITE" ] || { echo "错误:目录不存在 $SITE"; exit 1; } +command -v setfacl >/dev/null 2>&1 || { + echo "错误:缺少 setfacl,请先安装 acl" + echo " CentOS/RHEL/统信: yum install -y acl" + echo " Debian/Ubuntu: apt-get install -y acl" + exit 1 +} + +WEB_USER=$(stat -c '%U' "$SITE") +echo "站点目录属主(web 用户):$WEB_USER" +echo + +# ---------- 1. 创建用户 ---------- +if id "$DEPLOY_USER" >/dev/null 2>&1; then + echo "[=] 用户 $DEPLOY_USER 已存在,跳过创建" +else + useradd -m -s /bin/bash "$DEPLOY_USER" + passwd -l "$DEPLOY_USER" >/dev/null 2>&1 || true + echo "[+] 已创建用户 $DEPLOY_USER(密码锁定,仅允许密钥登录)" +fi +for g in wheel sudo adm root; do + if id -nG "$DEPLOY_USER" | tr ' ' '\n' | grep -qx "$g"; then + gpasswd -d "$DEPLOY_USER" "$g" >/dev/null 2>&1 || true + echo "[!] 已从特权组 $g 中移除 $DEPLOY_USER" + fi +done +echo "[i] 当前所属组:$(id -nG "$DEPLOY_USER")" + +# ---------- 2. 安装公钥 ---------- +install -d -m 700 -o "$DEPLOY_USER" -g "$DEPLOY_USER" "/home/$DEPLOY_USER/.ssh" +AUTH="/home/$DEPLOY_USER/.ssh/authorized_keys" +touch "$AUTH" +if grep -qF "$PUBKEY" "$AUTH"; then + echo "[=] 公钥已存在,跳过" +else + echo "$PUBKEY" >> "$AUTH" + echo "[+] 公钥已写入 authorized_keys" +fi +chmod 600 "$AUTH" +chown "$DEPLOY_USER:$DEPLOY_USER" "$AUTH" +chmod 700 "/home/$DEPLOY_USER" +echo + +# ---------- 3. 上级目录穿透 ---------- +for d in /www /www/wwwroot; do + if [ -d "$d" ]; then + other_bit=$(stat -c '%a' "$d"); other_bit=${other_bit: -1} + if [ "$other_bit" -lt 1 ]; then + setfacl -m "u:$DEPLOY_USER:x" "$d" + echo "[+] 已为 $d 补充 $DEPLOY_USER 的穿透(x)权限" + fi + fi +done + +# ---------- 4. 目录授权(ACL,不改属主) ---------- +# 站点根下可能有 root 属主文件(宝塔 .user.ini),setfacl -R 会报 EPERM,需要容错 +acl_denied="" +setfacl -R -m "u:$DEPLOY_USER:rwX" "$SITE" 2>/dev/null || acl_denied="yes" +setfacl -R -d -m "u:$DEPLOY_USER:rwX" "$SITE" 2>/dev/null || acl_denied="yes" +if [ "$WEB_USER" != "root" ] && [ "$WEB_USER" != "UNKNOWN" ]; then + setfacl -R -m "u:$WEB_USER:rwX" "$SITE" 2>/dev/null || true + setfacl -R -d -m "u:$WEB_USER:rwX" "$SITE" 2>/dev/null || true +fi +if [ -n "$acl_denied" ]; then + find "$SITE" ! -user "$WEB_USER" -print0 2>/dev/null | while IFS= read -r -d '' p; do + printf " [跳过·属主非 %s] %s\n" "$WEB_USER" "$p" + done + echo "[!] 有部分条目未能设置 ACL(通常是 root 属主的 .user.ini,不影响部署)" +fi +echo "[+] 已对 $SITE 授予 $DEPLOY_USER 与 $WEB_USER 读写权限(属主未改动)" +echo + +# ---------- 5. 预建应用可写目录 ---------- +APP="$SITE/$APP_SUBDIR" +install -d -m 775 "$APP/data" "$APP/uploads/icons" +setfacl -R -m "u:$DEPLOY_USER:rwX,u:$WEB_USER:rwX" "$APP" 2>/dev/null || true +setfacl -R -d -m "u:$DEPLOY_USER:rwX,u:$WEB_USER:rwX" "$APP" 2>/dev/null || true +echo "[+] 已预建 $APP/{data,uploads/icons},并让 $DEPLOY_USER 与 $WEB_USER 可读写" +echo + +# ---------- 6. 验收 ---------- +echo "==================== 验收 ====================" +echo "用户信息: $(id "$DEPLOY_USER")" +echo "密钥文件: $(stat -c '%a %U:%G' "$AUTH")" +echo +echo -n "以 $DEPLOY_USER 身份在 $SITE 写测试: " +if su -s /bin/sh "$DEPLOY_USER" -c "touch '$SITE/.deploy_write_test' && rm -f '$SITE/.deploy_write_test'"; then + echo "通过" +else + echo "失败(请检查上面的 ACL 输出)" +fi +echo -n "以 $DEPLOY_USER 身份读 /etc/shadow: " +if su -s /bin/sh "$DEPLOY_USER" -c "cat /etc/shadow" >/dev/null 2>&1; then + echo "竟然可读(异常)" +else + echo "被拒绝(正常)" +fi +echo "==============================================" +echo +echo "完成后请把下面这行结果发我:" +echo " sudo -l -U $DEPLOY_USER" diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4f2641f5ea218dd822c9a3b2be722b99d7c8ad49 GIT binary patch literal 11393 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 [ + 'timeout' => $timeout, + 'header' => "User-Agent: Mozilla/5.0 SimpleNav\r\n", + 'follow_location' => 1, + ]]); + $body = @file_get_contents($url, false, $ctx, 0, $maxBytes); + if ($body === false) { + return null; + } + return ['body' => $body, 'url' => $url, 'type' => '']; + } + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_MAXREDIRS => 5, + CURLOPT_TIMEOUT => $timeout, + CURLOPT_CONNECTTIMEOUT => 5, + CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; SimpleNav/1.0)', + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => 0, + CURLOPT_ENCODING => '', + ]); + $body = curl_exec($ch); + $type = (string)curl_getinfo($ch, CURLINFO_CONTENT_TYPE); + $eff = (string)curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); + $err = curl_errno($ch); + curl_close($ch); + if ($err || $body === false) { + return null; + } + return ['body' => $body, 'url' => $eff ?: $url, 'type' => $type]; +} + +/** + * 抓取网站元信息:标题、名称、图标地址。 + * @return array{title:string,name:string,icon:string,final_url:string} + */ +function fetch_site_meta(string $url): array +{ + $url = norm_url($url); + $out = ['title' => '', 'name' => '', 'icon' => '', 'final_url' => $url]; + $resp = http_get($url); + if ($resp === null) { + return $out; + } + $html = $resp['body']; + $final = $resp['url']; + $out['final_url'] = $final; + + // 编码:优先按 转换到 UTF-8 + $html = ensure_utf8($html); + + // + if (preg_match('#<title[^>]*>(.*?)#is', $html, $m)) { + $out['title'] = trim(preg_replace('/\s+/', ' ', html_entity_decode($m[1], ENT_QUOTES, 'UTF-8'))); + } + // og:site_name 优先作为"名称" + if (preg_match('#]+property=["\']og:site_name["\'][^>]+content=["\']([^"\']+)#i', $html, $m)) { + $out['name'] = trim(html_entity_decode($m[1], ENT_QUOTES, 'UTF-8')); + } elseif ($out['title'] !== '') { + // 用标题的首段(按分隔符切分)作为名称 + $t = preg_split('#[\|\-_—·::]#u', $out['title']); + $out['name'] = trim($t[0]); + } + + // 图标: + $icon = ''; + if (preg_match_all('#]+>#i', $html, $links)) { + foreach ($links[0] as $tag) { + if (preg_match('#rel=["\'][^"\']*icon[^"\']*["\']#i', $tag)) { + if (preg_match('#href=["\']([^"\']+)["\']#i', $tag, $hm)) { + $cand = html_entity_decode($hm[1], ENT_QUOTES, 'UTF-8'); + // 优先 apple-touch-icon(通常更大更清晰) + if (stripos($tag, 'apple-touch') !== false) { + $icon = $cand; + break; + } + if ($icon === '') { + $icon = $cand; + } + } + } + } + } + if ($icon !== '') { + $out['icon'] = absolutize($icon, $final); + } else { + $p = parse_url($final); + if (!empty($p['scheme']) && !empty($p['host'])) { + $out['icon'] = $p['scheme'] . '://' . $p['host'] . '/favicon.ico'; + } + } + return $out; +} + +/** 把可能为相对路径的 URL 补全为绝对 URL */ +function absolutize(string $href, string $base): string +{ + if (preg_match('#^https?://#i', $href)) { + return $href; + } + $p = parse_url($base); + $root = ($p['scheme'] ?? 'http') . '://' . ($p['host'] ?? ''); + if (strpos($href, '//') === 0) { + return ($p['scheme'] ?? 'http') . ':' . $href; + } + if (strpos($href, '/') === 0) { + return $root . $href; + } + $dir = rtrim(dirname($p['path'] ?? '/'), '/'); + return $root . $dir . '/' . $href; +} + +/** 简单编码归一:识别 GBK/GB18030 并转 UTF-8 */ +function ensure_utf8(string $s): string +{ + if (preg_match('#]+charset=["\']?\s*(gbk|gb2312|gb18030)#i', $s)) { + if (function_exists('mb_convert_encoding')) { + return mb_convert_encoding($s, 'UTF-8', 'GB18030'); + } + } + if (function_exists('mb_check_encoding') && !mb_check_encoding($s, 'UTF-8')) { + return mb_convert_encoding($s, 'UTF-8', 'GB18030'); + } + return $s; +} + +/** + * 下载图标并保存到本地(PNG,64x64,透明背景)。 + * @return string 本地文件名(如 ab12cd.png),失败返回 '' + */ +function download_icon(string $iconUrl, string $key, int $size = 64): string +{ + if ($iconUrl === '') { + return ''; + } + if (!is_dir(ICON_PATH)) { + @mkdir(ICON_PATH, 0755, true); + } + $resp = http_get($iconUrl, 500000, 8); + if ($resp === null || $resp['body'] === '') { + return ''; + } + $bin = $resp['body']; + $name = md5($key) . '.png'; + $dest = ICON_PATH . '/' . $name; + + // 用 GD 统一转成 PNG(顺带裁成正方形、缩放) + if (function_exists('imagecreatefromstring')) { + $im = @imagecreatefromstring($bin); + if ($im !== false) { + $w = imagesx($im); + $h = imagesy($im); + $side = min($w, $h); + $square = imagecreatetruecolor($size, $size); + imagealphablending($square, false); + imagesavealpha($square, true); + $trans = imagecolorallocatealpha($square, 0, 0, 0, 127); + imagefill($square, 0, 0, $trans); + $sx = (int)(($w - $side) / 2); + $sy = (int)(($h - $side) / 2); + imagecopyresampled($square, $im, 0, 0, $sx, $sy, $size, $size, $side, $side); + imagepng($square, $dest); + imagedestroy($square); + imagedestroy($im); + return $name; + } + } + // 无 GD:原样保存(按 content-type 猜扩展名) + $ext = 'ico'; + if (stripos($resp['type'], 'png') !== false) { + $ext = 'png'; + } elseif (stripos($resp['type'], 'svg') !== false) { + $ext = 'svg'; + } elseif (stripos($resp['type'], 'jpeg') !== false || stripos($resp['type'], 'jpg') !== false) { + $ext = 'jpg'; + } elseif (stripos($resp['type'], 'gif') !== false) { + $ext = 'gif'; + } + $name = md5($key) . '.' . $ext; + @file_put_contents(ICON_PATH . '/' . $name, $bin); + return is_file(ICON_PATH . '/' . $name) ? $name : ''; +} + +/** 图标显示地址(为空时返回 '',前端回退到首字母块) */ +function icon_src(string $file): string +{ + return $file === '' ? '' : url(ICON_URL . '/' . $file); +} + +/** 名称首字配色(无图标时的回退头像底色) */ +function letter_color(string $s): string +{ + $palette = ['#2F6BF7', '#5B8DEF', '#0EA5E9', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#14B8A6', '#F97316']; + return $palette[abs(crc32($s)) % count($palette)]; +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..7d5318a --- /dev/null +++ b/index.php @@ -0,0 +1,140 @@ +query("SELECT * FROM groups WHERE is_visible=1 AND display='grid' ORDER BY sort_order, id")->fetchAll(); +$tabGroups = $pdo->query("SELECT * FROM groups WHERE is_visible=1 AND display='tabs' ORDER BY sort_order, id")->fetchAll(); +$engines = $pdo->query("SELECT * FROM engines WHERE is_active=1 ORDER BY sort_order, id")->fetchAll(); + +// 默认引擎 +$defSlug = setting('default_engine', 'bing'); +$default = null; +foreach ($engines as $en) { + if ($en['slug'] === $defSlug) { $default = $en; break; } +} +if ($default === null) { $default = $engines[0] ?? null; } + +function sites_of(int $gid): array +{ + $st = db()->prepare("SELECT * FROM sites WHERE group_id=? AND is_visible=1 ORDER BY sort_order, id"); + $st->execute([$gid]); + return $st->fetchAll(); +} + +function site_label(array $s): string +{ + return $s['short_name'] !== '' ? $s['short_name'] : ($s['name'] !== '' ? $s['name'] : domain_of($s['url'])); +} +?> + + + + + +<?= e(site_name()) ?> · <?= e(setting('subtitle', '个人网站导航')) ?> + + + + + + + + + +
    + + + + + + + + + + + +
    +
    合集
    +
    + $g): ?> + + +
    +
    + $g): $items = sites_of((int)$g['id']); ?> + + +
    +
    + + + +
    + + + + + diff --git a/login.php b/login.php new file mode 100644 index 0000000..9b19bb9 --- /dev/null +++ b/login.php @@ -0,0 +1,64 @@ +query('SELECT COUNT(*) FROM users')->fetchColumn() > 0; +$err = ''; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $u = trim((string)($_POST['username'] ?? '')); + $p = (string)($_POST['password'] ?? ''); + if ($u === '' || $p === '') { + $err = '请输入用户名和密码'; + } elseif (mb_strlen($p) < 4) { + $err = '密码至少 4 位'; + } elseif (!$hasAdmin) { + // 首次创建管理员 + $st = db()->prepare('INSERT INTO users (username, password_hash, created_at) VALUES (?,?,?)'); + $st->execute([$u, password_hash($p, PASSWORD_DEFAULT), date('Y-m-d H:i:s')]); + $_SESSION['uid'] = (int)db()->lastInsertId(); + header('Location: ' . url('admin/')); + exit; + } else { + // 登录校验 + $st = db()->prepare('SELECT * FROM users WHERE username=? LIMIT 1'); + $st->execute([$u]); + $row = $st->fetch(); + if ($row && password_verify($p, $row['password_hash'])) { + $_SESSION['uid'] = (int)$row['id']; + header('Location: ' . url('admin/')); + exit; + } + $err = '用户名或密码错误'; + } +} +$title = $hasAdmin ? '登录' : '初始化管理员'; +?> + + + + +<?= e($title) ?> · <?= e(site_name()) ?> + + + + + + + diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..e028230 --- /dev/null +++ b/logout.php @@ -0,0 +1,14 @@ + '', 'items' => []]); + exit; +} + +$items = []; +try { + $st = db()->prepare('SELECT * FROM engines WHERE slug=? AND is_active=1 LIMIT 1'); + $st->execute([$slug]); + $engine = $st->fetch(); + if ($engine && $engine['suggest_url'] !== '') { + $u = str_replace('{q}', rawurlencode($q), $engine['suggest_url']); + $resp = http_get($u, 100000, 5); + if ($resp !== null) { + $items = parse_suggest($engine['suggest_type'], ensure_utf8($resp['body']), $q); + } + } +} catch (Throwable $e) { + $items = []; +} + +// 去重、限长、裁剪 +$items = array_values(array_unique(array_filter(array_map('trim', $items)))); +$items = array_slice($items, 0, 10); + +echo json_encode(['q' => $q, 'items' => $items], JSON_UNESCAPED_UNICODE); +exit; + +/** 按引擎类型解析联想结果 */ +function parse_suggest(string $type, string $body, string $q): array +{ + $body = trim($body); + $out = []; + + switch ($type) { + case 'bing_osjson': + case 'google_firefox': + // ["query", ["s1","s2",...]] + $j = json_decode($body, true); + if (is_array($j) && isset($j[1]) && is_array($j[1])) { + $out = $j[1]; + } + break; + + case 'baidu_sugrec': + // {"q":"x","g":[{"type":"..","q":".."}], ...} + $j = json_decode($body, true); + if (is_array($j)) { + if (!empty($j['g']) && is_array($j['g'])) { + foreach ($j['g'] as $it) { if (isset($it['q'])) $out[] = $it['q']; } + } elseif (!empty($j['s']) && is_array($j['s'])) { + $out = $j['s']; + } + } + break; + + case 'so360': + // {"s":[".."]} 或 {"result":[{"word":".."}]} + $j = json_decode($body, true); + if (is_array($j)) { + if (!empty($j['s']) && is_array($j['s'])) { $out = $j['s']; } + elseif (!empty($j['result']) && is_array($j['result'])) { + foreach ($j['result'] as $it) { if (isset($it['word'])) $out[] = $it['word']; } + } + } + break; + + case 'sogou_ajaj': + // {"query":"x","items":["a","b"]}(可能是 GBK,已在上面转码) + $j = json_decode($body, true); + if (is_array($j) && !empty($j['items']) && is_array($j['items'])) { + $out = $j['items']; + } else { + // 兜底:正则抓引号字符串 + if (preg_match_all('#"([^"]{1,40})"#u', $body, $m)) { + $out = array_slice($m[1], 1); // 跳过 query 自身 + } + } + break; + + case 'ddg': + // [{"phrase":".."},...] + $j = json_decode($body, true); + if (is_array($j)) { + foreach ($j as $it) { + if (is_array($it) && isset($it['phrase'])) { $out[] = $it['phrase']; } + elseif (is_string($it)) { $out[] = $it; } + } + } + break; + + default: + // 通用兜底:尝试 JSON,再退化正则 + $j = json_decode($body, true); + if (is_array($j)) { + if (isset($j[1]) && is_array($j[1])) { $out = $j[1]; } + elseif (!empty($j['s']) && is_array($j['s'])) { $out = $j['s']; } + } + } + return $out; +} diff --git a/uploads/.htaccess b/uploads/.htaccess new file mode 100644 index 0000000..7f135f3 --- /dev/null +++ b/uploads/.htaccess @@ -0,0 +1,12 @@ +# 禁止在 uploads 目录执行脚本(只允许作为静态资源访问) + + + Require all denied + + + Order allow,deny + Deny from all + + +Options -ExecCGI +AddType text/plain .php .phtml .phar diff --git a/uploads/icons/.gitkeep b/uploads/icons/.gitkeep new file mode 100644 index 0000000..e69de29