├── .gitignore ├── LICENSE ├── README.md ├── api ├── Poncon.php ├── add_collect.php ├── config.default.php ├── delete_collect.php ├── fenci.php ├── get_collect_list.php ├── get_collect_list_by_tag.php ├── get_tag_list.php ├── get_title.php ├── login.php ├── public_share_list.php ├── register.php └── search.php ├── css └── index.css ├── favicon.ico ├── img ├── at_phone.jpg ├── cat-2722309_640.png ├── chevron-left-solid.svg ├── insect-6626635_1920.jpg ├── plus-solid.svg ├── trash-can-solid.svg ├── uTools_1656678414889.png ├── uTools_1656678450484.png ├── uTools_1656678465902.png ├── uTools_1656678546233.png ├── uTools_1656678565171.png └── uTools_1656679524839.png ├── index.php ├── js ├── index.js ├── poncon.js └── poncon.min.js ├── public ├── api │ ├── Poncon.php │ ├── add_collect.php │ ├── config.default.php │ ├── delete_collect.php │ ├── fenci.php │ ├── get_collect_list.php │ ├── get_collect_list_by_tag.php │ ├── get_tag_list.php │ ├── get_title.php │ ├── login.php │ ├── public_share_list.php │ ├── register.php │ └── search.php ├── css │ └── index.css ├── img │ ├── cat-2722309_640.png │ ├── chevron-left-solid.svg │ ├── insect-6626635_1920.jpg │ ├── plus-solid.svg │ └── trash-can-solid.svg ├── index.php ├── js │ ├── index.js │ └── poncon.js └── share │ └── index.php └── share └── index.php /.gitignore: -------------------------------------------------------------------------------- 1 | # 配置 2 | config.php -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mypages 2 | 3 | 一款标签化管理网页收藏的工具 4 | 5 | ## 开发宗旨 6 | 7 | - 简约、轻量、便捷 8 | 9 | ## 安装说明 10 | 11 | - 将 `public` 目录上传到主机 12 | - 复制 `config.default.php` 为 `config.php` 13 | - 将 `config.php` 中的数据库信息填写完整 14 | - 运行 `index.html` 即可 15 | 16 | ## 项目介绍 17 | 18 | 当你想要收藏一个网站,而苦恼应该怎么分类的话,你可以尝试使用标签管理法。 19 | 20 | 对于一个综合型的网站,我们为其打上“影视”、“免费”、“学术”等标签,方便我们随性标记和下次方便找到。 21 | 22 | 我们平台推出了“标签精确匹配方法”,你可以按照不同的标签组合匹配出更加符合要求的记录,也提高了标签的复用性。 23 | 24 | 我们可以采用“联想法”去定义我们的标签序列,因为一般情况下,我们会输入我们联想到的关键词去匹配结果,我们将最容易联想的关键词作为标签,附与我们的收藏记录中,并且我们要求标签的定义需要遵从精简的原则,比如“编程教程视频”,我们需要拆解为“编程”、“教程”、“视频”,方便我们下次匹配如“搞笑+视频”、“编程+视频”这样的组合。 25 | 26 | 如果一次联想不能让我们定义出满意的标签序列,在我们下一次匹配某条记录时,可以根据匹配情况对该记录的标签系列进行编辑,以增加下次匹配的正确性。 27 | 28 | 我们在搜索模块上,分为了全局搜索和标签匹配,前者会按照记录的标题、URL 、标签、备注进行综合搜索,而后者是按照指定的标签组合进行精准匹配,我们可以结合两种方法,不断完善我们各自的标签系统。 29 | 30 | 我们平台支持增加收藏记录、定义标签序列、记录随时可编辑、主页共享、多功能搜索等功能。 31 | 32 | 我们的这套方法,会随着我们收藏记录和标签数量的增加而更加贴合我们的使用习惯,让我们从此解放大脑,随性收藏,不再错过精彩内容。 33 | 34 | ## 功能 35 | 36 | - 用户登录注册 37 | - 添加URL收藏,一键获取title,并支持设置标签 38 | - 整体列表按时间排序,可按标签筛选列表 39 | - 可随时编辑和删除收藏记录 40 | - 根据收藏列表自动生成标签列表,用户可多选标签进行一次性加载 41 | - 可生成个人收藏页,不显示私密部分(bata) 42 | - 主页上显示完整收藏列表,列表项上包含标签 43 | - 单击按钮弹出模态框,可以新增收藏 44 | - 点击列表中的标签,弹出模态框加载该标签(bata) 45 | - 不支持重复URL 46 | 47 | ### 待改进 48 | 49 | - 接入微信公众号用户系统 50 | - 标签筛选列表改为数量排序 51 | - 标签筛选时自动滚动滚动条 52 | 53 | ### 标签定义规则推荐 54 | 55 | - 标签能拆解的,尽量拆解,比如 `编程教程`,拆成 `编程` + `教程` 56 | - 对于实在不能拆解的,需保留,比如 `搜索引擎` 不能拆成 `搜索` + `引擎` 57 | - 这样一来,可以最大限度减少标签数量,提高标签复用性和筛选效率 58 | 59 | ## 关于 60 | 61 | - 作者:欧阳鹏 62 | - 个人主页:https://ouyangpeng.top 63 | - 一直在努力学习和探索中,欢迎关注我~ 64 | 65 | ## 示意图 66 | 67 | ![手机端](img/uTools_1656679524839.png) 68 | 69 | ![电脑网页端 - 首页](img/uTools_1656678414889.png) 70 | 71 | ![电脑网页端 - 编辑收藏](img/uTools_1656678450484.png) 72 | 73 | ![电脑网页端 - 搜索列表](img/uTools_1656678465902.png) 74 | 75 | ![电脑网页端 - 标签列表](img/uTools_1656678546233.png) 76 | 77 | ![电脑网页端 - 分享页面](img/uTools_1656678565171.png) 78 | -------------------------------------------------------------------------------- /api/Poncon.php: -------------------------------------------------------------------------------- 1 | $code, 108 | 'msg' => $msg 109 | ])); 110 | } 111 | 112 | /** 113 | * 返回成功信息,语法:success($msg); 114 | * @param string $msg 成功信息 115 | */ 116 | function success($msg, ...$args) 117 | { 118 | echo json_encode([ 119 | 'code' => 200, 120 | 'msg' => $msg, 121 | 'data' => isset($args[0]) ? $args[0] : null 122 | ]); 123 | } 124 | 125 | /** 126 | * 获取配置信息,语法:getConfig(); 127 | */ 128 | function getConfig() 129 | { 130 | require 'config.php'; 131 | return $config; 132 | } 133 | 134 | /** 135 | * 初始化数据库,语法:initDB(); 136 | */ 137 | function initDb() 138 | { 139 | $config = $this->getConfig(); 140 | $conn = mysqli_connect($config['mysql']['host'], $config['mysql']['user'], $config['mysql']['pass'], $config['mysql']['db']); 141 | if (!$conn) { 142 | die(json_encode([ 143 | 'code' => 903, 144 | 'msg' => '数据库错误' 145 | ])); 146 | } 147 | 148 | // 新建用户表 149 | $table = $config['table']['user']; 150 | $sql = "CREATE TABLE IF NOT EXISTS `$table` ( 151 | `id` int(11) NOT NULL AUTO_INCREMENT, 152 | `username` varchar(255) NOT NULL, -- 用户名 153 | `password` varchar(255) NOT NULL, -- 密码 md5密文 154 | `register_time` int(11) NOT NULL, -- 注册时间 155 | PRIMARY KEY (`id`) -- 主键 156 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; 157 | $result = mysqli_query($conn, $sql); 158 | if (!$result) { 159 | $this->error(903, '数据库错误'); 160 | } 161 | // 新建收藏表 162 | $table = $config['table']['collect']; 163 | $sql = "CREATE TABLE IF NOT EXISTS `$table` ( 164 | `id` int(11) NOT NULL AUTO_INCREMENT, 165 | `username` varchar(255) NOT NULL, -- 收藏者用户名 166 | `title` TEXT NOT NULL, -- 网页标题 167 | `url` TEXT NOT NULL, -- 网址 168 | `update_time` int(11) NOT NULL, -- 更新时间 169 | `tag_list` TEXT NOT NULL, -- 标签列表,以逗号分隔 170 | `note` TEXT NOT NULL, -- 备注 171 | `private` int(11) NOT NULL, -- 0:公开 1:私密 172 | PRIMARY KEY (`id`) -- 主键 173 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; 174 | $result = mysqli_query($conn, $sql); 175 | if (!$result) { 176 | $this->error(903, '数据库错误'); 177 | } 178 | return $conn; 179 | } 180 | 181 | /** 182 | * 登录验证,语法:login($conn, $username, $password); 183 | * @param object $conn 数据库连接 184 | * @param string $username 用户名 185 | * @param string $password 密码 186 | * @return array|null 187 | */ 188 | function login($conn, $username, $password) 189 | { 190 | $config = $this->getConfig(); 191 | $table = $config['table']['user']; 192 | $sql = "SELECT * FROM `$table` WHERE `username` = '$username' AND `password` = '$password'"; 193 | $result = mysqli_query($conn, $sql); 194 | if (!$result) { 195 | $this->error(903, '数据库错误'); 196 | } 197 | $row = mysqli_fetch_assoc($result); 198 | if (!$row) { 199 | $this->error(907, '用户名或密码错误'); 200 | } 201 | return $row; 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /api/add_collect.php: -------------------------------------------------------------------------------- 1 | initDb(); 14 | 15 | $username = $poncon->POST('username', null, true); 16 | $password = $poncon->POST('password', null, true); 17 | $tags = $poncon->POST('tags', null, true); // JSON数组[...] 18 | $title = $poncon->POST('title', null, true); 19 | $url = $poncon->POST('url', null, true); 20 | $private = $poncon->POST('private', 0, true); // 0:公开 1:私密 21 | $note = $poncon->POST('note', null, true); // 备注 22 | $update_time = time(); 23 | $mode = $poncon->POST('mode', 'add', true); // add:新增 update:更新 24 | 25 | if (!$username || !$password || !$title || !$url) { 26 | $poncon->error(900, '参数缺失'); 27 | } 28 | 29 | // 登录验证 30 | $poncon->login($conn, $username, $password); 31 | 32 | $config = $poncon->getConfig(); 33 | $table = $config['table']['collect']; 34 | 35 | // 判断URL是否存在 36 | 37 | $sql = "SELECT `url` FROM `$table` WHERE `url` = '$url' LIMIT 1;"; 38 | $result = mysqli_query($conn, $sql); 39 | if (mysqli_num_rows($result) > 0 && $mode == 'add') { 40 | $poncon->error(904, '记录已经存在'); 41 | } else if (mysqli_num_rows($result) == 0 && $mode == 'update') { 42 | $poncon->error(904, '记录不存在'); 43 | } 44 | if ($mode == 'add') { 45 | // 增加收藏 46 | $sql = "INSERT INTO `$table` (`username`, `tag_list`, `update_time`, `title`, `url`, `private`, `note`) VALUES ('$username', '$tags', $update_time, '$title', '$url', $private, '$note');"; 47 | } else if ($mode == 'update') { 48 | // 更新 49 | $sql = "UPDATE `$table` SET `tag_list` = '$tags', `title` = '$title', `url` = '$url', `private` = '$private', `update_time` = '$update_time', `note` = '$note' WHERE `url` = '$url' LIMIT 1;"; 50 | } 51 | 52 | $result = mysqli_query($conn, $sql); 53 | if (!$result) { 54 | $poncon->error(903, '数据库错误'); 55 | } 56 | 57 | $poncon->success('成功'); 58 | -------------------------------------------------------------------------------- /api/config.default.php: -------------------------------------------------------------------------------- 1 | [ 10 | 'host' => 'localhost', 11 | 'user' => 'root', 12 | 'pass' => '', // 数据库密码 13 | 'db' => '' // 数据库名称 14 | ], 15 | 'table' => [ 16 | 'user' => 'mypages_user', // 用户表 17 | 'collect' => 'mypages_collect' // 收藏表 18 | ] 19 | ]; 20 | 21 | -------------------------------------------------------------------------------- /api/delete_collect.php: -------------------------------------------------------------------------------- 1 | initDb(); 13 | 14 | $config = $poncon->getConfig(); 15 | 16 | $data = []; 17 | 18 | $table = $config['table']['collect']; 19 | 20 | $username = $poncon->POST('username', null, true); 21 | $password = $poncon->POST('password', null, true); 22 | // 通过URL和时间戳去删除收藏 23 | $url = $poncon->POST('url', null, true); 24 | $time = $poncon->POST('time', null, true); 25 | 26 | if (!$username || !$password || !$url || !$time) { 27 | $poncon->error(900, '参数缺失'); 28 | } 29 | 30 | $poncon->login($conn, $username, $password); 31 | 32 | $sql = "DELETE FROM `$table` WHERE `username` = '$username' AND `url` = '$url' AND `update_time` = '$time';"; 33 | $result = mysqli_query($conn, $sql); 34 | 35 | if ($result) { 36 | $poncon->success('删除成功'); 37 | } else { 38 | $poncon->error(910, '删除失败'); 39 | } 40 | -------------------------------------------------------------------------------- /api/fenci.php: -------------------------------------------------------------------------------- 1 | GET('text', '', true); 14 | 15 | $result = $poncon->request('http://39.96.43.154:8080/api', 'POST', json_encode(['text' => $text]), 'Content-Type: application/json'); 16 | 17 | $result = json_decode($result, true); 18 | 19 | $words = $result['words']; 20 | 21 | $data = []; 22 | 23 | foreach ($words as $word) { 24 | $data[] = $word['text']; 25 | } 26 | 27 | $poncon->success('分词成功', $data); 28 | -------------------------------------------------------------------------------- /api/get_collect_list.php: -------------------------------------------------------------------------------- 1 | initDb(); 15 | 16 | $data = []; 17 | 18 | $config = $poncon->getConfig(); 19 | $table = $config['table']['collect']; 20 | 21 | $username = $poncon->POST('username', null, true); 22 | $password = $poncon->POST('password', null, true); 23 | $page = $poncon->POST('page', 0, true); 24 | $pageSize = $poncon->POST('pageSize', 36, true); 25 | $offset = $page * $pageSize; 26 | if (!$username || !$password) { 27 | $poncon->error(900, '参数缺失'); 28 | } 29 | 30 | // 登录验证 31 | $poncon->login($conn, $username, $password); 32 | 33 | 34 | 35 | 36 | $sql = "SELECT * FROM `$table` WHERE `username` = '$username' ORDER BY `update_time` DESC LIMIT $pageSize OFFSET $offset;"; 37 | $result = mysqli_query($conn, $sql); 38 | 39 | if (!$result) { 40 | $poncon->error(903, '数据库错误'); 41 | } 42 | 43 | 44 | while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { 45 | $row['tag_list'] = json_decode($row['tag_list'], true); 46 | unset($row['id']); 47 | unset($row['username']); 48 | array_push($data, $row); 49 | } 50 | $poncon->success('获取成功', $data); 51 | -------------------------------------------------------------------------------- /api/get_collect_list_by_tag.php: -------------------------------------------------------------------------------- 1 | initDb(); 13 | 14 | $config = $poncon->getConfig(); 15 | 16 | $username = $poncon->POST('username', null, true); 17 | $password = $poncon->POST('password', null, true); 18 | $page = $poncon->POST('page', 0, true); 19 | $pageSize = $poncon->POST('pageSize', 36, true); 20 | $offset = $page * $pageSize; 21 | $tags = json_decode($poncon->POST('tags', '[]'), true); 22 | 23 | $data = []; 24 | 25 | $config = $poncon->getConfig(); 26 | $table = $config['table']['collect']; 27 | 28 | if (!$username || !$password) { 29 | $poncon->error(900, '参数缺失'); 30 | } 31 | 32 | $poncon->login($conn, $username, $password); 33 | 34 | // 查询包含标签列表的数据 35 | $sql = "SELECT * FROM `$table` WHERE `username` = '$username' AND (`tag_list` LIKE '%\"" . implode("\"%' AND `tag_list` LIKE '%\"", $tags) . "\"%') LIMIT $pageSize OFFSET $offset;"; 36 | 37 | $result = mysqli_query($conn, $sql); 38 | 39 | if (!$result) { 40 | $poncon->error(903, '数据库错误'); 41 | } 42 | 43 | while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { 44 | $row['tag_list'] = json_decode($row['tag_list'], true); 45 | unset($row['id']); 46 | unset($row['username']); 47 | array_push($data, $row); 48 | } 49 | $poncon->success('获取成功', $data); 50 | -------------------------------------------------------------------------------- /api/get_tag_list.php: -------------------------------------------------------------------------------- 1 | initDb(); 13 | 14 | $config = $poncon->getConfig(); 15 | 16 | $username = $poncon->POST('username', null, true); 17 | $password = $poncon->POST('password', null, true); 18 | 19 | if (!$username || !$password) { 20 | $poncon->error(900, '参数缺失'); 21 | } 22 | 23 | $poncon->login($conn, $username, $password); 24 | 25 | $sql = "SELECT `tag_list` FROM `{$config['table']['collect']}` WHERE `username` = '$username' ORDER BY `update_time` DESC;"; 26 | 27 | $result = mysqli_query($conn, $sql); 28 | 29 | if (!$result) { 30 | $poncon->error(903, '数据库错误'); 31 | } 32 | $data = []; 33 | 34 | while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { 35 | $list = json_decode($row['tag_list'], true); 36 | foreach ($list as $tag) { 37 | if (!isset($data[$tag])) { 38 | $data[$tag] = 1; 39 | } else { 40 | $data[$tag]++; 41 | } 42 | } 43 | } 44 | 45 | $poncon->success('获取成功', $data); 46 | -------------------------------------------------------------------------------- /api/get_title.php: -------------------------------------------------------------------------------- 1 | POST('url', null, true); 12 | 13 | $input = json_decode('{ 14 | "options": { 15 | "http": { 16 | "method": "GET", 17 | "header": "Content-type: application/x-www-form-urlencoded\n", 18 | "content": "", 19 | "timeout": 900 20 | }, 21 | "ssl": { 22 | "verify_peer": false, 23 | "verify_peer_name": false 24 | } 25 | }, 26 | "url": "' . $url . '", 27 | "charset": "utf-8" 28 | }', true); 29 | 30 | // $http是个数组,前端需要传入一个JSON 31 | $options = $input['options']; 32 | $charset = $input['charset']; 33 | if ($charset != 'utf-8') { 34 | $header = $options['http']['header']; 35 | $options['http']['header'] = iconv('utf-8', $charset, $header); 36 | } 37 | $context = stream_context_create($options); 38 | $ym = file_get_contents($input['url'], false, $context); 39 | foreach ($http_response_header as $key => $value) { 40 | $item = explode(':', $value); 41 | if (strtolower($item[0]) == 'content-encoding' && strtolower($item[1]) == ' gzip') { 42 | $result = gzdecode($result); 43 | } 44 | } 45 | 46 | if ($charset != 'utf-8') { 47 | $result = iconv($charset, 'utf-8//IGNORE', $result); 48 | } 49 | 50 | 51 | 52 | if (!$url) { 53 | $poncon->error(900, '参数缺失'); 54 | } 55 | 56 | 57 | $title = $poncon->sj($ym, ''); 58 | $title = $poncon->sj($title, '>', null); 59 | // 去除实体 60 | $title = html_entity_decode($title); 61 | 62 | $poncon->success('获取成功', $title); 63 | -------------------------------------------------------------------------------- /api/login.php: -------------------------------------------------------------------------------- 1 | initDb(); 15 | 16 | $username = $poncon->POST('username', null, true); 17 | $password = $poncon->POST('password', null, true); 18 | 19 | if (!$username || !$password) { 20 | $poncon->error(900, '参数缺失'); 21 | } 22 | 23 | // 登录验证 24 | $poncon->login($conn, $username, $password); 25 | 26 | $poncon->success('登录成功', [ 27 | 'username' => $username, 28 | 'password' => $password 29 | ]); 30 | -------------------------------------------------------------------------------- /api/public_share_list.php: -------------------------------------------------------------------------------- 1 | initDb(); 13 | 14 | $config = $poncon->getConfig(); 15 | 16 | $username = $poncon->POST('username', null, true); 17 | $page = $poncon->POST('page', 0, true); 18 | $pageSize = $poncon->POST('pageSize', 36, true); 19 | $offset = $page * $pageSize; 20 | 21 | $table = $config['table']['collect']; 22 | 23 | $data = []; 24 | 25 | $sql = "SELECT * FROM `$table` WHERE `username` = '$username' ORDER BY `update_time` DESC LIMIT $pageSize OFFSET $offset;"; 26 | 27 | 28 | $result = mysqli_query($conn, $sql); 29 | 30 | if (!$result) { 31 | $poncon->error(903, '数据库错误'); 32 | } 33 | 34 | while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { 35 | unset($row['id']); 36 | unset($row['username']); 37 | $data[] = $row; 38 | } 39 | 40 | $poncon->success('获取成功', $data); 41 | -------------------------------------------------------------------------------- /api/register.php: -------------------------------------------------------------------------------- 1 | initDb(); 15 | 16 | $username = $poncon->POST('username'); 17 | $password = $poncon->POST('password'); 18 | 19 | if (!$username || !$password) { 20 | $poncon->error(900, '参数缺失'); 21 | } 22 | 23 | 24 | // 查询用户名是否存在 25 | $sql = "SELECT * FROM `mypages_user` WHERE `username` = '$username'"; 26 | $result = mysqli_query($conn, $sql); 27 | if (!$result) { 28 | $poncon->error(903, '数据库错误'); 29 | } 30 | if (mysqli_num_rows($result) > 0) { 31 | $poncon->error(901, '用户名已存在'); 32 | } 33 | 34 | // 插入用户信息 35 | $sql = "INSERT INTO `mypages_user` (`username`, `password`, `register_time`) VALUES ('$username', '$password', " . time() . ")"; 36 | $result = mysqli_query($conn, $sql); 37 | if (!$result) { 38 | $poncon->error(903, '数据库错误'); 39 | } 40 | 41 | $poncon->success('注册成功', [ 42 | 'username' => $username, 43 | 'password' => $password 44 | ]); 45 | -------------------------------------------------------------------------------- /api/search.php: -------------------------------------------------------------------------------- 1 | initDb(); 14 | 15 | $username = $poncon->POST('username', null, true); 16 | $password = $poncon->POST('password', null, true); 17 | $keyword = $poncon->POST('keyword', null, true); 18 | $page = $poncon->POST('page', 0, true); 19 | $pageSize = $poncon->POST('pageSize', 36, true); 20 | $offset = $page * $pageSize; 21 | 22 | if (!$username || !$password) { 23 | $poncon->error(900, '参数缺失'); 24 | } 25 | 26 | // 登录验证 27 | $poncon->login($conn, $username, $password); 28 | 29 | $data = []; 30 | 31 | $config = $poncon->getConfig(); 32 | $table = $config['table']['collect']; 33 | preg_match('/^#\s*(.*)$/', $keyword, $matches); 34 | if (isset($matches[1])) { 35 | // 搜索标签 36 | $tag = $matches[1]; 37 | $sql = "SELECT * FROM `$table` WHERE `username` = '$username' AND `tag_list` LIKE '%$tag%' ORDER BY `update_time` DESC LIMIT $pageSize OFFSET $offset;"; 38 | $result = mysqli_query($conn, $sql); 39 | while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { 40 | $row['tag_list'] = json_decode($row['tag_list'], true); 41 | unset($row['id']); 42 | unset($row['username']); 43 | array_push($data, $row); 44 | } 45 | $poncon->success('获取成功', $data); 46 | } else { 47 | // 搜索所有 48 | $sql = "SELECT * FROM `$table` WHERE `username` = '$username' AND (`title` LIKE '%$keyword%' OR `url` LIKE '%$keyword%' OR `tag_list` LIKE '%$keyword%' OR `note` LIKE '%$keyword%') ORDER BY `update_time` DESC LIMIT $pageSize OFFSET $offset;"; 49 | $result = mysqli_query($conn, $sql); 50 | while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { 51 | $row['tag_list'] = json_decode($row['tag_list'], true); 52 | unset($row['id']); 53 | unset($row['username']); 54 | array_push($data, $row); 55 | } 56 | $poncon->success('获取成功', $data); 57 | } 58 | -------------------------------------------------------------------------------- /css/index.css: -------------------------------------------------------------------------------- 1 | .oyp-rounded-lg { 2 | border-radius: 1rem; 3 | } 4 | 5 | * { 6 | word-wrap: break-word !important; 7 | word-break: break-all !important; 8 | } 9 | 10 | .oyp-limit-line { 11 | overflow: hidden; 12 | display: -webkit-box; 13 | -webkit-line-clamp: 2; 14 | -webkit-box-orient: vertical; 15 | } 16 | 17 | .collectList .rightTop { 18 | position: absolute; 19 | bottom: 0; 20 | right: 0; 21 | } 22 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iuroc/mypages/e1fa7187460eb41275f69ca84e3da6677224023c/favicon.ico -------------------------------------------------------------------------------- /img/at_phone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iuroc/mypages/e1fa7187460eb41275f69ca84e3da6677224023c/img/at_phone.jpg -------------------------------------------------------------------------------- /img/cat-2722309_640.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iuroc/mypages/e1fa7187460eb41275f69ca84e3da6677224023c/img/cat-2722309_640.png -------------------------------------------------------------------------------- /img/chevron-left-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/insect-6626635_1920.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iuroc/mypages/e1fa7187460eb41275f69ca84e3da6677224023c/img/insect-6626635_1920.jpg -------------------------------------------------------------------------------- /img/plus-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/trash-can-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/uTools_1656678414889.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iuroc/mypages/e1fa7187460eb41275f69ca84e3da6677224023c/img/uTools_1656678414889.png -------------------------------------------------------------------------------- /img/uTools_1656678450484.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iuroc/mypages/e1fa7187460eb41275f69ca84e3da6677224023c/img/uTools_1656678450484.png -------------------------------------------------------------------------------- /img/uTools_1656678465902.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iuroc/mypages/e1fa7187460eb41275f69ca84e3da6677224023c/img/uTools_1656678465902.png -------------------------------------------------------------------------------- /img/uTools_1656678546233.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iuroc/mypages/e1fa7187460eb41275f69ca84e3da6677224023c/img/uTools_1656678546233.png -------------------------------------------------------------------------------- /img/uTools_1656678565171.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iuroc/mypages/e1fa7187460eb41275f69ca84e3da6677224023c/img/uTools_1656678565171.png -------------------------------------------------------------------------------- /img/uTools_1656679524839.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iuroc/mypages/e1fa7187460eb41275f69ca84e3da6677224023c/img/uTools_1656679524839.png -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 主页 - My Pages 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 30 | 31 | 32 | 33 |
34 |
35 | 36 |

37 |
38 | 39 |
40 |
41 |
42 | 43 |
44 |
45 | 51 |
52 |
53 |
54 |
55 | 56 |
57 |
58 | 59 |
60 |
61 |
62 |
63 | 64 |
65 | 66 |
67 |
68 | 143 |
144 | 145 | 146 | 171 | 172 | 173 | 200 | 201 | 202 | 267 | 268 | 269 | 270 | 309 | 310 | 311 | 312 | -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | $('body').removeClass('fade') 3 | new ClipboardJS('.copybtn') 4 | if (!location.hash.split('/')[1]) { 5 | history.replaceState({}, null, Poncon.entryPage) 6 | } 7 | 8 | // 加载路由 9 | router(location.hash) 10 | 11 | // 加载设置 12 | Poncon.setting.newWindowOpen = !Poncon.getStorage('newWindowOpen') 13 | 14 | /** 15 | * 响应路由控制 16 | * @param {string} hash 页面的hash,如#/home/ 17 | */ 18 | function router(hash) { 19 | 20 | if (!Poncon.loginStatus) { 21 | Poncon.login(Poncon.getStorage('username'), Poncon.getStorage('password'), true) 22 | } 23 | 24 | scrollTo(0, 0) 25 | // 获取目标界面标识 26 | hash = hash.split('/') 27 | var target = hash[1] 28 | // 交换界面显示 29 | $('.page-oyp').css('display', 'none') 30 | try { 31 | var page = $('.page-' + target) 32 | page.css('display', 'block') 33 | } catch { 34 | location.href = Poncon.entryPage 35 | } 36 | // 判断目标界面标识 执行对应模块的载入事件 37 | if (target == 'home') { 38 | document.title = '主页 - ' + Poncon.title 39 | $('.webTitle').html(Poncon.title) 40 | if (!Poncon.pageLoad.home) { 41 | Poncon.pageLoad.home = true 42 | Poncon.setting.isBottom = 0 43 | } 44 | Poncon.loadCollectList(0) 45 | } else if (target == 'login') { 46 | $('.webTitle').html('') 47 | if (hash[2] == 'register') { 48 | document.title = '用户注册 - ' + Poncon.title 49 | page.find('.sub-page-login').hide() 50 | page.find('.sub-page-register').show() 51 | } else { 52 | document.title = '用户登录 - ' + Poncon.title 53 | page.find('.sub-page-register').hide() 54 | page.find('.sub-page-login').show() 55 | } 56 | } else if (target == 'user') { 57 | document.title = '个人中心 - ' + Poncon.title 58 | $('.webTitle').html('个人中心') 59 | } else { 60 | location.href = Poncon.entryPage 61 | } 62 | 63 | 64 | // 主页隐藏返回按钮 65 | if (target == 'home' || target == 'login') { 66 | $('.btn-back-oyp').css({ 'display': 'none' }) 67 | } else { 68 | $('.btn-back-oyp').css({ 'display': 'block' }) 69 | } 70 | 71 | if (target == 'home') { 72 | $('.userCenter').css('display', 'inline-block') 73 | } else { 74 | $('.userCenter').css('display', 'none') 75 | } 76 | 77 | } 78 | 79 | window.addEventListener('hashchange', function (event) { 80 | // 监听Hash改变 通过location.hash='xxx'可触发 81 | var hash = new URL(event.newURL).hash 82 | router(hash) 83 | }) 84 | 85 | 86 | 87 | 88 | $(window).scroll(function () { 89 | var scrollTop = $(this).scrollTop() 90 | var scrollHeight = $(document).height() 91 | var windowHeight = $(this).height() 92 | if (location.hash.split('/')[1] == 'home' && scrollTop + windowHeight + 50 > scrollHeight && !Poncon.setting.isBottom) { 93 | Poncon.setting.isBottom = 1 94 | // if (Poncon.data.listType == 'search') { 95 | // Poncon.searchCollect(Poncon.data.keyword, Poncon.data.nowPage + 1) 96 | // } else if (Poncon.data.listType == 'load') { 97 | Poncon.loadCollectList(Poncon.data.nowPage + 1) 98 | // } 99 | } 100 | }) 101 | 102 | $('.modal-searchCollect .modal-body').scroll(function () { 103 | var scrollTop = $(this)[0].scrollTop 104 | var scrollHeight = $(this)[0].scrollHeight 105 | var offsetHeight = $(this)[0].offsetHeight 106 | if (location.hash.split('/')[1] == 'home' && scrollTop + offsetHeight + 50 > scrollHeight && !Poncon.setting.isBottom_search) { 107 | Poncon.setting.isBottom_search = 1 108 | Poncon.searchCollect(Poncon.data.keyword, Poncon.data.nowPage_search + 1) 109 | } 110 | }) 111 | 112 | $('.modal-tagList .modal-body').scroll(function () { 113 | var modal = $('.modal-tagList') 114 | var scrollTop = $(this)[0].scrollTop 115 | var scrollHeight = $(this)[0].scrollHeight 116 | var offsetHeight = $(this)[0].offsetHeight 117 | if (location.hash.split('/')[1] == 'home' && scrollTop + offsetHeight + 50 > scrollHeight && !Poncon.setting.isBottom_byTag && modal.find('.tagList').css('display') == 'none') { 118 | Poncon.setting.isBottom_byTag = 1 119 | Poncon.loadCollectListByTag(Poncon.data.tagListObjSelected, Poncon.data.nowPage_byTag + 1) 120 | } 121 | }) 122 | }) -------------------------------------------------------------------------------- /js/poncon.js: -------------------------------------------------------------------------------- 1 | const Poncon = { 2 | title: 'My Pages', // 网页总标题 3 | baseUrl: '', // 项目安装目录,不以/结尾 4 | storageKey: 'my_pages', // 本地存储键名 5 | entryPage: '#/home', // 主页,路由出错时加载 6 | loginStatus: 0, // 登录状态 0:未登录 1: 已登录 7 | tagList: [], // 标签列表 8 | pageLoad: {}, // 页面加载状态 9 | setting: {}, 10 | data: { // 网页数据 11 | listType: 'load', // 列表类型 load 正常加载 search 搜索 12 | tagListObjSelected: {}, // 当前选中的标签 13 | tagListObj: {}, // 标签列表 14 | tagListObjTemp: {}, // 筛选后的标签列表 15 | }, 16 | /** 17 | * 用户登录 18 | * @param {string} username 用户名 19 | * @param {string} password 密文密码 20 | * @returns {boolean} 是否验证成功 21 | */ 22 | login(username, password, ifLoad) { 23 | if (!username || !password) { 24 | this.notLogin() 25 | return false 26 | } 27 | var success 28 | var target = this 29 | $.ajax({ 30 | method: 'post', 31 | url: this.baseUrl + 'api/login.php', 32 | data: { 33 | username: username, 34 | password: password 35 | }, 36 | contentType: 'application/x-www-form-urlencoded', 37 | dataType: 'json', 38 | success: function (data) { 39 | if (data.code == 200) { 40 | success = true 41 | target.setStorage('username', username) 42 | target.setStorage('password', password) 43 | target.loginStatus = 1 44 | if (ifLoad) { 45 | location.href = target.entryPage 46 | } 47 | return true 48 | } 49 | if (!ifLoad) { 50 | alert(data.msg) 51 | } 52 | target.notLogin() 53 | success = false 54 | return false 55 | }, 56 | async: false 57 | }) 58 | return success 59 | }, 60 | /** 61 | * 获取存储值 62 | * @param {string} key 键名 63 | * @returns {any} 返回值 64 | */ 65 | getStorage(key) { 66 | var data = localStorage[this.storageKey] 67 | try { 68 | data = JSON.parse(data) 69 | return data[key] 70 | } catch { 71 | return null 72 | } 73 | }, 74 | /** 75 | * 设置存储值 76 | * @param {string} key 键名 77 | * @param {any} value 值 78 | */ 79 | setStorage(key, value) { 80 | var data = localStorage[this.storageKey] 81 | data = data ? data : '{}' 82 | data = JSON.parse(data) 83 | data[key] = value 84 | localStorage[this.storageKey] = JSON.stringify(data) 85 | }, 86 | /** 87 | * 未登录状态 88 | */ 89 | notLogin() { 90 | if (location.hash.split('/')[1] != 'login') { 91 | location.hash = '/login/register' 92 | } 93 | }, 94 | /** 95 | * 点击登录 96 | */ 97 | clickLogin() { 98 | var page = $('.page-login .sub-page-login') 99 | var username = page.find('.input-username').val() 100 | var password = page.find('.input-password').val() 101 | if (!username.match(/^\w{4,20}$/) || !password.match(/^\w{8,20}$/)) { 102 | alert('请输入正确的格式') 103 | return 104 | } 105 | 106 | if (this.login(username, md5(password))) { 107 | location.href = this.entryPage 108 | } 109 | }, 110 | /** 111 | * 点击注册 112 | */ 113 | clickRegister() { 114 | var page = $('.page-login .sub-page-register') 115 | var username = page.find('.input-username').val() 116 | var password = page.find('.input-password').val() 117 | var password2 = page.find('.input-password2').val() 118 | if (password != password2) { 119 | alert('两次输入的密码不一致') 120 | return 121 | } else if (!username.match(/^\w{4,20}$/) || !password.match(/^\w{8,20}$/)) { 122 | alert('请输入正确的格式') 123 | return 124 | } 125 | this.register(username, password) 126 | }, 127 | /** 128 | * 用户注册 129 | * @param {string} username 用户名 130 | * @param {string} password 明文密码 131 | */ 132 | register(username, password) { 133 | var target = this 134 | password = md5(password) 135 | $.ajax({ 136 | method: 'post', 137 | url: this.baseUrl + 'api/register.php', 138 | data: { 139 | username: username, 140 | password: password 141 | }, 142 | contentType: 'application/x-www-form-urlencoded', 143 | dataType: 'json', 144 | success: function (data) { 145 | if (data.code == 200) { 146 | target.setStorage('username', username) 147 | target.setStorage('password', password) 148 | target.loginStatus = 1 149 | location.href = target.entryPage 150 | return 151 | } 152 | 153 | alert(data.msg) 154 | 155 | } 156 | }) 157 | }, 158 | /** 159 | * 显示模态框 160 | * @param {string} modalName 模态框名称 161 | */ 162 | showModal(modalName, mode, mode2) { 163 | if (modalName == 'addCollect') { 164 | var modal = $('.modal-addCollect') 165 | if (this.editMode == 'update' && mode == 'add') { 166 | this.cleanInput() 167 | } 168 | $('.modal-addCollect').unbind() 169 | if (mode2 == 'search') { 170 | $('.modal-addCollect').on('hidden.bs.modal', function () { 171 | var modal = $('.modal-searchCollect') 172 | modal.modal('show') 173 | var input = modal.find('.input-keyword') 174 | input.focus() 175 | }) 176 | } else if (mode2 == 'byTag') { 177 | $('.modal-addCollect').on('hidden.bs.modal', function () { 178 | var modal = $('.modal-tagList') 179 | modal.modal('show') 180 | }) 181 | } 182 | 183 | // 加载标签列表 184 | Poncon.loadTagList('edit') 185 | var tags = Poncon.data.tagListObj 186 | var tagsHtml = this.makeTags(tags, 'edit') 187 | modal.find('.allTagList').html(tagsHtml) 188 | // 新增收藏 189 | $('.modal-addCollect').modal('show') 190 | 191 | 192 | modal.find('.input-url').removeAttr('readonly') 193 | modal.find('.getHost').removeAttr('disabled') 194 | 195 | this.editMode = mode // 编辑模式 add: 新增 update: 更新 196 | if (mode == 'add') { 197 | $('.modal-addCollect .input-url').focus() 198 | modal.find('.addCollect').html('添加收藏') 199 | modal.find('.addCollect').html('添加收藏') 200 | } else { 201 | modal.find('.addCollect').html('确定编辑') 202 | modal.find('.modal-title').html('编辑收藏') 203 | } 204 | } else if (modalName == 'searchCollect') { 205 | // 搜索收藏 206 | var modal = $('.modal-searchCollect') 207 | modal.modal('show') 208 | var input = modal.find('.input-keyword') 209 | input.focus() 210 | if (!input.val() && modal.find('.searchList').html().match(/^\s*$/)) { 211 | this.clickSearch() 212 | } 213 | } else if (modalName == 'tagList') { 214 | var modal = $('.modal-tagList') 215 | modal.modal('show') 216 | this.loadTagList() 217 | this.backToTagList() 218 | modal.find('.input-keyword').val('').focus() 219 | } else if (modalName == 'userSetting') { 220 | var modal = $('.modal-userSetting') 221 | modal.modal('show') 222 | this.loadSetting() 223 | } 224 | }, 225 | /** 226 | * 加载设置项 227 | */ 228 | loadSetting() { 229 | var modal = $('.modal-userSetting') 230 | var target = this 231 | $('#customSwitch_newWindow')[0].checked = this.setting.newWindowOpen 232 | $('#customSwitch_newWindow').unbind().on('change', function () { 233 | var newWindowOpen = $('#customSwitch_newWindow')[0].checked 234 | target.setStorage('newWindowOpen', !newWindowOpen) 235 | target.setting.newWindowOpen = newWindowOpen 236 | }) 237 | var shareUrl = window.location.origin + window.location.pathname.replace('index.html', '') + 'share/?u=' + this.getStorage('username') 238 | modal.find('.input-shareUrl').val(shareUrl) 239 | }, 240 | /** 241 | * 加载标签列表 242 | */ 243 | loadTagList(mode) { 244 | if (mode != 'edit') { 245 | var modal = $('.modal-tagList') 246 | var target = this 247 | this.data.tagListObj = {} 248 | this.data.tagListObjTemp = {} 249 | this.data.tagListObjSelected = {} 250 | } 251 | var target = this 252 | $.ajax({ 253 | method: 'post', 254 | url: this.baseUrl + 'api/get_tag_list.php', 255 | data: { 256 | username: this.getStorage('username'), 257 | password: this.getStorage('password') 258 | }, 259 | contentType: 'application/x-www-form-urlencoded', 260 | dataType: 'json', 261 | success: function (data) { 262 | if (data.code == 200) { 263 | if (mode == 'edit') { 264 | target.data.tagListObj = data.data 265 | return 266 | } 267 | if (data.data.length == 0) { 268 | modal.find('.tagList').html(`暂无标签 269 |
270 | 当前暂无标签 271 |
`) 272 | return 273 | } 274 | target.loadTagListHtml(data.data) 275 | modal.find('.allUnSelectTag').hide() 276 | modal.find('.allSelectTag').show() 277 | modal.find('.submitSelect').attr('disabled', 'disabled') 278 | return 279 | } 280 | alert(data.msg) 281 | }, 282 | async: false 283 | }) 284 | return this.data.tagListObj 285 | }, 286 | loadTagListHtml(obj) { 287 | this.data.tagListObj = obj 288 | this.data.tagListObjTemp = obj 289 | var modal = $('.modal-tagList') 290 | var list = this.sortByNum(obj) 291 | var html = this.makeTags(list, 'all') 292 | modal.find('.tagList').html(html) 293 | 294 | }, 295 | /** 296 | * 对象值排序 297 | * @param {object} obj 对象 298 | * @returns {object} 排序后的对象 299 | */ 300 | sortByNum(obj) { 301 | var list = {} 302 | Object.keys(obj).sort(function (a, b) { 303 | return obj[b] - obj[a] 304 | }).forEach((key) => { 305 | list[key] = obj[key] 306 | }) 307 | return list 308 | }, 309 | /** 310 | * 对象按键名的拼音排序 311 | * @param {object} obj 对象 312 | * @returns {object} 排序后的对象 313 | */ 314 | sortByKey(obj) { 315 | var list = {} 316 | Object.keys(obj).sort(function (a, b) { 317 | return a.localeCompare(b) 318 | }).forEach((key) => { 319 | list[key] = obj[key] 320 | }) 321 | return list 322 | }, 323 | /** 324 | * 新增标签 325 | * @param {string} tagName 标签名 如果为空则读取input 326 | */ 327 | addTag(tagName) { 328 | var target = this 329 | var modal = $('.modal-addCollect') 330 | var tagName = tagName ? tagName : modal.find('.input-tagName').val() 331 | tagName = $.trim(tagName) 332 | if (!tagName) { 333 | return 334 | } 335 | this.tagList.push(tagName) 336 | this.tagList = this.unique(this.tagList) 337 | modal.find('.tagList').html(this.makeTags(this.tagList)) 338 | this.giveClick('.tagList') 339 | modal.find('.input-tagName').val('').focus() 340 | }, 341 | /** 342 | * 获取URL中的主域部分 343 | */ 344 | getHostFromUrl() { 345 | var target = this 346 | var modal = $('.modal-addCollect') 347 | var ele = modal.find('.input-url') 348 | try { 349 | var url = new URL(ele.val()) 350 | } catch { 351 | alert('网址格式错误') 352 | return 353 | } 354 | ele.val(url.origin) 355 | }, 356 | /** 357 | * 获取网页标题 358 | */ 359 | getWebTitle() { 360 | var target = this 361 | var modal = $('.modal-addCollect') 362 | var ele = modal.find('.input-url') 363 | try { 364 | var temp = new URL(ele.val()) 365 | } catch { 366 | alert('网址格式错误') 367 | return 368 | } 369 | var url = ele.val() 370 | modal.find('button.getWebTitle').html('获取中').attr('disabled', 'disabled') 371 | $.ajax({ 372 | method: 'post', 373 | url: this.baseUrl + 'api/get_title.php', 374 | data: { 375 | url: url 376 | }, 377 | contentType: 'application/x-www-form-urlencoded', 378 | dataType: 'json', 379 | success: function (data) { 380 | modal.find('button.getWebTitle').html('获取').removeAttr('disabled') 381 | modal.find('.input-title').val(data.data) 382 | data.data ? target.fenci() : null 383 | } 384 | }) 385 | }, 386 | /** 387 | * 为某个容器中的组件绑定单击事件 388 | * @param {string} select 选择器名称 389 | */ 390 | giveClick(select) { 391 | var target = this 392 | if (select == '.tagList') { 393 | var modal = $('.modal-addCollect') 394 | modal.find('.tagList div').unbind().click(function () { 395 | var tagName = $(this).text() 396 | target.removeArray(target.tagList, tagName) 397 | modal.find('.tagList').html(target.makeTags(target.tagList)) 398 | target.giveClick('.tagList') 399 | modal.find('.input-tagName').focus() 400 | }) 401 | } 402 | }, 403 | // 新增收藏 404 | addCollect() { 405 | var target = this 406 | var modal = $('.modal-addCollect') 407 | var url = $.trim(modal.find('.input-url').val()) 408 | var title = $.trim(modal.find('.input-title').val()) 409 | var note = $.trim(modal.find('.input-note').val()) 410 | try { 411 | var temp = new URL(url) 412 | } catch { 413 | alert('网址格式错误') 414 | return 415 | } 416 | if (!title) { 417 | alert('请输入网页标题') 418 | return 419 | } 420 | var tags = JSON.stringify(this.tagList) 421 | var _private = $('#customSwitch_private')[0].checked ? 1 : 0 422 | $.ajax({ 423 | method: 'post', 424 | url: this.baseUrl + 'api/add_collect.php', 425 | data: { 426 | username: this.getStorage('username'), 427 | password: this.getStorage('password'), 428 | url: url, 429 | title: title, 430 | tags: tags, 431 | private: _private, 432 | mode: this.editMode, 433 | note: note 434 | }, 435 | contentType: 'application/x-www-form-urlencoded', 436 | dataType: 'json', 437 | success: function (data) { 438 | if (data.code == 200) { 439 | $('.modal-addCollect').modal('hide') 440 | if (target.editMode == 'add') { 441 | // 新增收藏 更新列表 清空表单 442 | target.loadCollectList(0) 443 | target.cleanInput() 444 | } else if (target.editMode == 'update') { 445 | // 更新收藏 更新当前列表项 清空表单 446 | target.updateEditingNode() 447 | target.cleanInput() 448 | } 449 | return 450 | } 451 | alert(data.msg) 452 | } 453 | }) 454 | }, 455 | /** 456 | * 更新正在编辑的收藏项 457 | */ 458 | updateEditingNode() { 459 | var node = $(this.editingNode) 460 | var modal = $('.modal-addCollect') 461 | node.find('.title').text(modal.find('.input-title').val()) 462 | node.find('.note').text(modal.find('.input-note').val()) 463 | node.find('.url').text(modal.find('.input-url').val()) 464 | node.find('.card-body').attr('data-private', $('#customSwitch_private')[0].checked ? 1 : 0) 465 | var tagsHtml = '' 466 | this.tagList.forEach((tag) => { 467 | tagsHtml += `
${tag}
` 468 | }) 469 | node.find('.tags').html(tagsHtml).attr('data-tags', encodeURIComponent(JSON.stringify(this.tagList))) 470 | node.find('.update_time').html(this.parseDate(new Date().getTime())) 471 | }, 472 | /** 473 | * 清空输入框 474 | */ 475 | cleanInput() { 476 | var modal = $('.modal-addCollect') 477 | modal.find('.input-url').val('') 478 | modal.find('.input-title').val('') 479 | modal.find('.input-tagName').val('') 480 | this.tagList = [] 481 | modal.find('.tagList').html('') 482 | modal.find('.input-note').val('') 483 | }, 484 | /** 485 | * 删除数组中某个值 486 | * @param {array} array 待操作数组 487 | * @param {any} need 需要删除的值 488 | */ 489 | removeArray(array, need) { 490 | array.map((value, index) => { 491 | if (value == need) { 492 | array.splice(index, 1) 493 | } 494 | }) 495 | return array 496 | }, 497 | /** 498 | * 生成标签列表 499 | * @param {array} tagList 数据列表 500 | * @param {string} mode all: 所有标签 501 | * @returns {string} 502 | */ 503 | makeTags(tagList, mode) { 504 | var html = '' 505 | if (mode == 'all') { 506 | for (var tag in tagList) { 507 | html += `
${tag} ${tagList[tag]}
` 508 | } 509 | return html 510 | } else if (mode == 'edit') { 511 | for (var tag in tagList) { 512 | html += `
${tag} ${tagList[tag]}
` 513 | } 514 | return html 515 | } 516 | tagList.forEach(tag => { 517 | html += `
${tag}
` 518 | }) 519 | return html 520 | }, 521 | /** 522 | * 新增收藏或编辑收藏时标签的单击事件 523 | * @param {event} event 事件对象 524 | */ 525 | addTagListChecked(event) { 526 | var ele = $(event.target) 527 | if (!ele.hasClass('btn')) { 528 | ele = ele.parent() 529 | } 530 | var tagName = ele.find('.tag').text() 531 | var modal = $('.modal-addCollect') 532 | tagName = $.trim(tagName) 533 | if (!tagName) { 534 | return 535 | } 536 | this.tagList.push(tagName) 537 | this.tagList = this.unique(this.tagList) 538 | modal.find('.tagList').html(this.makeTags(this.tagList)) 539 | this.giveClick('.tagList') 540 | modal.find('.input-tagName').val('').focus() 541 | }, 542 | /** 543 | * 新增或编辑收藏时标签的单击事件 544 | * @param {event} event 事件对象 545 | */ 546 | tagListChecked(event) { 547 | var ele = $(event.target) 548 | if (!ele.hasClass('btn')) { 549 | ele = ele.parent() 550 | } 551 | var tagName = ele.find('.tag').text() 552 | if (ele.hasClass('btn-light')) { 553 | ele.removeClass('btn-light') 554 | ele.addClass('btn-primary') 555 | ele.removeClass('bg-warning') 556 | this.data.tagListObjSelected[tagName] = this.data.tagListObj[tagName] 557 | } else { 558 | ele.removeClass('btn-primary') 559 | ele.addClass('btn-light') 560 | this.indexTags() 561 | delete this.data.tagListObjSelected[tagName] 562 | } 563 | if (Object.keys(this.data.tagListObjSelected).length 564 | == Object.keys(this.data.tagListObjTemp).length 565 | && Object.keys(this.data.tagListObjTemp).length > 0) { 566 | var modal = $('.modal-tagList') 567 | modal.find('.allUnSelectTag').show() 568 | modal.find('.allSelectTag').hide() 569 | } else { 570 | var modal = $('.modal-tagList') 571 | modal.find('.allUnSelectTag').hide() 572 | modal.find('.allSelectTag').show() 573 | } 574 | this.disabledButton() 575 | }, 576 | /** 577 | * 中文分词 578 | */ 579 | fenci() { 580 | var target = this 581 | var modal = $('.modal-addCollect') 582 | var title = modal.find('.input-title').val() 583 | var note = modal.find('.input-note').val() 584 | $.ajax({ 585 | url: this.baseUrl + 'api/fenci.php', 586 | type: 'GET', 587 | data: { 588 | text: title + ',' + note 589 | }, 590 | success: (data) => { 591 | if (data.code == 200) { 592 | var tags = data.data 593 | tags.forEach(tag => { 594 | tag.length > 1 ? target.addTag(tag) : null 595 | }) 596 | } 597 | } 598 | }) 599 | }, 600 | /** 601 | * 筛选标签 602 | */ 603 | screeningTag() { 604 | var modal = $('.modal-tagList') 605 | var keyword = $.trim(modal.find('.input-keyword').val()) 606 | this.data.tagListObjSelected = {} 607 | this.data.tagListObjTemp = {} 608 | for (var tag in this.data.tagListObj) { 609 | if (tag.indexOf(keyword) != -1) { 610 | this.data.tagListObjTemp[tag] = this.data.tagListObj[tag] 611 | } 612 | } 613 | var list = this.data.tagListObjTemp 614 | list = this.sortByKey(list) 615 | modal.find('.tagList').html(this.makeTags(list, 'all')) 616 | if (keyword) { 617 | this.allSelectTag() 618 | } 619 | this.backToTagList() 620 | }, 621 | /** 622 | * 索引标签 623 | */ 624 | indexTags() { 625 | var modal = $('.modal-tagList') 626 | var keyword = $.trim(modal.find('.input-keyword').val()) 627 | var eles = modal.find('.tagList .btn') 628 | for (var i = 0; i < eles.length; i++) { 629 | if (eles[i].innerText.search(keyword) != -1 && keyword && !$(eles[i]).hasClass('btn-primary')) { 630 | $(eles[i]).addClass('bg-warning') 631 | } else { 632 | $(eles[i]).removeClass('bg-warning') 633 | } 634 | } 635 | }, 636 | 637 | 638 | /** 639 | * 全选标签 640 | */ 641 | allSelectTag() { 642 | var modal = $('.modal-tagList') 643 | modal.find('.allUnSelectTag').show() 644 | modal.find('.allSelectTag').hide() 645 | var tagList = $('.tagList') 646 | tagList.find('.btn').removeClass('btn-light') 647 | tagList.find('.btn').addClass('btn-primary') 648 | this.data.tagListObjSelected = JSON.parse(JSON.stringify(this.data.tagListObjTemp)) 649 | this.disabledButton() 650 | }, 651 | /** 652 | * 取消全选标签 653 | */ 654 | allUnSelectTag() { 655 | var modal = $('.modal-tagList') 656 | modal.find('.allUnSelectTag').hide() 657 | modal.find('.allSelectTag').show() 658 | var tagList = $('.tagList') 659 | tagList.find('.btn').removeClass('btn-primary') 660 | tagList.find('.btn').addClass('btn-light') 661 | this.data.tagListObjSelected = {} 662 | this.disabledButton() 663 | }, 664 | /** 665 | * 当全不选时禁用确定按钮 666 | */ 667 | disabledButton() { 668 | if (Object.keys(this.data.tagListObjSelected).length == 0) { 669 | $('.modal-tagList').find('.submitSelect').attr('disabled', 'disabled') 670 | } else { 671 | $('.modal-tagList').find('.submitSelect').removeAttr('disabled') 672 | } 673 | }, 674 | /** 675 | * 根据标签获取收藏列表 676 | * @param {object} tags 标签名 677 | */ 678 | loadCollectListByTag(tags, page) { 679 | if (isNaN(page)) { 680 | return 681 | } 682 | var modal = $('.modal-tagList') 683 | if (page == 0) { 684 | modal.find('.tagList').hide() 685 | modal.find('.collectList').html('').show() 686 | modal.find('.allSelectTag, .allUnSelectTag').hide() 687 | modal.find('.submitSelect').hide() 688 | modal.find('.backToList').show() 689 | } 690 | 691 | tags = JSON.stringify(Object.keys(tags)) 692 | var target = this 693 | $.ajax({ 694 | method: 'post', 695 | url: this.baseUrl + 'api/get_collect_list_by_tag.php', 696 | data: { 697 | tags: tags, 698 | username: this.getStorage('username'), 699 | password: this.getStorage('password'), 700 | page: page, 701 | pageSize: 36 702 | }, 703 | contentType: 'application/x-www-form-urlencoded', 704 | dataType: 'json', 705 | success: function (data) { 706 | if (data.code == 200) { 707 | var collectList = data.data 708 | if (collectList.length == 0) { 709 | target.setting.isBottom_byTag = 1 710 | return 711 | } 712 | var html = target.makeList(collectList, 'byTag') 713 | modal.find('.collectList').append(html) 714 | new ClipboardJS('.copybtn', { 715 | container: modal[0] 716 | }) 717 | target.data.nowPage_byTag = page 718 | target.setting.isBottom_byTag = 0 719 | return 720 | } 721 | target.setting.isBottom_byTag = 1 722 | alert(data.msg) 723 | } 724 | }) 725 | }, 726 | /** 727 | * 从筛选后的收藏列表返回标签列表 728 | */ 729 | backToTagList() { 730 | var modal = $('.modal-tagList') 731 | modal.find('.tagList').show() 732 | modal.find('.collectList').hide() 733 | modal.find('.submitSelect').show() 734 | modal.find('.backToList').hide() 735 | if (Object.keys(this.data.tagListObjSelected).length 736 | == Object.keys(this.data.tagListObjTemp).length 737 | && Object.keys(this.data.tagListObjTemp).length > 0) { 738 | var modal = $('.modal-tagList') 739 | modal.find('.allUnSelectTag').show() 740 | modal.find('.allSelectTag').hide() 741 | } else { 742 | var modal = $('.modal-tagList') 743 | modal.find('.allUnSelectTag').hide() 744 | modal.find('.allSelectTag').show() 745 | } 746 | }, 747 | /** 748 | * 749 | * @param {string} eventName 回车触发的事件名称 750 | */ 751 | inputKeyup(event, eventName) { 752 | if (event.keyCode == 13) { 753 | this[eventName]() 754 | } 755 | }, 756 | /** 757 | * 返回随机颜色类名 758 | * @returns 759 | */ 760 | randomColor() { 761 | var colorList = ['danger', 'secondary', 'primary', 'dark', 'info', 'success'] 762 | var index = Math.floor(Math.random() * colorList.length) 763 | return colorList[index] 764 | }, 765 | /** 766 | * 删除数组中重复元素 767 | * @param {array} arr 待操作数组 768 | * @returns {array} 769 | */ 770 | unique(arr) { 771 | for (var i = 0; i < arr.length; i++) { 772 | for (var j = i + 1; j < arr.length; j++) { 773 | if (arr[i] == arr[j]) { 774 | arr.splice(j, 1) 775 | j-- 776 | } 777 | } 778 | } 779 | return arr 780 | }, 781 | /** 782 | * 主页加载收藏列表 783 | * @param {number} page 页码 从0开始 784 | */ 785 | loadCollectList(page) { 786 | var target = this 787 | var _page = $('.page-home') 788 | if (page == 0) { 789 | _page.find('.collectList').html('') 790 | } 791 | $.ajax({ 792 | method: 'post', 793 | url: this.baseUrl + 'api/get_collect_list.php', 794 | data: { 795 | username: this.getStorage('username'), 796 | password: this.getStorage('password'), 797 | page: page, 798 | pageSize: 36 799 | }, 800 | contentType: 'application/x-www-form-urlencoded', 801 | dataType: 'json', 802 | success: function (data) { 803 | if (data.code == 200) { 804 | if (data.data.length == 0) { 805 | target.setting.isBottom = 1 806 | return 807 | } 808 | 809 | var html = target.makeList(data.data) 810 | _page.find('.collectList').append(html) 811 | new ClipboardJS('.copybtn') 812 | target.data.nowPage = page 813 | target.setting.isBottom = 0 814 | return 815 | } 816 | target.setting.isBottom = 1 817 | // alert(data.msg) 818 | } 819 | }) 820 | }, 821 | /** 822 | * 将数据列表转为HTML代码 823 | * @param {array} dataList 数据列表 824 | */ 825 | makeList(dataList, mode) { 826 | var html = '' 827 | _class = (mode == 'search') || (mode == 'byTag') ? 'mb-4' : 'col-xl-4 col-lg-6 mb-4' 828 | dataList.forEach((item) => { 829 | var tagsHtml = '' 830 | item.tag_list.forEach((tag) => { 831 | tagsHtml += `
${tag}
` 832 | }) 833 | html += `
834 |
835 |
836 |
${item.title}
837 | ${item.url} 838 |
${item.note}
839 |
${tagsHtml}
840 |
841 | 删除 842 | 复制 843 | 编辑 844 | ${this.parseDate(parseInt(item.update_time) * 1000)} 845 |
846 |
847 |
848 |
` 849 | }) 850 | return html 851 | }, 852 | /** 853 | * 编辑收藏记录 854 | * @param {event} event 事件对象 855 | */ 856 | listItemEdit(event, mode) { 857 | var ele = $(event.target).parents('.card-body') 858 | var title = ele.find('.title').text() 859 | var url = ele.find('.url').text() 860 | var note = ele.find('.note').text() 861 | var tags = JSON.parse(decodeURIComponent(ele.find('.tags').attr('data-tags'))) 862 | this.tagList = tags 863 | this.tagList = this.unique(this.tagList) 864 | var _private = ele.attr('data-private') 865 | this.showModal('addCollect', 'update', mode) 866 | if (mode == 'search') { 867 | $('.modal-searchCollect').modal('hide') 868 | } else if (mode == 'byTag') { 869 | $('.modal-tagList').modal('hide') 870 | } 871 | var modal = $('.modal-addCollect') 872 | modal.find('.tagList').html(this.makeTags(this.tagList)) 873 | this.giveClick('.tagList') 874 | modal.find('.input-title').val(title) 875 | modal.find('.input-note').val(note) 876 | modal.find('.input-url').val(url).attr('readonly', 'readonly') 877 | modal.find('.getHost').attr('disabled', 'disabled') 878 | modal.find('#customSwitch_private')[0].checked = _private == '1' ? true : false 879 | this.editMode = 'update' 880 | this.editingNode = $(event.target).parents('.mb-4') // 正在编辑的节点 881 | }, 882 | /** 883 | * 删除收藏 884 | */ 885 | listItemDelete(event, url, time) { 886 | if (!confirm('确定删除吗?')) { 887 | return 888 | } 889 | $.ajax({ 890 | method: 'post', 891 | url: this.baseUrl + 'api/delete_collect.php', 892 | data: { 893 | username: this.getStorage('username'), 894 | password: this.getStorage('password'), 895 | url: url, 896 | time: time 897 | }, 898 | contentType: 'application/x-www-form-urlencoded', 899 | dataType: 'json', 900 | success: function (data) { 901 | if (data.code == 200) { 902 | $(event.target).parent().parent().parent().parent().remove() 903 | return 904 | } 905 | alert(data.msg) 906 | } 907 | }) 908 | }, 909 | /** 910 | * 跳转网址 911 | * @param {url} url 网址 912 | */ 913 | goHref(url) { 914 | if (this.setting.newWindowOpen) { 915 | window.open(url) 916 | return 917 | } 918 | location.href = url 919 | }, 920 | /** 921 | * 格式化时间戳 922 | * @param {number} date 时间戳 923 | * @returns {string} 924 | */ 925 | parseDate(date) { 926 | function two(t) { 927 | if (t < 10) { 928 | return '0' + t 929 | } 930 | return t 931 | } 932 | date = new Date(date) 933 | return two(date.getFullYear()) + '-' + two(date.getMonth() + 1) + '-' + two(date.getDate()) + ' ' + two(date.getHours()) + ':' + two(date.getMinutes()) 934 | }, 935 | /** 936 | * 单击搜索事件 937 | */ 938 | clickSearch() { 939 | var modal = $('.modal-searchCollect') 940 | var keyword = modal.find('.input-keyword').val() 941 | this.searchCollect(keyword, 0) 942 | }, 943 | /** 944 | * 搜索收藏列表 945 | * @param {string} keyword 搜索关键词 946 | * @param {number} page 页码 从0开始 947 | */ 948 | searchCollect(keyword, page) { 949 | var target = this 950 | var modal = $('.modal-searchCollect') 951 | 952 | if (page == 0) { 953 | modal.find('.searchList').html('') 954 | } 955 | $.ajax({ 956 | method: 'post', 957 | url: this.baseUrl + 'api/search.php', 958 | data: { 959 | username: this.getStorage('username'), 960 | password: this.getStorage('password'), 961 | keyword: keyword, 962 | page: page, 963 | pageSize: 36 964 | }, 965 | contentType: 'application/x-www-form-urlencoded', 966 | dataType: 'json', 967 | success: function (data) { 968 | if (data.code == 200) { 969 | if (data.data.length == 0) { 970 | target.setting.isBottom_search = 1 971 | return 972 | } 973 | var html = target.makeList(data.data, 'search') 974 | modal.find('.searchList').append(html) 975 | new ClipboardJS('.copybtn', { 976 | container: modal[0] 977 | }) 978 | target.data.nowPage_search = page 979 | target.data.keyword = keyword 980 | target.setting.isBottom_search = 0 981 | return 982 | } 983 | this.setting.isBottom_search = 1 984 | alert(data.msg) 985 | } 986 | }) 987 | }, 988 | /** 989 | * 退出登录 990 | */ 991 | logout() { 992 | if (confirm('确定退出吗?')) { 993 | localStorage.removeItem(this.storageKey) 994 | location.reload() 995 | } 996 | 997 | } 998 | } -------------------------------------------------------------------------------- /js/poncon.min.js: -------------------------------------------------------------------------------- 1 | const Poncon={title:"My Pages",baseUrl:"",storageKey:"my_pages",entryPage:"#/home",loginStatus:0,tagList:[],pageLoad:{},setting:{},data:{listType:"load",tagListObjSelected:{},tagListObj:{},tagListObjTemp:{}},login(a,e,i){if(!a||!e)return this.notLogin(),!1;var s,d=this;return $.ajax({method:"post",url:this.baseUrl+"api/login.php",data:{username:a,password:e},contentType:"application/x-www-form-urlencoded",dataType:"json",success:function(t){return 200==t.code?(s=!0,d.setStorage("username",a),d.setStorage("password",e),d.loginStatus=1,i&&(location.href=d.entryPage),!0):(i||alert(t.msg),d.notLogin(),s=!1)},async:!1}),s},getStorage(t){var a=localStorage[this.storageKey];try{return JSON.parse(a)[t]}catch{return null}},setStorage(t,a){var e=(e=localStorage[this.storageKey])||"{}";(e=JSON.parse(e))[t]=a,localStorage[this.storageKey]=JSON.stringify(e)},notLogin(){"login"!=location.hash.split("/")[1]&&(location.hash="/login/register")},clickLogin(){var t=$(".page-login .sub-page-login"),a=t.find(".input-username").val(),t=t.find(".input-password").val();a.match(/^\w{4,20}$/)&&t.match(/^\w{8,20}$/)?this.login(a,md5(t))&&(location.href=this.entryPage):alert("请输入正确的格式")},clickRegister(){var t=$(".page-login .sub-page-register"),a=t.find(".input-username").val(),e=t.find(".input-password").val();e!=t.find(".input-password2").val()?alert("两次输入的密码不一致"):a.match(/^\w{4,20}$/)&&e.match(/^\w{8,20}$/)?this.register(a,e):alert("请输入正确的格式")},register(a,e){var i=this;e=md5(e),$.ajax({method:"post",url:this.baseUrl+"api/register.php",data:{username:a,password:e},contentType:"application/x-www-form-urlencoded",dataType:"json",success:function(t){if(200==t.code)return i.setStorage("username",a),i.setStorage("password",e),i.loginStatus=1,void(location.href=i.entryPage);alert(t.msg)}})},showModal(t,a,e){var i;"addCollect"==t?(i=$(".modal-addCollect"),"update"==this.editMode&&"add"==a&&this.cleanInput(),$(".modal-addCollect").unbind(),"search"==e?$(".modal-addCollect").on("hidden.bs.modal",function(){var t=$(".modal-searchCollect");t.modal("show"),t.find(".input-keyword").focus()}):"byTag"==e&&$(".modal-addCollect").on("hidden.bs.modal",function(){$(".modal-tagList").modal("show")}),Poncon.loadTagList("edit"),e=Poncon.data.tagListObj,e=this.makeTags(e,"edit"),i.find(".allTagList").html(e),$(".modal-addCollect").modal("show"),i.find(".input-url").removeAttr("readonly"),i.find(".getHost").removeAttr("disabled"),"add"==(this.editMode=a)?($(".modal-addCollect .input-url").focus(),i.find(".addCollect").html("添加收藏"),i.find(".addCollect").html("添加收藏")):(i.find(".addCollect").html("确定编辑"),i.find(".modal-title").html("编辑收藏"))):"searchCollect"==t?((i=$(".modal-searchCollect")).modal("show"),(e=i.find(".input-keyword")).focus(),!e.val()&&i.find(".searchList").html().match(/^\s*$/)&&this.clickSearch()):"tagList"==t?((i=$(".modal-tagList")).modal("show"),this.loadTagList(),this.backToTagList(),i.find(".input-keyword").val("").focus()):"userSetting"==t&&((i=$(".modal-userSetting")).modal("show"),this.loadSetting())},loadSetting(){var t=$(".modal-userSetting"),a=this,e=($("#customSwitch_newWindow")[0].checked=this.setting.newWindowOpen,$("#customSwitch_newWindow").unbind().on("change",function(){var t=$("#customSwitch_newWindow")[0].checked;a.setStorage("newWindowOpen",!t),a.setting.newWindowOpen=t}),window.location.origin+window.location.pathname.replace("index.html","")+"share/?u="+this.getStorage("username"));t.find(".input-shareUrl").val(e)},loadTagList(a){"edit"!=a&&(e=$(".modal-tagList"),(i=this).data.tagListObj={},this.data.tagListObjTemp={},this.data.tagListObjSelected={});var e,i=this;return $.ajax({method:"post",url:this.baseUrl+"api/get_tag_list.php",data:{username:this.getStorage("username"),password:this.getStorage("password")},contentType:"application/x-www-form-urlencoded",dataType:"json",success:function(t){if(200==t.code)return"edit"==a?void(i.data.tagListObj=t.data):0==t.data.length?void e.find(".tagList").html(`暂无标签
当前暂无标签
`):(i.loadTagListHtml(t.data),e.find(".allUnSelectTag").hide(),e.find(".allSelectTag").show(),void e.find(".submitSelect").attr("disabled","disabled"));alert(t.msg)},async:!1}),this.data.tagListObj},loadTagListHtml(t){this.data.tagListObj=t,this.data.tagListObjTemp=t;var a=$(".modal-tagList"),t=this.sortByNum(t),t=this.makeTags(t,"all");a.find(".tagList").html(t)},sortByNum(e){var a={};return Object.keys(e).sort(function(t,a){return e[a]-e[t]}).forEach(t=>{a[t]=e[t]}),a},sortByKey(a){var e={};return Object.keys(a).sort(function(t,a){return t.localeCompare(a)}).forEach(t=>{e[t]=a[t]}),e},addTag(t){var a=$(".modal-addCollect"),t=t||a.find(".input-tagName").val();(t=$.trim(t))&&(this.tagList.push(t),this.tagList=this.unique(this.tagList),a.find(".tagList").html(this.makeTags(this.tagList)),this.giveClick(".tagList"),a.find(".input-tagName").val("").focus())},getHostFromUrl(){var t=$(".modal-addCollect").find(".input-url");try{var a=new URL(t.val())}catch{return void alert("网址格式错误")}t.val(a.origin)},getWebTitle(){var a=this,e=$(".modal-addCollect"),t=e.find(".input-url");try{new URL(t.val())}catch{return void alert("网址格式错误")}t=t.val();e.find("button.getWebTitle").html("获取中").attr("disabled","disabled"),$.ajax({method:"post",url:this.baseUrl+"api/get_title.php",data:{url:t},contentType:"application/x-www-form-urlencoded",dataType:"json",success:function(t){e.find("button.getWebTitle").html("获取").removeAttr("disabled"),e.find(".input-title").val(t.data),t.data&&a.fenci()}})},giveClick(t){var a,e=this;".tagList"==t&&(a=$(".modal-addCollect")).find(".tagList div").unbind().click(function(){var t=$(this).text();e.removeArray(e.tagList,t),a.find(".tagList").html(e.makeTags(e.tagList)),e.giveClick(".tagList"),a.find(".input-tagName").focus()})},addCollect(){var t,a,e=this,i=$(".modal-addCollect"),s=$.trim(i.find(".input-url").val()),d=$.trim(i.find(".input-title").val()),i=$.trim(i.find(".input-note").val());try{new URL(s)}catch{return void alert("网址格式错误")}d?(t=JSON.stringify(this.tagList),a=$("#customSwitch_private")[0].checked?1:0,$.ajax({method:"post",url:this.baseUrl+"api/add_collect.php",data:{username:this.getStorage("username"),password:this.getStorage("password"),url:s,title:d,tags:t,private:a,mode:this.editMode,note:i},contentType:"application/x-www-form-urlencoded",dataType:"json",success:function(t){if(200==t.code)return $(".modal-addCollect").modal("hide"),void("add"==e.editMode?(e.loadCollectList(0),e.cleanInput()):"update"==e.editMode&&(e.updateEditingNode(),e.cleanInput()));alert(t.msg)}})):alert("请输入网页标题")},updateEditingNode(){var t=$(this.editingNode),a=$(".modal-addCollect"),e=(t.find(".title").text(a.find(".input-title").val()),t.find(".note").text(a.find(".input-note").val()),t.find(".url").text(a.find(".input-url").val()),t.find(".card-body").attr("data-private",$("#customSwitch_private")[0].checked?1:0),"");this.tagList.forEach(t=>{e+=`
${t}
`}),t.find(".tags").html(e).attr("data-tags",encodeURIComponent(JSON.stringify(this.tagList))),t.find(".update_time").html(this.parseDate((new Date).getTime()))},cleanInput(){var t=$(".modal-addCollect");t.find(".input-url").val(""),t.find(".input-title").val(""),t.find(".input-tagName").val(""),this.tagList=[],t.find(".tagList").html(""),t.find(".input-note").val("")},removeArray(e,i){return e.map((t,a)=>{t==i&&e.splice(a,1)}),e},makeTags(t,a){var e="";if("all"==a){for(var i in t)e+=`
${i} ${t[i]}
`;return e}if("edit"!=a)return t.forEach(t=>{e+=`
${t}
`}),e;for(i in t)e+=`
${i} ${t[i]}
`;return e},addTagListChecked(t){var t=$(t.target),t=(t=t.hasClass("btn")?t:t.parent()).find(".tag").text(),a=$(".modal-addCollect");(t=$.trim(t))&&(this.tagList.push(t),this.tagList=this.unique(this.tagList),a.find(".tagList").html(this.makeTags(this.tagList)),this.giveClick(".tagList"),a.find(".input-tagName").val("").focus())},tagListChecked(t){var a,t=$(t.target),e=(t=t.hasClass("btn")?t:t.parent()).find(".tag").text();t.hasClass("btn-light")?(t.removeClass("btn-light"),t.addClass("btn-primary"),t.removeClass("bg-warning"),this.data.tagListObjSelected[e]=this.data.tagListObj[e]):(t.removeClass("btn-primary"),t.addClass("btn-light"),this.indexTags(),delete this.data.tagListObjSelected[e]),Object.keys(this.data.tagListObjSelected).length==Object.keys(this.data.tagListObjTemp).length&&0{200==t.code&&t.data.forEach(t=>{1{var a="";t.tag_list.forEach(t=>{a+=`
${t}
`}),i+=`
${t.title}
${t.url}
${t.note}
${a}
删除复制编辑${this.parseDate(1e3*parseInt(t.update_time))}
`}),i},listItemEdit(t,a){var e=$(t.target).parents(".card-body"),i=e.find(".title").text(),s=e.find(".url").text(),d=e.find(".note").text(),l=JSON.parse(decodeURIComponent(e.find(".tags").attr("data-tags"))),l=(this.tagList=l,this.tagList=this.unique(this.tagList),e.attr("data-private")),e=(this.showModal("addCollect","update",a),"search"==a?$(".modal-searchCollect").modal("hide"):"byTag"==a&&$(".modal-tagList").modal("hide"),$(".modal-addCollect"));e.find(".tagList").html(this.makeTags(this.tagList)),this.giveClick(".tagList"),e.find(".input-title").val(i),e.find(".input-note").val(d),e.find(".input-url").val(s).attr("readonly","readonly"),e.find(".getHost").attr("disabled","disabled"),e.find("#customSwitch_private")[0].checked="1"==l,this.editMode="update",this.editingNode=$(t.target).parents(".mb-4")},listItemDelete(a,t,e){confirm("确定删除吗?")&&$.ajax({method:"post",url:this.baseUrl+"api/delete_collect.php",data:{username:this.getStorage("username"),password:this.getStorage("password"),url:t,time:e},contentType:"application/x-www-form-urlencoded",dataType:"json",success:function(t){200==t.code?$(a.target).parent().parent().parent().parent().remove():alert(t.msg)}})},goHref(t){this.setting.newWindowOpen?window.open(t):location.href=t},parseDate(t){function a(t){return t<10?"0"+t:t}return a((t=new Date(t)).getFullYear())+"-"+a(t.getMonth()+1)+"-"+a(t.getDate())+" "+a(t.getHours())+":"+a(t.getMinutes())},clickSearch(){var t=$(".modal-searchCollect").find(".input-keyword").val();this.searchCollect(t,0)},searchCollect(e,i){var s=this,d=$(".modal-searchCollect");0==i&&d.find(".searchList").html(""),$.ajax({method:"post",url:this.baseUrl+"api/search.php",data:{username:this.getStorage("username"),password:this.getStorage("password"),keyword:e,page:i,pageSize:36},contentType:"application/x-www-form-urlencoded",dataType:"json",success:function(t){if(200==t.code){if(0==t.data.length)return void(s.setting.isBottom_search=1);var a=s.makeList(t.data,"search");return d.find(".searchList").append(a),new ClipboardJS(".copybtn",{container:d[0]}),s.data.nowPage_search=i,s.data.keyword=e,void(s.setting.isBottom_search=0)}this.setting.isBottom_search=1,alert(t.msg)}})},logout(){confirm("确定退出吗?")&&(localStorage.removeItem(this.storageKey),location.reload())}}; -------------------------------------------------------------------------------- /public/api/Poncon.php: -------------------------------------------------------------------------------- 1 | $code, 107 | 'msg' => $msg 108 | ])); 109 | } 110 | 111 | /** 112 | * 返回成功信息,语法:success($msg); 113 | * @param string $msg 成功信息 114 | */ 115 | function success($msg, ...$args) 116 | { 117 | echo json_encode([ 118 | 'code' => 200, 119 | 'msg' => $msg, 120 | 'data' => isset($args[0]) ? $args[0] : null 121 | ]); 122 | } 123 | 124 | /** 125 | * 获取配置信息,语法:getConfig(); 126 | */ 127 | function getConfig() 128 | { 129 | require 'config.php'; 130 | return $config; 131 | } 132 | 133 | /** 134 | * 初始化数据库,语法:initDB(); 135 | */ 136 | function initDb() 137 | { 138 | $config = $this->getConfig(); 139 | $conn = mysqli_connect($config['mysql']['host'], $config['mysql']['user'], $config['mysql']['pass'], $config['mysql']['db']); 140 | if (!$conn) { 141 | die(json_encode([ 142 | 'code' => 903, 143 | 'msg' => '数据库错误' 144 | ])); 145 | } 146 | 147 | // 新建用户表 148 | $table = $config['table']['user']; 149 | $sql = "CREATE TABLE IF NOT EXISTS `$table` ( 150 | `id` int(11) NOT NULL AUTO_INCREMENT, 151 | `username` varchar(255) NOT NULL, -- 用户名 152 | `password` varchar(255) NOT NULL, -- 密码 md5密文 153 | `register_time` int(11) NOT NULL, -- 注册时间 154 | PRIMARY KEY (`id`) -- 主键 155 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; 156 | $result = mysqli_query($conn, $sql); 157 | if (!$result) { 158 | $this->error(903, '数据库错误'); 159 | } 160 | // 新建收藏表 161 | $table = $config['table']['collect']; 162 | $sql = "CREATE TABLE IF NOT EXISTS `$table` ( 163 | `id` int(11) NOT NULL AUTO_INCREMENT, 164 | `username` varchar(255) NOT NULL, -- 收藏者用户名 165 | `title` TEXT NOT NULL, -- 网页标题 166 | `url` TEXT NOT NULL, -- 网址 167 | `update_time` int(11) NOT NULL, -- 更新时间 168 | `tag_list` TEXT NOT NULL, -- 标签列表,以逗号分隔 169 | `note` TEXT NOT NULL, -- 备注 170 | `private` int(11) NOT NULL, -- 0:公开 1:私密 171 | PRIMARY KEY (`id`) -- 主键 172 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; 173 | $result = mysqli_query($conn, $sql); 174 | if (!$result) { 175 | $this->error(903, '数据库错误'); 176 | } 177 | return $conn; 178 | } 179 | 180 | /** 181 | * 登录验证,语法:login($conn, $username, $password); 182 | * @param object $conn 数据库连接 183 | * @param string $username 用户名 184 | * @param string $password 密码 185 | * @return array|null 186 | */ 187 | function login($conn, $username, $password) 188 | { 189 | $config = $this->getConfig(); 190 | $table = $config['table']['user']; 191 | $sql = "SELECT * FROM `$table` WHERE `username` = '$username' AND `password` = '$password'"; 192 | $result = mysqli_query($conn, $sql); 193 | if (!$result) { 194 | $this->error(903, '数据库错误'); 195 | } 196 | $row = mysqli_fetch_assoc($result); 197 | if (!$row) { 198 | $this->error(907, '用户名或密码错误'); 199 | } 200 | return $row; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /public/api/add_collect.php: -------------------------------------------------------------------------------- 1 | initDb(); 14 | 15 | $username = $poncon->POST('username', null, true); 16 | $password = $poncon->POST('password', null, true); 17 | $tags = $poncon->POST('tags', null, true); // JSON数组[...] 18 | $title = $poncon->POST('title', null, true); 19 | $url = $poncon->POST('url', null, true); 20 | $private = $poncon->POST('private', 0, true); // 0:公开 1:私密 21 | $note = $poncon->POST('note', null, true); // 备注 22 | $update_time = time(); 23 | $mode = $poncon->POST('mode', 'add', true); // add:新增 update:更新 24 | 25 | if (!$username || !$password || !$title || !$url) { 26 | $poncon->error(900, '参数缺失'); 27 | } 28 | 29 | // 登录验证 30 | $poncon->login($conn, $username, $password); 31 | 32 | $config = $poncon->getConfig(); 33 | $table = $config['table']['collect']; 34 | 35 | // 判断URL是否存在 36 | 37 | $sql = "SELECT `url` FROM `$table` WHERE `url` = '$url' LIMIT 1;"; 38 | $result = mysqli_query($conn, $sql); 39 | if (mysqli_num_rows($result) > 0 && $mode == 'add') { 40 | $poncon->error(904, '记录已经存在'); 41 | } else if (mysqli_num_rows($result) == 0 && $mode == 'update') { 42 | $poncon->error(904, '记录不存在'); 43 | } 44 | if ($mode == 'add') { 45 | // 增加收藏 46 | $sql = "INSERT INTO `$table` (`username`, `tag_list`, `update_time`, `title`, `url`, `private`, `note`) VALUES ('$username', '$tags', $update_time, '$title', '$url', $private, '$note');"; 47 | } else if ($mode == 'update') { 48 | // 更新 49 | $sql = "UPDATE `$table` SET `tag_list` = '$tags', `title` = '$title', `url` = '$url', `private` = '$private', `update_time` = '$update_time', `note` = '$note' WHERE `url` = '$url' LIMIT 1;"; 50 | } 51 | 52 | $result = mysqli_query($conn, $sql); 53 | if (!$result) { 54 | $poncon->error(903, '数据库错误'); 55 | } 56 | 57 | $poncon->success('成功'); 58 | -------------------------------------------------------------------------------- /public/api/config.default.php: -------------------------------------------------------------------------------- 1 | [ 10 | 'host' => 'localhost', 11 | 'user' => 'root', 12 | 'pass' => '', // 数据库密码 13 | 'db' => '' // 数据库名称 14 | ], 15 | 'table' => [ 16 | 'user' => 'mypages_user', // 用户表 17 | 'collect' => 'mypages_collect' // 收藏表 18 | ] 19 | ]; 20 | 21 | -------------------------------------------------------------------------------- /public/api/delete_collect.php: -------------------------------------------------------------------------------- 1 | initDb(); 13 | 14 | $config = $poncon->getConfig(); 15 | 16 | $data = []; 17 | 18 | $table = $config['table']['collect']; 19 | 20 | $username = $poncon->POST('username', null, true); 21 | $password = $poncon->POST('password', null, true); 22 | // 通过URL和时间戳去删除收藏 23 | $url = $poncon->POST('url', null, true); 24 | $time = $poncon->POST('time', null, true); 25 | 26 | if (!$username || !$password || !$url || !$time) { 27 | $poncon->error(900, '参数缺失'); 28 | } 29 | 30 | $poncon->login($conn, $username, $password); 31 | 32 | $sql = "DELETE FROM `$table` WHERE `username` = '$username' AND `url` = '$url' AND `update_time` = '$time';"; 33 | $result = mysqli_query($conn, $sql); 34 | 35 | if ($result) { 36 | $poncon->success('删除成功'); 37 | } else { 38 | $poncon->error(910, '删除失败'); 39 | } 40 | -------------------------------------------------------------------------------- /public/api/fenci.php: -------------------------------------------------------------------------------- 1 | GET('text', '', true); 14 | 15 | $result = $poncon->request('http://39.96.43.154:8080/api', 'POST', json_encode(['text' => $text]), 'Content-Type: application/json'); 16 | 17 | $result = json_decode($result, true); 18 | 19 | $words = $result['words']; 20 | 21 | $data = []; 22 | 23 | foreach ($words as $word) { 24 | $data[] = $word['text']; 25 | } 26 | 27 | $poncon->success('分词成功', $data); 28 | -------------------------------------------------------------------------------- /public/api/get_collect_list.php: -------------------------------------------------------------------------------- 1 | initDb(); 15 | 16 | $data = []; 17 | 18 | $config = $poncon->getConfig(); 19 | $table = $config['table']['collect']; 20 | 21 | $username = $poncon->POST('username', null, true); 22 | $password = $poncon->POST('password', null, true); 23 | $page = $poncon->POST('page', 0, true); 24 | $pageSize = $poncon->POST('pageSize', 36, true); 25 | $offset = $page * $pageSize; 26 | if (!$username || !$password) { 27 | $poncon->error(900, '参数缺失'); 28 | } 29 | 30 | // 登录验证 31 | $poncon->login($conn, $username, $password); 32 | 33 | 34 | 35 | 36 | $sql = "SELECT * FROM `$table` WHERE `username` = '$username' ORDER BY `update_time` DESC LIMIT $pageSize OFFSET $offset;"; 37 | $result = mysqli_query($conn, $sql); 38 | 39 | if (!$result) { 40 | $poncon->error(903, '数据库错误'); 41 | } 42 | 43 | 44 | while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { 45 | $row['tag_list'] = json_decode($row['tag_list'], true); 46 | unset($row['id']); 47 | unset($row['username']); 48 | array_push($data, $row); 49 | } 50 | $poncon->success('获取成功', $data); 51 | -------------------------------------------------------------------------------- /public/api/get_collect_list_by_tag.php: -------------------------------------------------------------------------------- 1 | initDb(); 13 | 14 | $config = $poncon->getConfig(); 15 | 16 | $username = $poncon->POST('username', null, true); 17 | $password = $poncon->POST('password', null, true); 18 | $page = $poncon->POST('page', 0, true); 19 | $pageSize = $poncon->POST('pageSize', 36, true); 20 | $offset = $page * $pageSize; 21 | $tags = json_decode($poncon->POST('tags', '[]'), true); 22 | 23 | $data = []; 24 | 25 | $config = $poncon->getConfig(); 26 | $table = $config['table']['collect']; 27 | 28 | if (!$username || !$password) { 29 | $poncon->error(900, '参数缺失'); 30 | } 31 | 32 | $poncon->login($conn, $username, $password); 33 | 34 | // 查询包含标签列表的数据 35 | $sql = "SELECT * FROM `$table` WHERE `username` = '$username' AND (`tag_list` LIKE '%\"" . implode("\"%' AND `tag_list` LIKE '%\"", $tags) . "\"%') LIMIT $pageSize OFFSET $offset;"; 36 | 37 | $result = mysqli_query($conn, $sql); 38 | 39 | if (!$result) { 40 | $poncon->error(903, '数据库错误'); 41 | } 42 | 43 | while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { 44 | $row['tag_list'] = json_decode($row['tag_list'], true); 45 | unset($row['id']); 46 | unset($row['username']); 47 | array_push($data, $row); 48 | } 49 | $poncon->success('获取成功', $data); 50 | -------------------------------------------------------------------------------- /public/api/get_tag_list.php: -------------------------------------------------------------------------------- 1 | initDb(); 13 | 14 | $config = $poncon->getConfig(); 15 | 16 | $username = $poncon->POST('username', null, true); 17 | $password = $poncon->POST('password', null, true); 18 | 19 | if (!$username || !$password) { 20 | $poncon->error(900, '参数缺失'); 21 | } 22 | 23 | $poncon->login($conn, $username, $password); 24 | 25 | $sql = "SELECT `tag_list` FROM `{$config['table']['collect']}` WHERE `username` = '$username' ORDER BY `update_time` DESC;"; 26 | 27 | $result = mysqli_query($conn, $sql); 28 | 29 | if (!$result) { 30 | $poncon->error(903, '数据库错误'); 31 | } 32 | $data = []; 33 | 34 | while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { 35 | $list = json_decode($row['tag_list'], true); 36 | foreach ($list as $tag) { 37 | if (!isset($data[$tag])) { 38 | $data[$tag] = 1; 39 | } else { 40 | $data[$tag]++; 41 | } 42 | } 43 | } 44 | 45 | $poncon->success('获取成功', $data); 46 | -------------------------------------------------------------------------------- /public/api/get_title.php: -------------------------------------------------------------------------------- 1 | POST('url', null, true); 13 | if (!$url) { 14 | $poncon->error(900, '参数缺失'); 15 | } 16 | 17 | $ym = file_get_contents($url); 18 | 19 | $title = $poncon->sj($ym, ''); 20 | $title = $poncon->sj($title, '>', null); 21 | // 去除实体 22 | $title = html_entity_decode($title); 23 | 24 | $poncon->success('获取成功', $title); 25 | -------------------------------------------------------------------------------- /public/api/login.php: -------------------------------------------------------------------------------- 1 | initDb(); 15 | 16 | $username = $poncon->POST('username', null, true); 17 | $password = $poncon->POST('password', null, true); 18 | 19 | if (!$username || !$password) { 20 | $poncon->error(900, '参数缺失'); 21 | } 22 | 23 | // 登录验证 24 | $poncon->login($conn, $username, $password); 25 | 26 | $poncon->success('登录成功', [ 27 | 'username' => $username, 28 | 'password' => $password 29 | ]); 30 | -------------------------------------------------------------------------------- /public/api/public_share_list.php: -------------------------------------------------------------------------------- 1 | initDb(); 13 | 14 | $config = $poncon->getConfig(); 15 | 16 | $username = $poncon->POST('username', null, true); 17 | $page = $poncon->POST('page', 0, true); 18 | $pageSize = $poncon->POST('pageSize', 36, true); 19 | $offset = $page * $pageSize; 20 | 21 | $table = $config['table']['collect']; 22 | 23 | $data = []; 24 | 25 | $sql = "SELECT * FROM `$table` WHERE `username` = '$username' ORDER BY `update_time` DESC LIMIT $pageSize OFFSET $offset;"; 26 | 27 | 28 | $result = mysqli_query($conn, $sql); 29 | 30 | if (!$result) { 31 | $poncon->error(903, '数据库错误'); 32 | } 33 | 34 | while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { 35 | unset($row['id']); 36 | unset($row['username']); 37 | $data[] = $row; 38 | } 39 | 40 | $poncon->success('获取成功', $data); 41 | -------------------------------------------------------------------------------- /public/api/register.php: -------------------------------------------------------------------------------- 1 | initDb(); 15 | 16 | $username = $poncon->POST('username'); 17 | $password = $poncon->POST('password'); 18 | 19 | if (!$username || !$password) { 20 | $poncon->error(900, '参数缺失'); 21 | } 22 | 23 | 24 | // 查询用户名是否存在 25 | $sql = "SELECT * FROM `mypages_user` WHERE `username` = '$username'"; 26 | $result = mysqli_query($conn, $sql); 27 | if (!$result) { 28 | $poncon->error(903, '数据库错误'); 29 | } 30 | if (mysqli_num_rows($result) > 0) { 31 | $poncon->error(901, '用户名已存在'); 32 | } 33 | 34 | // 插入用户信息 35 | $sql = "INSERT INTO `mypages_user` (`username`, `password`, `register_time`) VALUES ('$username', '$password', " . time() . ")"; 36 | $result = mysqli_query($conn, $sql); 37 | if (!$result) { 38 | $poncon->error(903, '数据库错误'); 39 | } 40 | 41 | $poncon->success('注册成功', [ 42 | 'username' => $username, 43 | 'password' => $password 44 | ]); 45 | -------------------------------------------------------------------------------- /public/api/search.php: -------------------------------------------------------------------------------- 1 | initDb(); 14 | 15 | $username = $poncon->POST('username', null, true); 16 | $password = $poncon->POST('password', null, true); 17 | $keyword = $poncon->POST('keyword', null, true); 18 | $page = $poncon->POST('page', 0, true); 19 | $pageSize = $poncon->POST('pageSize', 36, true); 20 | $offset = $page * $pageSize; 21 | 22 | if (!$username || !$password) { 23 | $poncon->error(900, '参数缺失'); 24 | } 25 | 26 | // 登录验证 27 | $poncon->login($conn, $username, $password); 28 | 29 | $data = []; 30 | 31 | $config = $poncon->getConfig(); 32 | $table = $config['table']['collect']; 33 | preg_match('/^#\s*(.*)$/', $keyword, $matches); 34 | if (isset($matches[1])) { 35 | // 搜索标签 36 | $tag = $matches[1]; 37 | $sql = "SELECT * FROM `$table` WHERE `username` = '$username' AND `tag_list` LIKE '%$tag%' ORDER BY `update_time` DESC LIMIT $pageSize OFFSET $offset;"; 38 | $result = mysqli_query($conn, $sql); 39 | while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { 40 | $row['tag_list'] = json_decode($row['tag_list'], true); 41 | unset($row['id']); 42 | unset($row['username']); 43 | array_push($data, $row); 44 | } 45 | $poncon->success('获取成功', $data); 46 | } else { 47 | // 搜索所有 48 | $sql = "SELECT * FROM `$table` WHERE `username` = '$username' AND (`title` LIKE '%$keyword%' OR `url` LIKE '%$keyword%' OR `tag_list` LIKE '%$keyword%' OR `note` LIKE '%$keyword%') ORDER BY `update_time` DESC LIMIT $pageSize OFFSET $offset;"; 49 | $result = mysqli_query($conn, $sql); 50 | while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { 51 | $row['tag_list'] = json_decode($row['tag_list'], true); 52 | unset($row['id']); 53 | unset($row['username']); 54 | array_push($data, $row); 55 | } 56 | $poncon->success('获取成功', $data); 57 | } 58 | -------------------------------------------------------------------------------- /public/css/index.css: -------------------------------------------------------------------------------- 1 | .oyp-rounded-lg{border-radius:1rem}*{word-wrap:break-word!important;word-break:break-all!important}.oyp-limit-line{overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.collectList .rightTop{position:absolute;bottom:0;right:0} -------------------------------------------------------------------------------- /public/img/cat-2722309_640.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iuroc/mypages/e1fa7187460eb41275f69ca84e3da6677224023c/public/img/cat-2722309_640.png -------------------------------------------------------------------------------- /public/img/chevron-left-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/insect-6626635_1920.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iuroc/mypages/e1fa7187460eb41275f69ca84e3da6677224023c/public/img/insect-6626635_1920.jpg -------------------------------------------------------------------------------- /public/img/plus-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/trash-can-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 主页 - My Pages

-------------------------------------------------------------------------------- /public/js/index.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){function n(o){Poncon.loginStatus||Poncon.login(Poncon.getStorage("username"),Poncon.getStorage("password"),!0),scrollTo(0,0);var n=(o=o.split("/"))[1];$(".page-oyp").css("display","none");try{var t=$(".page-"+n);t.css("display","block")}catch{location.href=Poncon.entryPage}"home"==n?(document.title="主页 - "+Poncon.title,$(".webTitle").html(Poncon.title),Poncon.pageLoad.home||(Poncon.pageLoad.home=!0,Poncon.setting.isBottom=0),Poncon.loadCollectList(0)):"login"==n?($(".webTitle").html(""),"register"==o[2]?(document.title="用户注册 - "+Poncon.title,t.find(".sub-page-login").hide(),t.find(".sub-page-register").show()):(document.title="用户登录 - "+Poncon.title,t.find(".sub-page-register").hide(),t.find(".sub-page-login").show())):"user"==n?(document.title="个人中心 - "+Poncon.title,$(".webTitle").html("个人中心")):location.href=Poncon.entryPage,"home"==n||"login"==n?$(".btn-back-oyp").css({display:"none"}):$(".btn-back-oyp").css({display:"block"}),"home"==n?$(".userCenter").css("display","inline-block"):$(".userCenter").css("display","none")}$("body").removeClass("fade"),new ClipboardJS(".copybtn"),location.hash.split("/")[1]||history.replaceState({},null,Poncon.entryPage),n(location.hash),Poncon.setting.newWindowOpen=!Poncon.getStorage("newWindowOpen"),window.addEventListener("hashchange",function(o){n(new URL(o.newURL).hash)}),$(window).scroll(function(){var o=$(this).scrollTop(),n=$(document).height(),t=$(this).height();"home"==location.hash.split("/")[1]&&n
当前暂无标签
`):(i.loadTagListHtml(t.data),e.find(".allUnSelectTag").hide(),e.find(".allSelectTag").show(),void e.find(".submitSelect").attr("disabled","disabled"));alert(t.msg)},async:!1}),this.data.tagListObj},loadTagListHtml(t){this.data.tagListObj=t,this.data.tagListObjTemp=t;var a=$(".modal-tagList"),t=this.sortByNum(t),t=this.makeTags(t,"all");a.find(".tagList").html(t)},sortByNum(e){var a={};return Object.keys(e).sort(function(t,a){return e[a]-e[t]}).forEach(t=>{a[t]=e[t]}),a},sortByKey(a){var e={};return Object.keys(a).sort(function(t,a){return t.localeCompare(a)}).forEach(t=>{e[t]=a[t]}),e},addTag(t){var a=$(".modal-addCollect"),t=t||a.find(".input-tagName").val();(t=$.trim(t))&&(this.tagList.push(t),this.tagList=this.unique(this.tagList),a.find(".tagList").html(this.makeTags(this.tagList)),this.giveClick(".tagList"),a.find(".input-tagName").val("").focus())},getHostFromUrl(){var t=$(".modal-addCollect").find(".input-url");try{var a=new URL(t.val())}catch{return void alert("网址格式错误")}t.val(a.origin)},getWebTitle(){var a=this,e=$(".modal-addCollect"),t=e.find(".input-url");try{new URL(t.val())}catch{return void alert("网址格式错误")}t=t.val();e.find("button.getWebTitle").html("获取中").attr("disabled","disabled"),$.ajax({method:"post",url:this.baseUrl+"api/get_title.php",data:{url:t},contentType:"application/x-www-form-urlencoded",dataType:"json",success:function(t){e.find("button.getWebTitle").html("获取").removeAttr("disabled"),e.find(".input-title").val(t.data),t.data&&a.fenci()}})},giveClick(t){var a,e=this;".tagList"==t&&(a=$(".modal-addCollect")).find(".tagList div").unbind().click(function(){var t=$(this).text();e.removeArray(e.tagList,t),a.find(".tagList").html(e.makeTags(e.tagList)),e.giveClick(".tagList"),a.find(".input-tagName").focus()})},addCollect(){var t,a,e=this,i=$(".modal-addCollect"),s=$.trim(i.find(".input-url").val()),d=$.trim(i.find(".input-title").val()),i=$.trim(i.find(".input-note").val());try{new URL(s)}catch{return void alert("网址格式错误")}d?(t=JSON.stringify(this.tagList),a=$("#customSwitch_private")[0].checked?1:0,$.ajax({method:"post",url:this.baseUrl+"api/add_collect.php",data:{username:this.getStorage("username"),password:this.getStorage("password"),url:s,title:d,tags:t,private:a,mode:this.editMode,note:i},contentType:"application/x-www-form-urlencoded",dataType:"json",success:function(t){if(200==t.code)return $(".modal-addCollect").modal("hide"),void("add"==e.editMode?(e.loadCollectList(0),e.cleanInput()):"update"==e.editMode&&(e.updateEditingNode(),e.cleanInput()));alert(t.msg)}})):alert("请输入网页标题")},updateEditingNode(){var t=$(this.editingNode),a=$(".modal-addCollect"),e=(t.find(".title").text(a.find(".input-title").val()),t.find(".note").text(a.find(".input-note").val()),t.find(".url").text(a.find(".input-url").val()),t.find(".card-body").attr("data-private",$("#customSwitch_private")[0].checked?1:0),"");this.tagList.forEach(t=>{e+=`
${t}
`}),t.find(".tags").html(e).attr("data-tags",encodeURIComponent(JSON.stringify(this.tagList))),t.find(".update_time").html(this.parseDate((new Date).getTime()))},cleanInput(){var t=$(".modal-addCollect");t.find(".input-url").val(""),t.find(".input-title").val(""),t.find(".input-tagName").val(""),this.tagList=[],t.find(".tagList").html(""),t.find(".input-note").val("")},removeArray(e,i){return e.map((t,a)=>{t==i&&e.splice(a,1)}),e},makeTags(t,a){var e="";if("all"==a){for(var i in t)e+=`
${i} ${t[i]}
`;return e}if("edit"!=a)return t.forEach(t=>{e+=`
${t}
`}),e;for(i in t)e+=`
${i} ${t[i]}
`;return e},addTagListChecked(t){var t=$(t.target),t=(t=t.hasClass("btn")?t:t.parent()).find(".tag").text(),a=$(".modal-addCollect");(t=$.trim(t))&&(this.tagList.push(t),this.tagList=this.unique(this.tagList),a.find(".tagList").html(this.makeTags(this.tagList)),this.giveClick(".tagList"),a.find(".input-tagName").val("").focus())},tagListChecked(t){var a,t=$(t.target),e=(t=t.hasClass("btn")?t:t.parent()).find(".tag").text();t.hasClass("btn-light")?(t.removeClass("btn-light"),t.addClass("btn-primary"),t.removeClass("bg-warning"),this.data.tagListObjSelected[e]=this.data.tagListObj[e]):(t.removeClass("btn-primary"),t.addClass("btn-light"),this.indexTags(),delete this.data.tagListObjSelected[e]),Object.keys(this.data.tagListObjSelected).length==Object.keys(this.data.tagListObjTemp).length&&0{200==t.code&&t.data.forEach(t=>{1{var a="";t.tag_list.forEach(t=>{a+=`
${t}
`}),i+=`
${t.title}
${t.url}
${t.note}
${a}
删除复制编辑${this.parseDate(1e3*parseInt(t.update_time))}
`}),i},listItemEdit(t,a){var e=$(t.target).parents(".card-body"),i=e.find(".title").text(),s=e.find(".url").text(),d=e.find(".note").text(),l=JSON.parse(decodeURIComponent(e.find(".tags").attr("data-tags"))),l=(this.tagList=l,this.tagList=this.unique(this.tagList),e.attr("data-private")),e=(this.showModal("addCollect","update",a),"search"==a?$(".modal-searchCollect").modal("hide"):"byTag"==a&&$(".modal-tagList").modal("hide"),$(".modal-addCollect"));e.find(".tagList").html(this.makeTags(this.tagList)),this.giveClick(".tagList"),e.find(".input-title").val(i),e.find(".input-note").val(d),e.find(".input-url").val(s).attr("readonly","readonly"),e.find(".getHost").attr("disabled","disabled"),e.find("#customSwitch_private")[0].checked="1"==l,this.editMode="update",this.editingNode=$(t.target).parents(".mb-4")},listItemDelete(a,t,e){confirm("确定删除吗?")&&$.ajax({method:"post",url:this.baseUrl+"api/delete_collect.php",data:{username:this.getStorage("username"),password:this.getStorage("password"),url:t,time:e},contentType:"application/x-www-form-urlencoded",dataType:"json",success:function(t){200==t.code?$(a.target).parent().parent().parent().parent().remove():alert(t.msg)}})},goHref(t){this.setting.newWindowOpen?window.open(t):location.href=t},parseDate(t){function a(t){return t<10?"0"+t:t}return a((t=new Date(t)).getFullYear())+"-"+a(t.getMonth()+1)+"-"+a(t.getDate())+" "+a(t.getHours())+":"+a(t.getMinutes())},clickSearch(){var t=$(".modal-searchCollect").find(".input-keyword").val();this.searchCollect(t,0)},searchCollect(e,i){var s=this,d=$(".modal-searchCollect");0==i&&d.find(".searchList").html(""),$.ajax({method:"post",url:this.baseUrl+"api/search.php",data:{username:this.getStorage("username"),password:this.getStorage("password"),keyword:e,page:i,pageSize:36},contentType:"application/x-www-form-urlencoded",dataType:"json",success:function(t){if(200==t.code){if(0==t.data.length)return void(s.setting.isBottom_search=1);var a=s.makeList(t.data,"search");return d.find(".searchList").append(a),new ClipboardJS(".copybtn",{container:d[0]}),s.data.nowPage_search=i,s.data.keyword=e,void(s.setting.isBottom_search=0)}this.setting.isBottom_search=1,alert(t.msg)}})},logout(){confirm("确定退出吗?")&&(localStorage.removeItem(this.storageKey),location.reload())}}; -------------------------------------------------------------------------------- /public/share/index.php: -------------------------------------------------------------------------------- 1 | initDb(); 15 | 16 | $config = $poncon->getConfig(); 17 | 18 | 19 | 20 | $username = $poncon->GET('u', null, true); 21 | $keyword = $poncon->GET('s', null, true); 22 | 23 | if (!$username) { 24 | die('用户名不能为空'); 25 | } 26 | 27 | // 判断用户名是否存在 28 | $table = $config['table']['user']; 29 | $sql = "SELECT * FROM $table WHERE username = '$username'"; 30 | $result = mysqli_query($conn, $sql); 31 | if (mysqli_num_rows($result) == 0) { 32 | die('用户名不存在'); 33 | } 34 | 35 | $table = $config['table']['collect']; 36 | $sql = "SELECT * FROM $table WHERE username = '$username' AND `private` = 0 AND (`title` LIKE '%$keyword%' OR `url` LIKE '%$keyword%' OR `tag_list` LIKE '%$keyword%' OR `note` LIKE '%$keyword%') ORDER BY `update_time` DESC;"; 37 | $result = mysqli_query($conn, $sql); 38 | 39 | ?> 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | <?php echo "$username 的分享" ?> 48 | 49 | 50 | 51 |
52 |
$username 的分享" ?>
53 | 57 | 62 | '; 63 | } 64 | 65 | ?> 66 | 67 |
68 | # $tag"; 75 | } 76 | $html .= " 77 |
78 |
79 |
{$row['title']}
80 | {$row['url']} 81 |
{$row['note']}
82 |
{$tagsHtml}
83 |
84 |
"; 85 | } 86 | echo $html; 87 | ?> 88 | 89 |
90 |
91 | 92 | 93 | -------------------------------------------------------------------------------- /share/index.php: -------------------------------------------------------------------------------- 1 | initDb(); 15 | 16 | $config = $poncon->getConfig(); 17 | 18 | 19 | 20 | $username = $poncon->GET('u', null, true); 21 | $keyword = $poncon->GET('s', null, true); 22 | 23 | if (!$username) { 24 | die('用户名不能为空'); 25 | } 26 | 27 | // 判断用户名是否存在 28 | $table = $config['table']['user']; 29 | $sql = "SELECT * FROM $table WHERE username = '$username'"; 30 | $result = mysqli_query($conn, $sql); 31 | if (mysqli_num_rows($result) == 0) { 32 | die('用户名不存在'); 33 | } 34 | 35 | $table = $config['table']['collect']; 36 | $sql = "SELECT * FROM $table WHERE username = '$username' AND `private` = 0 AND (`title` LIKE '%$keyword%' OR `url` LIKE '%$keyword%' OR `tag_list` LIKE '%$keyword%' OR `note` LIKE '%$keyword%') ORDER BY `update_time` DESC;"; 37 | $result = mysqli_query($conn, $sql); 38 | 39 | ?> 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | <?php echo "$username 的分享" ?> 48 | 49 | 50 | 51 |
52 |
$username 的分享" ?>
53 | 57 | 62 | '; 63 | } 64 | 65 | ?> 66 | 67 |
68 | # $tag"; 75 | } 76 | $html .= " 77 |
78 |
79 |
{$row['title']}
80 | {$row['url']} 81 |
{$row['note']}
82 |
{$tagsHtml}
83 |
84 |
"; 85 | } 86 | echo $html; 87 | ?> 88 | 89 |
90 |
91 | 92 | 93 | --------------------------------------------------------------------------------