├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── WordReview ├── Makefile ├── WordReview │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── apps │ ├── review │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── src │ │ │ ├── init_db.py │ │ │ └── spider.py │ │ ├── templates │ │ │ ├── calendar.pug │ │ │ ├── homepage.pug │ │ │ ├── import_db.pug │ │ │ └── review.pug │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py │ └── src │ │ └── util.py ├── config.py ├── config_sample.conf ├── installer.py ├── manage.py ├── static │ ├── css │ │ ├── base.css │ │ ├── calendar.css │ │ └── thirdParty │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.css.map │ ├── font │ │ └── fontawesome │ │ │ ├── FontAwesome.otf │ │ │ ├── font-awesome.min.css │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ └── fontawesome-webfont.woff │ ├── js │ │ ├── calendar.js │ │ ├── echarts.min.js │ │ ├── homepage.js │ │ ├── review.js │ │ ├── thirdParty │ │ │ ├── hotkeys.min.js │ │ │ ├── jquery │ │ │ │ ├── jquery-3.5.1.min.js │ │ │ │ └── jquery.tmpl.min.js │ │ │ └── layer │ │ │ │ ├── layer.js │ │ │ │ ├── mobile │ │ │ │ ├── layer.js │ │ │ │ └── need │ │ │ │ │ └── layer.css │ │ │ │ └── theme │ │ │ │ └── default │ │ │ │ ├── icon-ext.png │ │ │ │ ├── icon.png │ │ │ │ ├── layer.css │ │ │ │ ├── loading-0.gif │ │ │ │ ├── loading-1.gif │ │ │ │ └── loading-2.gif │ │ └── util.js │ ├── media │ │ ├── muyi.png │ │ └── vocabulary.png │ └── scss │ │ └── review.scss └── templates │ └── base.pug ├── data └── sample │ ├── sample.csv │ └── sample.xlsx ├── doc ├── CHANGELOG.md ├── database_init.md ├── demo.js ├── img │ ├── demo1.png │ ├── demo2.png │ ├── demo_calendar.png │ └── demo_homepage.png └── install.md ├── extension ├── img │ └── vocabulary.png ├── js │ ├── background.js │ ├── content-script.js │ ├── jquery-3.5.1.min.js │ ├── options.js │ ├── popup.js │ └── util.js ├── manifest.json ├── options.html └── popup.html ├── index.html └── requirements.txt /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: 报告问题 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | 10 | 11 | 12 | 13 | - [ ] 我已阅读[READEME.md](https://github.com/Benature/WordReview/blob/ben/README.md)中的[问题自检](https://github.com/Benature/WordReview/blob/ben/README.md#%E9%97%AE%E9%A2%98%E8%87%AA%E6%A3%80) 14 | 15 | **描述问题** 16 | 请简明阐述您的问题 17 | 18 | **复现问题** 19 | 复现问题的步骤: 20 | 21 | 22 | 23 | 1. 24 | 2. 25 | 26 | ``` 27 | 请将报错信息复制到此处 28 | ``` 29 | 30 | **截图:**(可选) 31 | 32 | 33 | 34 | 35 | **操作环境(请提供以下完整数据):** 36 | 37 | - 操作系统:mac / windows 38 | - 使用方式:源码 / 可执行文件(即在 release 页面下载的) 39 | - 可执行文件版本(若是):如 0.2.0 40 | - 数据库类型:sqlite(默认) / mysql 41 | - 浏览器:Chrome / Firefox / Edge / etc. 42 | 43 | **额外信息:**(可选) 44 | 任何其他的您认为有助于能描述问题/解决问题的信息。 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: 新功能建议 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | 10 | 11 | **功能描述** 12 | 13 | 14 | **希望这个功能可以解决:**(可选) 15 | 16 | 17 | **额外的信息**(可选) 18 | 任何其他的您认为能描述该功能的信息。 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | /.idea/ 3 | /.vscode/ 4 | 5 | **/__pycache__/ 6 | **/.ipynb_checkpoints/ 7 | 8 | # django 9 | **/migrations/* 10 | !**/migrations/__init__.py 11 | staticsfile/ 12 | uwsgi.out 13 | logs/ 14 | /media/ 15 | 16 | # config 17 | WordReview/config.conf 18 | 19 | # PyInstaller 20 | WordReview/build/ 21 | WordReview/dist/ 22 | *.spec 23 | 24 | # local 25 | data/ 26 | *.sqlite3 27 | /backup/ 28 | /pypi/ 29 | WordReview.wiki/ 30 | *_ig.* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Word Review 单词复习 5 | 6 | ![GitHub stars](https://img.shields.io/github/stars/Benature/WordReview?style=flat) 7 | ![GitHub stars](https://img.shields.io/github/forks/Benature/WordReview?style=flat) 8 | ![GitHub issues](https://img.shields.io/github/issues/Benature/WordReview) 9 | ![GitHub closed issues](https://img.shields.io/github/issues-closed/Benature/WordReview) 10 | ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/Benature/WordReview) 11 | 12 | 13 | 14 | 15 | Django + MySQL + Pug + JS 16 | 17 | - Python 3.7+ 18 | - Django 3 19 | - Mysql 8 / sqlite 3 20 | 21 | --- 22 | 23 | - DEMO 24 | - 二月的时候简单录了一个 DEMO 视频,上传到了[B 站](https://mp.weixin.qq.com/s/zOmpevAUafFY5kPGYr65uA),欢迎康康。 25 | - 还有一个[在线试玩](https://benature.github.io/WordReview/),可以先感受一下。 26 | _在线体验的版本对应`master`分支,现在默认显示的是`ben`分支(开发分支)_ 27 | 31 | - 资瓷一下呗 😋 32 | 如果觉得还不错的话,不如在右上方点个 stars🌟 呗( ̄ ▽  ̄)~ 33 | 如果童鞋有兴趣的话希望可以一起开发新功能呀 ٩(๑>◡<๑)۶ 34 | 35 | 36 | 37 | - 回复慢 QAQ 38 | - 由于本人精力有线,回复可能不会很及时,(或者漏看了邮件),还请见谅orz 39 | 40 | --- 41 | 42 | [前言](#前言) 43 | [安装指引](#安装) 44 | [使用说明](#使用) 45 | [问题自检](#问题自检) 46 | [更新日志](#更新日志) 47 | 48 | ## 前言 49 | 50 | 此项目主要是将`Excel背单词`方法给 App 化,更符合用户操作习惯。 51 | 第一次听说`Excel背单词`这个方法是看了[红专学姐](https://www.zhihu.com/people/you-hong-you-zhuan-ai-dang-wu-si-qing-nian)的[文章](https://zhuanlan.zhihu.com/p/100104481),后来在[B 站](https://www.bilibili.com/video/av46223252/)看到了更详细的讲解,几天后这个项目便诞生了。 52 | 53 | 第一篇[介绍推送](https://mp.weixin.qq.com/s/zOmpevAUafFY5kPGYr65uA)微信公众号「恰好恰好」上发送了,这里就先不展开讲了。 54 | 55 | 功能特性太多,写在这就太长了,新开一页写[特性说明](https://benature.notion.site/Word-Review-c7e1ab07e7d54a249f461248426dfd08)。 56 | 57 |
58 | 59 |

单词复习页

60 |

61 | 62 |

63 |
64 |

艾宾浩斯日历 & 主页

65 |

66 | 67 | 68 |

69 |
70 | 71 | _蓝条是历史记忆率,绿条是上一轮的记忆率_ 72 | _关于两种进度条的具体解释见[此处](#list-%E7%9A%84%E8%AE%B0%E5%BF%86%E7%8E%87)_ 73 | 74 |
75 | 76 | ## 安装 77 | 78 | > 对于小白可能还需要一些预备说明,请看[这里](https://benature.notion.site/Word-Review-9046ae4330ff49198c39491602064f3e) 79 | 80 | 命令行输入 81 | 82 | ```shell 83 | git clone https://github.com/Benature/WordReview.git 84 | ``` 85 | 86 | 或者点击右上角的`Clone or Download`的绿色按钮。 87 | 88 | 详细的安装指引写的有点长,请点击[这里](doc/install.md)查看,数据库初始化看[这里](doc/database_init.md)。 89 | 90 | > 如果你实在不想折腾配置的话,可以在[这里](https://github.com/Benature/WordReview/releases)直接安装可执行文件。(但不推荐) 91 | 92 | ## 使用 93 | 94 | ```shell 95 | conda activate # 小白流程不用这条命令 96 | python manage.py runserver 97 | ``` 98 | 99 | 默认情况下会自动在默认浏览器打开,开始背单词之旅吧 🤓 100 | 101 | 当您想要更新代码的时候,请 102 | 103 | ```shell 104 | git pull 105 | python manage.py makemigrations 106 | python manage.py migrate 107 | ``` 108 | 109 | 110 | ### 快捷键 111 | 112 | | 操作 | 快捷键 | 页面 | 状态 | 113 | | :--------------------: | :----------------------------------------------------------: | :------: | :--------------: | 114 | | 设为重难词 | Shift+H (Hard) | 复习页面 | 全局 | 115 | | 设为已掌握 | Shift+G (Get) | 复习页面 | 全局 | 116 | | 设为很熟悉 | Shift+F (Familiar) | 复习页面 | 全局 | 117 | | 设为太简单 | Shift+E (Easy) | 复习页面 | 全局 | 118 | | 进入笔记输入框 | N (Note) | 复习页面 | 全局 | 119 | | 跳转查看助记法(中) | T (Tips) / V (View) | 复习页面 | 全局 | 120 | | 跳转查看助记法(英) | M (Mnemonic) | 复习页面 | 全局 | 121 | | 跳转查看近义词 | S (Synonyms) | 复习页面 | 全局 | 122 | | 词卡前后切换 | <> | 复习页面 | 全局 | 123 | | List 前后切换 | Shift+<Shift+> | 复习页面 | 学习状态 | 124 | | 查看释义 | 空格 | 复习页面 | 复习状态 | 125 | | 切换至学习状态 | P (Preview) | 复习页面 | 复习状态 | 126 | | 触发重现模式 | R (Repeat) | 复习页面 | 复习状态 | 127 | | 触发输入模式(实验中) | I (Input) | 复习页面 | 复习状态 | 128 | | 我记得 | Shift+ | 复习页面 | 复习状态 | 129 | | 不认识 | Shift+ | 复习页面 | 复习状态 | 130 | | 复制`WordSand`助记法 | C (Copy) | 复习页面 | 安装 Chrome 插件 | 131 | | 跳转到日历页面 | C (Calendar) | 主页 | - | 132 | | 跳转到昨日重现 | Y (Yesterday) | 主页 | - | 133 | 134 | ### 词根词缀词源拆词渲染 135 | 136 | 1. 【推荐】等号`=`与回车作为标记符,detain 为例: 137 | 138 | ```txt 139 | de=down 140 | tain 141 | ``` 142 | 143 | 2. 以中文括号与`+`标识,temerity 为例: 144 | 145 | ```txt 146 | temer(轻率)+ity 147 | ``` 148 | 149 | ### List 的记忆率 150 | 151 | - 蓝条:历史记忆率,对 List 内单词的总记忆率取平均 152 | - 绿条:上轮记忆率,按 List 内单词的 **最新两次** 记忆情况计算平均记忆率 153 | 154 | ### 单词的`flag` 155 | 156 | - 太简单:✅ 打钩,下次背词不再出现,统计记忆率时视为 `1` 157 | - 很熟悉:☁️ 浮云,下次背词不再出现,统计记忆率时视为 `1` 158 | - 已掌握:🟢 绿灯,下次背词仍然出现,统计记忆率时视为 `1` 159 | - 重难词:⭐️ 标星 160 | 161 | --- 162 | 163 | 164 | ### 问题自检 165 | 如果遇到问题,请先查看这几处是否有报错信息 166 | - 浏览器的 Console (F12) 167 | - 启动`python manage.py runserver`的命令行 168 | 169 | 如果导入数据出现问题,请先尝试导入本仓库提供的示例数据。如果示例数据导入成功,很有可能是自定义数据哪里有误。 170 | 171 | 请先尝试根据上方得到的信息自行检索尝试,如若仍未解决,可以 issue 提出。 172 | 173 | 174 | ### 更新日志 175 | 参见 [CHANGELOG.md](./doc/CHANGELOG.md) 176 | 177 | ### 赞赏 178 | 179 | 如果觉得有帮助的话,可以赏点让孩子在饭堂多打份肉呦,感谢~ 180 | 181 | 182 |

183 | 184 |

-------------------------------------------------------------------------------- /WordReview/Makefile: -------------------------------------------------------------------------------- 1 | run: activate 2 | python manage.py runserver 3 | 4 | migrate: 5 | python manage.py makemigrations 6 | python manage.py migrate 7 | 8 | activate: 9 | conda activate word -------------------------------------------------------------------------------- /WordReview/WordReview/__init__.py: -------------------------------------------------------------------------------- 1 | from config import config 2 | if config.get('custom', 'db_type') == 'mysql': 3 | import pymysql 4 | pymysql.install_as_MySQLdb() 5 | # import pymysql 6 | # pymysql.install_as_MySQLdb() 7 | -------------------------------------------------------------------------------- /WordReview/WordReview/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for WordReview project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'WordReview.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /WordReview/WordReview/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for WordReview project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.0.3. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | import sys 15 | from config import config 16 | 17 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 18 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 19 | sys.path.insert(0, os.path.join(BASE_DIR, "apps")) 20 | 21 | # Quick-start development settings - unsuitable for production 22 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ 23 | 24 | # SECURITY WARNING: keep the secret key used in production secret! 25 | SECRET_KEY = '6-1t%wpwkpxl=an0h%2_8^n0s^xpwq+3!a^#b=6ik9n1wki9f6' 26 | 27 | # SECURITY WARNING: don't run with debug turned on in production! 28 | DEBUG = True 29 | 30 | ALLOWED_HOSTS = [] 31 | 32 | 33 | # Application definition 34 | sys.path.insert(0, os.path.join(BASE_DIR, 'apps')) 35 | INSTALLED_APPS = [ 36 | 'django.contrib.admin', 37 | 'django.contrib.auth', 38 | 'django.contrib.contenttypes', 39 | 'django.contrib.sessions', 40 | 'django.contrib.messages', 41 | 'django.contrib.staticfiles', 42 | 'sass_processor', 43 | 'apps.review', 44 | ] 45 | 46 | MIDDLEWARE = [ 47 | 'django.middleware.security.SecurityMiddleware', 48 | 'django.contrib.sessions.middleware.SessionMiddleware', 49 | 'django.middleware.common.CommonMiddleware', 50 | 'django.middleware.csrf.CsrfViewMiddleware', 51 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 52 | 'django.contrib.messages.middleware.MessageMiddleware', 53 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 54 | ] 55 | 56 | ROOT_URLCONF = 'WordReview.urls' 57 | 58 | TEMPLATES = [ 59 | { 60 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 61 | 'DIRS': [ 62 | os.path.join(BASE_DIR, "templates") 63 | ], 64 | # 'APP_DIRS': True, 65 | 'OPTIONS': { 66 | 'context_processors': [ 67 | 'django.template.context_processors.debug', 68 | 'django.template.context_processors.request', 69 | 'django.contrib.auth.context_processors.auth', 70 | 'django.contrib.messages.context_processors.messages', 71 | ], 72 | # PyPugJS part: 73 | 'loaders': [ 74 | ('pypugjs.ext.django.Loader', ( 75 | 'django.template.loaders.filesystem.Loader', 76 | 'django.template.loaders.app_directories.Loader', 77 | )) 78 | ], 79 | 'builtins': [ 80 | 'pypugjs.ext.django.templatetags', 81 | ], 82 | }, 83 | }, 84 | ] 85 | 86 | WSGI_APPLICATION = 'WordReview.wsgi.application' 87 | 88 | 89 | # Database 90 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases 91 | 92 | db_type = config.get('custom', 'db_type') 93 | if db_type == 'mysql': 94 | DATABASES = { 95 | 'default': { 96 | 'ENGINE': 'django.db.backends.mysql', 97 | 'NAME': config.get('mysql', 'mysql_name', fallback='word_db'), 98 | 'USER': config.get('mysql', 'mysql_user', fallback='word_user'), 99 | 'PASSWORD': config.get('mysql', 'mysql_password', fallback='word2020'), 100 | 'HOST': config.get('mysql', 'mysql_host', fallback='localhost'), 101 | 'PORT': config.get('mysql', 'mysql_port', fallback=''), 102 | 'OPTIONS': {'charset': 'utf8mb4'}, 103 | } 104 | } 105 | elif db_type == 'sqlite': 106 | DATABASES = { 107 | 'default': { 108 | 'ENGINE': 'django.db.backends.sqlite3', 109 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 110 | } 111 | } 112 | else: 113 | raise ValueError(f'请选择正确的数据库:`mysql`、`sqlite`,而非{db_type}') 114 | 115 | # Password validation 116 | # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators 117 | 118 | AUTH_PASSWORD_VALIDATORS = [ 119 | { 120 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 121 | }, 122 | { 123 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 124 | }, 125 | { 126 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 127 | }, 128 | { 129 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 130 | }, 131 | ] 132 | 133 | 134 | # Internationalization 135 | # https://docs.djangoproject.com/en/3.0/topics/i18n/ 136 | 137 | LANGUAGE_CODE = 'zh-hans' 138 | 139 | TIME_ZONE = 'Asia/Shanghai' 140 | 141 | USE_I18N = True 142 | 143 | USE_L10N = True 144 | 145 | USE_TZ = True 146 | 147 | 148 | # Static files (CSS, JavaScript, Images) 149 | # https://docs.djangoproject.com/en/3.0/howto/static-files/ 150 | 151 | STATIC_URL = '/static/' 152 | STATICFILES_DIRS = [ 153 | os.path.join(BASE_DIR, "static"), 154 | ] 155 | 156 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticsfile') 157 | SASS_PROCESSOR_ROOT = STATIC_ROOT 158 | STATICFILES_FINDERS = [ 159 | 'django.contrib.staticfiles.finders.FileSystemFinder', 160 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 161 | 'sass_processor.finders.CssFinder', 162 | ] 163 | -------------------------------------------------------------------------------- /WordReview/WordReview/urls.py: -------------------------------------------------------------------------------- 1 | """WordReview URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import static 17 | from django.contrib import admin 18 | from django.urls import path, include 19 | from django.views.generic.base import RedirectView 20 | from WordReview import settings 21 | 22 | urlpatterns = [ 23 | path('admin/', admin.site.urls), 24 | path('', include('apps.review.urls')), 25 | path(r'favicon.ico', RedirectView.as_view( 26 | url='/static/media/vocabulary.png')), 27 | 28 | ] 29 | urlpatterns += static.static(settings.STATIC_URL, 30 | document_root=settings.STATIC_ROOT) 31 | -------------------------------------------------------------------------------- /WordReview/WordReview/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for WordReview project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'WordReview.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /WordReview/apps/review/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/WordReview/apps/review/__init__.py -------------------------------------------------------------------------------- /WordReview/apps/review/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /WordReview/apps/review/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ReviewConfig(AppConfig): 5 | name = 'review' 6 | -------------------------------------------------------------------------------- /WordReview/apps/review/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/WordReview/apps/review/migrations/__init__.py -------------------------------------------------------------------------------- /WordReview/apps/review/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | import django.utils.timezone as timezone 3 | 4 | import uuid 5 | 6 | flag_choices = ( 7 | (1, '太简单'), 8 | (0, 'default'), 9 | (-1, '重难词'), 10 | ) 11 | 12 | 13 | class Review(models.Model): 14 | '''复习单词表''' 15 | word = models.CharField('英文单词', max_length=50, unique=False) 16 | # mean = models.CharField('中文释义', max_length=500, default='') 17 | total_num = models.IntegerField('复习总次数', default=0) 18 | forget_num = models.IntegerField('忘记次数', default=0) 19 | rate = models.FloatField( 20 | '单词遗忘率', default=-1, null=False) 21 | LIST = models.IntegerField('list', default=0) 22 | UNIT = models.IntegerField('unit', default=0) 23 | INDEX = models.IntegerField('index', default=0) 24 | BOOK = models.CharField('单词书', max_length=20, default='') 25 | history = models.CharField('记忆历史', max_length=100, 26 | default='') # 10100101 27 | flag = models.IntegerField('tag', default=0, choices=flag_choices) 28 | 29 | class Meta: 30 | db_table = 'review' 31 | ordering = ('LIST', 'UNIT', 'INDEX') 32 | 33 | 34 | class Books(models.Model): 35 | '''单词书清单''' 36 | index_choices = ( 37 | (0, '从0开始'), 38 | (1, '从1开始'), 39 | ) 40 | BOOK = models.CharField( 41 | '单词书', max_length=20, default='Unknow_Book', unique=True) 42 | BOOK_zh = models.CharField( 43 | '单词书中文', max_length=20, default='未知书本', unique=True) 44 | BOOK_abbr = models.CharField( 45 | '单词书简写', max_length=20, default='❔') 46 | uuid = models.UUIDField( 47 | 'uuid', default=uuid.uuid4, editable=False, unique=True) 48 | create_time = models.DateTimeField( 49 | '创建时间', default=timezone.now, editable=False) 50 | begin_index = models.IntegerField('列表开始是1还是0', default=0) 51 | hide = models.BooleanField('是否隐藏', default=False) 52 | 53 | class Meta: 54 | db_table = 'books' 55 | ordering = ('create_time',) 56 | 57 | 58 | class BookList(models.Model): 59 | '''单词书的 List 信息''' 60 | modify_time = models.DateTimeField('上次修改时间', auto_now=True) 61 | BOOK = models.CharField('单词书', max_length=20, default='UnknowBook') 62 | LIST = models.IntegerField('list', default=-1) 63 | # last_review_date = models.CharField('上次复习时间(艾宾浩斯安排)', max_length=10, default='') 64 | review_dates = models.CharField( 65 | '所有复习日期(仅记录艾宾浩斯复习曲线时间)', max_length=100, default='') 66 | review_dates_plus = models.CharField( 67 | '自愿复习日期(非艾宾浩斯复习安排)', max_length=1000, default='') 68 | list_uuid = models.UUIDField( 69 | 'uuid', default=uuid.uuid4, editable=False, unique=True) 70 | list_rate = models.FloatField('表记忆率', default=-1) 71 | recent_list_rate = models.FloatField('近期表记忆率', default=-1) 72 | review_word_counts = models.CharField( 73 | 'list 内单词复习次数(分号分隔)set', max_length=100, default='') 74 | word_num = models.IntegerField('list 内的单词数目', default=-1) 75 | ebbinghaus_counter = models.IntegerField('艾宾浩斯复习次数', default=0) 76 | unlearned_num = models.IntegerField('仍需复习单词数', default=-1) 77 | 78 | class Meta: 79 | db_table = 'book_list' 80 | ordering = ('BOOK', 'LIST', 'modify_time') 81 | 82 | 83 | class Words(models.Model): 84 | '''单纯的单词表''' 85 | modify_time = models.DateTimeField('上次修改时间', auto_now=True) 86 | word = models.CharField('英文单词', max_length=50, unique=True) 87 | mean = models.CharField('中文释义', max_length=500, default='') 88 | note = models.CharField('记忆法', max_length=200, default='') 89 | total_num = models.IntegerField('复习总次数', default=0) 90 | forget_num = models.IntegerField('忘记次数', default=0) 91 | last_forget_num = models.IntegerField('上次复习错误次数', default=0) 92 | sentence = models.CharField('例句', default='', max_length=800) 93 | rate = models.FloatField( 94 | '单词遗忘率', default=-1, null=False) 95 | history = models.CharField('记忆历史', max_length=100, 96 | default='') # 10100101 97 | flag = models.IntegerField('tag', default=0, choices=flag_choices) 98 | webster = models.BooleanField('是否被WebsterBuilder收录', default=False) 99 | mnemonic = models.CharField('助记法', max_length=500, default='') 100 | phonetic = models.CharField('音标', max_length=50, default='') 101 | antonym = models.CharField('反义词', max_length=500, default='') 102 | synonym = models.CharField('近义词', max_length=800, default='') 103 | derivative = models.CharField('派生词', max_length=300, default='') 104 | 105 | class Meta: 106 | db_table = 'words' 107 | ordering = ('word',) 108 | -------------------------------------------------------------------------------- /WordReview/apps/review/src/init_db.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from pandas import read_excel 3 | # import pandas as pd 4 | import config 5 | 6 | # read_excel = pd.read_excel 7 | # bookName = config.BOOK # 请用英文 8 | # List_begin_num = config.begin_index # 或 1,看你的 List 是从 0 开始还是 1 开始 9 | 10 | ''' 11 | 如果是 excel/csv,你应该有以下列 12 | word 单词 13 | mean 中文 14 | List 15 | Unit 16 | Index 17 | ''' 18 | 19 | # path = config.excel_path 20 | # df = read_excel(path) 21 | 22 | 23 | def check_df(df): 24 | '''检查导入文件表头是否完整或符合名字要求''' 25 | cols = df.columns 26 | checklist = ['word', 'List', 'Index'] 27 | vacant = [] 28 | for ci in checklist: 29 | if ci not in cols: 30 | vacant.append(ci) 31 | return vacant 32 | 33 | 34 | def import_word(Review, df, bookName): 35 | '''导入 df 到 Review 数据库中''' 36 | print('begin import db Review') 37 | optional_keys = ['Unit'] 38 | for key in optional_keys: 39 | if key not in df.columns: 40 | optional_keys.remove(key) 41 | print('optional keys', optional_keys) 42 | 43 | for i in range(0, (len(df))): 44 | dr = df.iloc[i] 45 | review_db = { 46 | 'word': dr['word'], 47 | 'LIST': dr['List'], 48 | # 'UNIT': dr['Unit'], 49 | 'INDEX': dr['Index'], 50 | 'BOOK': bookName, 51 | } 52 | for key in optional_keys: 53 | review_db[key.upper()] = dr[key] 54 | print(i, dr['word']) 55 | 56 | new_word = Review.objects.create(**review_db) 57 | new_word.save() 58 | 59 | print('finish import db Review') 60 | 61 | 62 | def init_db_booklist(BookList, Review, bookName, List_begin_num): 63 | '''导入单词书 List 信息''' 64 | print('begin import db BookList') 65 | for l in range(List_begin_num, List_begin_num + len(set(Review.objects.filter(BOOK=bookName).values_list('LIST')))): 66 | ld = Review.objects.filter(BOOK=bookName, LIST=l) # list data 67 | if len(ld) == 0: 68 | print(f"List{l} has no content") 69 | continue 70 | print(l) 71 | # rate = sum([r[0] if r[0] is not None else 1 for r in ld.values_list('rate') 72 | # ]) / len(ld) * 1 73 | # rate = 1 - rate if rate != 0.0 else 0 74 | data = { 75 | 'LIST': l, 76 | 'BOOK': bookName, 77 | # 'list_rate': rate, 78 | 'word_num': len(ld), 79 | # 'review_word_counts': ';'.join( 80 | # set([str(t[0]) for t in ld.values_list('total_num')])), 81 | } 82 | BookList.objects.create(**data) 83 | print('finish import db BookList') 84 | 85 | 86 | def init_db_words(Review, Words, df): 87 | print('begin import db Words') 88 | # path = config.excel_path 89 | # df = read_excel(path) 90 | optional_keys = ['sentence', 'mnemonic', 91 | 'phonetic', 'antonym', 'synonym', 'derivative'] 92 | for key in optional_keys[::-1]: 93 | if key not in df.columns.tolist(): 94 | optional_keys.remove(key) 95 | print('optional keys', optional_keys) 96 | 97 | for i in range(0, (len(df))): 98 | dr = df.iloc[i] 99 | try: 100 | word = Words.objects.get(word=dr['word']) 101 | print('skip because word already exists', dr['word']) 102 | continue 103 | except: 104 | data = { 105 | 'word': dr['word'], 106 | 'mean': dr['mean'], 107 | } 108 | for key in optional_keys: 109 | data[key] = dr[key] 110 | word = Words.objects.create(**data) 111 | print(word.word) 112 | word.save() 113 | print('finish import db Words') 114 | 115 | 116 | def init_db_books(Books, BOOK, BOOK_zh, BOOK_abbr, begin_index, hide=False): 117 | print('begin import db Books') 118 | data = { 119 | 'BOOK': BOOK, 120 | 'BOOK_zh': BOOK_zh, 121 | 'BOOK_abbr': BOOK_abbr, 122 | 'begin_index': begin_index, 123 | 'hide': hide, 124 | } 125 | Books.objects.create(**data).save() 126 | print('finish import db Books') 127 | 128 | 129 | def init_db(BOOK, BOOK_zh, BOOK_abbr, begin_index, excel_path, Books, Review, BookList, Words): 130 | df = read_excel(excel_path) 131 | vacant = check_df(df) 132 | if len(vacant) != 0: 133 | raise Exception(f"缺表头: {vacant}") 134 | init_db_words(Review, Words, df) 135 | import_word(Review, df, BOOK) 136 | init_db_booklist(BookList, Review, BOOK, begin_index) 137 | init_db_books(Books, BOOK, BOOK_zh, BOOK_abbr, begin_index) 138 | 139 | 140 | def update_db(Words): 141 | fail = [] 142 | df = pd.read_csv('data/xxxx.csv') 143 | df = df.fillna('') 144 | for i, data in enumerate(df.iloc): 145 | # if i < 2500: 146 | # continue 147 | print(i, data['word'], end='|') 148 | # break 149 | try: 150 | word = Words.objects.get(word=data['word']) 151 | except: 152 | print(data['word']) 153 | print('\nunfound word', data['word']) 154 | fail.append(data['word']) 155 | # break 156 | continue 157 | word.antonym = data['antonym'] 158 | word.synonym = data['synonyms'] 159 | word.derivative = data['derivative'] 160 | word.save() 161 | print(word.word) 162 | # break 163 | print('fail counts', len(fail), '/', len(df)) 164 | print(set(fail)) 165 | -------------------------------------------------------------------------------- /WordReview/apps/review/src/spider.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup as bs 3 | import re 4 | 5 | 6 | def crawl_other_dict(word, url): 7 | '''crawl data from other dictionary sites''' 8 | if url == "http://dict.cn/mini.php": 9 | try: 10 | soup = bs(requests.get( 11 | f"http://dict.cn/mini.php?q={word}").text, 'lxml') 12 | out = re.sub(r'|', '', 13 | str(soup.select('body')[0])).strip('\n ') 14 | out = '
' + out + '
' 15 | return 200, str(out) 16 | except Exception as e: 17 | return 505, e 18 | elif url == 'http://www.wordsand.cn/lookup.asp': 19 | try: 20 | soup = bs(requests.get( 21 | f'http://www.wordsand.cn/lookup.asp?word={word}').text, 'lxml') 22 | out = re.findall(r'([\s\S]*?)<\/td>') 23 | print(out) 24 | return 200, out 25 | except Exception as e: 26 | return 505, e 27 | elif url == 'https://mnemonicdictionary.com/': 28 | try: 29 | soup = bs(requests.get( 30 | f'https://mnemonicdictionary.com/?word={word}').text, 'lxml') 31 | data = [] 32 | for card in soup.select('.card.mnemonic-card'): 33 | footers = re.findall(r'(\d+)[^\d]+?(\d+)', 34 | card.select('.card-footer')[0].text.strip())[0] 35 | data.append({ 36 | 'text': card.select('.card-text')[0].text.strip(), 37 | 'up': int(footers[0]), 38 | 'down': int(footers[1]), 39 | }) 40 | return 200, data 41 | except Exception as e: 42 | return 505, e 43 | -------------------------------------------------------------------------------- /WordReview/apps/review/templates/calendar.pug: -------------------------------------------------------------------------------- 1 | {% extends "base.pug" %} 2 | {% block title %}艾宾浩斯记忆日历{% endblock %} 3 | {% block css %} 4 | link(href="/static/css/calendar.css" rel="stylesheet") 5 | script(src="/static/js/calendar.js") 6 | {% endblock %} 7 | 8 | {% block content %} 9 | div.container.flex-column 10 | div.align-self-center 11 | table(border='1') 12 | tbody#tbMain 13 | tr 14 | th 日 15 | th 一 16 | th 二 17 | th 三 18 | th 四 19 | th 五 20 | th 六 21 | div#yesterday-mode.list-block(href="/review/review?limit=50" style="width: 35%!important; align-self: center;") 22 | a 昨日重现 23 | {% endblock %} -------------------------------------------------------------------------------- /WordReview/apps/review/templates/homepage.pug: -------------------------------------------------------------------------------- 1 | {% extends "base.pug" %} 2 | {% block title %}复习主页{% endblock %} 3 | {% block css %} 4 | script(src="/static/js/homepage.js") 5 | style 6 | | .list-block{ 7 | | width: 7rem; 8 | | padding: 10px; 9 | | box-shadow: 0 .5rem 1rem rgba(0,0,0,.15); 10 | | border-radius: .25rem!important; 11 | | text-align: center; 12 | | margin: 10px; 13 | | cursor: pointer; 14 | | } 15 | | #yesterday-mode{ 16 | | width: 35%; 17 | | padding: 10px; 18 | | box-shadow: 0 .5rem 1rem rgba(0,0,0,.15); 19 | | border-radius: .25rem!important; 20 | | text-align: center; 21 | | margin: 10px; 22 | | cursor: pointer; 23 | | align-self: center; 24 | | } 25 | | .progress{ 26 | | font-size: 10px; 27 | | height: 13px; 28 | | } 29 | | #tmpl-qotd{ 30 | | display: flex; 31 | | justify-content: center; 32 | | } 33 | | .github{ 34 | | margin: 0px 3px 35 | } 36 | {% endblock %} 37 | 38 | {% block content %} 39 | div.container.flex-column 40 | div.text-center 41 | a(style="color:black;" href="https://github.com/Benature/WordReview") 42 | i.icon-github 43 | |  Github: Benature 44 | a.text-center#github-commit(style="color:grey; font-size:10px;") 45 | p.d-flex.justify-content-center 46 | a.github(href="https://github.com/Benature/WordReview") 47 | img(alt="GitHub stars" src="https://img.shields.io/github/stars/Benature/WordReview?style=social") 48 | a.github(href="https://github.com/Benature/WordReview") 49 | img(alt="GitHub forks" src="https://img.shields.io/github/forks/Benature/WordReview?style=social") 50 | a.github(href="https://github.com/Benature/WordReview") 51 | img(src="https://camo.githubusercontent.com/efdf447dc924ab7cee9459d16fb48e2e3e4e2060/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732f42656e61747572652f576f7264526576696577" alt="GitHub issues" data-canonical-src="https://img.shields.io/github/issues/Benature/WordReview" style="height: 16px;") 52 | a.github(href="https://github.com/Benature/WordReview") 53 | img(src="https://camo.githubusercontent.com/f4156ae04961b5fa9df11f2aa21e539ee6840e6c/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732d636c6f7365642f42656e61747572652f576f7264526576696577" alt="GitHub closed issues" data-canonical-src="https://img.shields.io/github/issues-closed/Benature/WordReview" style="height: 16px;") 54 | a.github(href="https://gitter.im/WordReview/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge") 55 | img(src="https://badges.gitter.im/WordReview/community.svg" style="height: 16px;") 56 | //- a(class="github" target="_blank",rel="noopener noreferrer", href="https://camo.githubusercontent.com/422b8612f68b05f322f5b524122f9416e20400b9/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f42656e61747572652f576f7264526576696577") 57 | //- img(src="https://camo.githubusercontent.com/422b8612f68b05f322f5b524122f9416e20400b9/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f42656e61747572652f576f7264526576696577" alt="GitHub" data-canonical-src="https://img.shields.io/github/license/Benature/WordReview" style="height: 16px;") 58 | 59 | p 60 | a(style="text-align:end;" href="/calendar/") 艾宾浩斯复习日历 61 | 62 | #tmpl-qotd 63 | 64 | div#yesterday-mode.list-block(href="/review/review?limit=50" style="width: 35%!important; align-self: center;") 65 | a 昨日重现 66 | 67 | {% for d in data%} 68 | h2(style="margin-top:20px;") {{d.name}} 69 | div.d-flex.flex-wrap 70 | {% for l in d.lists %} 71 | div.list-block(href='/review/review?list={{l.i}}&book={{d.name_en}}') 72 | {% if l.index == 0 %} 73 | h4 List {{l.i|add:1}} 74 | {% else %} 75 | h4 List {{l.i}} 76 | {% endif %} 77 | div 78 | a {{l.len}} 79 | sub(style="font-size:10px; color: grey;") {{l.del_len}} 80 | a 81 | | [{{l.times}} 82 | sub {{l.plus}} 83 | | 次] 84 | div.progress(style="width:100%;") 85 | div.progress-bar(style="width:{{l.rate}}%; background-color: #7998e0;" role="progressbar" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100") 86 | | {{l.rate}}% 87 | div.progress(style="width:100%;margin-top: 3px;") 88 | div.progress-bar(style="width:{{l.recent_rate}}%; background-color: #72d4c7;" role="progressbar" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100") 89 | | {{l.recent_rate}}% 90 | {% endfor %} 91 | {% endfor %} 92 | a(style="text-align:end;margin-top:30px" href="/import/") 我要导入新的单词书 93 | hr 94 | div.text-center(style="margin-bottom: 130px;") 95 | p 96 | a(style="color:black;" href="https://github.com/Benature/WordReview/issues") 97 | i.icon-github 98 | |  意见反馈 99 | p(style="color:grey; font-size:14px;") 100 | a(style="color:grey; font-size:14px;" href="https://t.me/joinchat/IEhuIhx4UJKf_ZK-46mbNw") Telegram 101 | a  /  102 | //- p(style="color:grey; font-size:14px;") 103 | a(style="color:grey;" href="https://join.slack.com/t/word-review/shared_invite/zt-f2hnv9v9-rW_DV0y7fsAyFQFsJwOFlg") Slack 104 | a(style="font-size:10px;") (7.13过期) 105 | a  /  106 | a(style="color:grey; font-size:14px;" href="https://gitter.im/WordReview/community") Gitter 107 | a  /  108 | a(style="color:grey; font-size:14px;" href="https://discord.gg/6sE32Jh") Discord 109 | a.text-center(style="color:grey; font-size:10px;" href="https://github.com/Benature/WordReview") 如果能帮到您,希望可以在 GitHub 点个 Star~🌟 也是对我莫大的鼓励,不胜感激!🙇‍♂️ 110 | {% endblock %} -------------------------------------------------------------------------------- /WordReview/apps/review/templates/import_db.pug: -------------------------------------------------------------------------------- 1 | {% extends "base.pug" %} 2 | {% block title %}导入数据库{% endblock %} 3 | {% block css %} 4 | script 5 | | $(function () { 6 | | $('#submit-btn').on('click', function (e) { 7 | | //$('#submit-btn').addClass('d-none'); 8 | | $('#wait').text('导入数据成功后将自动跳转') 9 | | }) 10 | | }) 11 | {% endblock %} 12 | 13 | {% block content %} 14 | 15 | div.d-flex.container.justify-content-center.flex-column 16 | h3(style="margin-bottom:20px;text-align: center;") 导入数据库 17 | {% if message %} 18 | div.alert.alert-warning 19 | | 您的输入有问题😯(报错详见命令行) 20 | | {{ message }} 21 | {% endif %} 22 | form(method="post" action='/import/')#pwdForm.d-flex.flex-column 23 | {% csrf_token %} 24 | div.form-group 25 | label 单词本的名字(请用英文,不带空格) 26 | input(type="text" name="BOOK" placeholder="eg: CET6_green" required='true').form-control 27 | div.form-group 28 | label 单词本的中文名 29 | input(type="text" name="BOOK_zh" placeholder="eg: 新东方六级绿皮书" required='true').form-control 30 | div.form-group 31 | label 单词本的缩写(用于日历显示,建议一个英文大写字符) 32 | input(type="text" name="BOOK_abbr" placeholder="eg: G" required='true').form-control 33 | div.form-group 34 | label 单词本 list、unit、index 的序号从 0 开始还是从 1 开始 35 | input(type="text" name="begin_index" placeholder="输入 0 或 1" required='true').form-control 36 | div.form-group 37 | label 单词 excel 文件路径(绝对路径) 38 | input(type="text" name="excel_path" placeholder="/file/path/to/excel.xls" required='true').form-control 39 | div.justify-content-center.d-flex 40 | label 41 | p#wait 42 | button(type='submit').btn.btn-primary#submit-btn 开始导入数据 43 | a(href="https://github.com/Benature/WordReview/blob/ben/doc/database_init.md#%E7%BD%91%E9%A1%B5%E5%AF%BC%E5%85%A5") 对导入的设置内容有疑惑?点这里 44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /WordReview/apps/review/templates/review.pug: -------------------------------------------------------------------------------- 1 | {% extends "base.pug" %} 2 | {% block title %}单词复习{% endblock %} 3 | {% block css %} 4 | {% load sass_tags %} 5 | script(src="/static/js/echarts.min.js") 6 | script(src="/static/js/thirdParty/hotkeys.min.js") 7 | link(href="{% sass_src 'scss/review.scss' %}" rel="stylesheet" type="text/css") 8 | script(src="/static/js/review.js") 9 | {% endblock %} 10 | 11 | {% block content %} 12 | nav.navbar.navbar-light.bg-light.sticky-top 13 | a.navbar-brand(href="/review/") 14 | img(src="/static/media/muyi.png" height="30" class="d-inline-block align-top" alt="") 15 | |   Word Review 16 | div.d-flex.flex-row 17 | div.d-flex.flex-row.justify-content-center(style="margin-right: 40px;") 18 | input#jump-index.form-control 19 | button#btn-quick-jump.btn 跳转 20 | div.navbar-nav.ml-auto.flex-row 21 | li.nav-item(style="margin-right:20px;") 22 | a.nav-link.repeat.cur-p.enabled 重现模式:开 23 | li.nav-item 24 | a.nav-link.sort-array 顺序 25 | li.nav-item 26 | a.nav-link.sort-array 乱序 27 | li.nav-item 28 | a.nav-link.sort-array 记忆序 29 | li.nav-item 30 | a.nav-link.sort-array 次数序 31 | div.progress.sticky#nav-progress(style="width:100%;height:3px;") 32 | div.progress.sticky#review-progress(style="width:100%;height: 2px; margin-top:0px;") 33 | a(href="https://github.com/Benature/WordReview#%E5%BF%AB%E6%8D%B7%E9%94%AE" style="margin-left:20px;color:grey;font-size:10px;") 34 | .icon-question-sign 35 | |  快捷键 36 | 37 | div.container.flex-column 38 | div.align-self-center 39 | //- div.d-flex.justify-content-center(style="text-align: center") 40 | //- div.side-card 41 | //- //- div(style="width:300px;margin: 0 36px;").d-flex.justify-content-center 42 | //- //- div#tmpl-word 43 | //- div.side-card 44 | .d-flex.flex-row 45 | div 46 | div.d-flex.flex-row 47 | //- left side card 48 | div.side-card.d-flex.flex-column.justify-content-between 49 | div 50 | div#echarts-left.hide 51 | div 52 | div#echarts-bottom 53 | 54 | //- middle card 55 | div 56 | div(style="width:300px;margin: 0 36px;").d-flex.justify-content-center.flex-column 57 | div#tmpl-word.text-center(style="min-height: 50px;") 58 | div#tmpl-phonetic 59 | div#tmpl-index 60 | div.d-flex.flex-row 61 | div.align-self-center.btn-jump#jump-back « 62 | div 63 | div.d-flex.flex-row.justify-content-between 64 | div#tmpl-last-word 65 | div(style="margin-right: 20px;") 66 | div.icon-flags.icon-star-div 67 | i.icon-star.icon-disabled 68 | div.icon-flags.icon-circle-div 69 | i.icon-circle.icon-disabled 70 | div.icon-flags.icon-cloud-div 71 | i.icon-cloud.icon-disabled 72 | div.icon-flags.icon-ok-div 73 | i.icon-ok.icon-disabled 74 | div#meaning-box.rounded.shadow 75 | div.align-self-center#tmpl-content.hide 76 | div.align-self-center.btn-jump#jump-forward(style="margin-right: 5px;") » 77 | 78 | //- progress bar 79 | div.progress-div 80 | div.progress(style="width:250px; background-color: palevioletred") 81 | div.progress-bar.bg-dodgerblue#progress-bar-word(role="progressbar" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100") 82 | a#tmpl-progress 83 | div#tmpl-total-num 84 | div.d-flex.justify-content-center 85 | div.btn-div 86 | button.btn.btn-secondary.shadow#btn-forget.jump-btn 不认识 87 | button.btn.btn-primary.shadow#btn-remember.jump-btn 我记得 88 | 89 | //- right side card: NOTE 90 | div.side-card.justify-content-center 91 | div(style="font-size:12px;") 92 | #tmpl-derivative.hide 93 | #tmpl-antonym.hide 94 | #tmpl-synonym.hide 95 | hr 96 | #active-note 97 | div#tmpl-mnemonic.hide 98 | div(style="color:grey;text-align:center").hide.d-none 99 | hr 100 | a Note 101 | div#tmpl-break-word.hide 102 | div#tmpl-note.d-n-note.hide(contentEditable="true") 103 | //- textarea#tmpl-note.form-control.d-n-note.hide 104 | //- bottom 105 | div#bottom-box.d-flex.flex-row.justify-content-center(style="margin-top:20px;") 106 | div.hide#tmpl-sentence(style="max-width: 400px;") 107 | div(style="max-width: 400px;") 108 | div.hide#word-sand(style="font-size:12px;max-width: 400px;") 109 | //- iframe(stc="http://baidu.comdict.cn/mini.php?q=idle") 110 | div(style="width: 220px; margin-left: 20px;") 111 | #word-mnemonic.hide 112 | 113 | 114 | input#clipboard(style="position: absolute;top: 0;") 115 | 116 | div#bd-tts 117 | {% endblock %} -------------------------------------------------------------------------------- /WordReview/apps/review/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /WordReview/apps/review/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | path('', views.homepage, name='index'), 6 | path('review/review/', views.review, name='复习单词'), 7 | path('review/homepage/', views.homepage, name='复习主页'), 8 | path('review/', views.homepage, name='复习主页'), 9 | path('calendar/', views.calendar, name='艾宾浩斯日历'), 10 | path('temp/', views.temp, name='temp'), 11 | path('import/', views.import_db, name='导入数据库'), 12 | 13 | # 接口 14 | # GET 15 | path('review/get_word', views.get_word, name='获取单词'), 16 | path('review/get_calendar_data', views.get_calendar_data, name='获取日历渲染数据'), 17 | # POST 18 | path('review/review_a_word', views.review_a_word, name='获取单词'), 19 | path('review/review_list_finish', views.review_lists, name='复习结束'), 20 | path('review/update_word_flag', views.update_word_flag, name='更新单词flag'), 21 | path('review/update_note', views.update_note, name='更新单词note'), 22 | path('review/spider/other_dict', views.spider_other_dict, 23 | name='API_spider_other_dict'), 24 | # path('import-database/', views.import_db, name='导入数据库'), 25 | ] 26 | -------------------------------------------------------------------------------- /WordReview/apps/review/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect, HttpResponse 2 | from django.http import JsonResponse 3 | from django.views.decorators.csrf import csrf_exempt 4 | from django.db.models import Q 5 | 6 | from apps.review.models import Review, BookList, Words, Books 7 | 8 | from apps.src.util import ormToJson, valueList 9 | import config 10 | from apps.review.src.init_db import init_db, update_db 11 | from apps.review.src.spider import crawl_other_dict 12 | 13 | from datetime import datetime, timedelta, date 14 | 15 | import traceback 16 | 17 | Delay_Hours = 4 18 | 19 | EBBINGHAUS_DAYS = [0, 1, 2, 4, 7, 15, 30] 20 | 21 | 22 | def index(request): 23 | return render(request, "review.pug") 24 | 25 | 26 | def temp(request): 27 | # out = Words.objects.filter(word__icontains='abandon') 28 | # for w in out: 29 | # print(w.word, ".") 30 | # print(w.word.count(' ')) 31 | # print(w.id) 32 | # print(out) 33 | update_db(Words) 34 | return render(request, "homepage.pug") 35 | 36 | 37 | def import_db(request): 38 | if request.method == 'POST': 39 | post = request.POST 40 | print(post) 41 | BOOK = post.get('BOOK') 42 | BOOK_zh = post.get('BOOK_zh') 43 | BOOK_abbr = post.get('BOOK_abbr') 44 | excel_path = post.get('excel_path') 45 | 46 | try: 47 | begin_index = int(post.get('begin_index')) 48 | if begin_index not in [0, 1]: 49 | return render(request, "import_db.pug", 50 | {'message': '请输入 0 或 1!'}) 51 | except: 52 | return render(request, "import_db.pug", {'message': '请输入 0 或 1!'}) 53 | print(BOOK, BOOK_zh, BOOK_abbr, begin_index, excel_path) 54 | 55 | try: 56 | init_db(BOOK, BOOK_zh, BOOK_abbr, begin_index, excel_path, Books, 57 | Review, BookList, Words) 58 | except Exception as e: 59 | print(traceback.format_exc()) 60 | return render(request, "import_db.pug", {'message': e}) 61 | return redirect('/review') 62 | return render(request, "import_db.pug") 63 | 64 | 65 | @csrf_exempt 66 | def review_lists(request): 67 | '''接口:复习完成 list,更新 book_list''' 68 | post = request.POST 69 | today = datetime.now() - timedelta(hours=Delay_Hours) # 熬夜情况 70 | today_str = today.strftime('%Y-%m-%d') 71 | if post.get('yesterday_mode') == 'true': 72 | LIST = 0 73 | while True: 74 | print(LIST) 75 | try: 76 | book_list = BookList.objects.get(BOOK='WORD_REVIEW', LIST=LIST) 77 | except BookList.DoesNotExist: 78 | if LIST == 0: # 这个 BOOK 还没建 79 | from apps.review.src.init_db import init_db_books 80 | init_db_books(Books, 81 | 'WORD_REVIEW', 82 | '昨日重现自主复习', 83 | '😇', 84 | 0, 85 | hide=True) 86 | book_list = BookList.objects.create(BOOK='WORD_REVIEW', 87 | LIST=LIST) 88 | dates = book_list.review_dates_plus 89 | if today_str != dates.split(';')[-1]: 90 | book_list.review_dates_plus = (dates + ';' + 91 | today_str).strip(';') 92 | book_list.save() 93 | print(book_list.review_dates_plus) 94 | break 95 | else: 96 | LIST += 1 97 | LISTS = [] 98 | else: 99 | LISTS = [int(i) for i in post.get('list').split('-')] 100 | if len(LISTS) == 2: 101 | LISTS = list(range(LISTS[0], LISTS[1] + 1)) 102 | BOOK = post.get('book') 103 | 104 | msg = 'done' 105 | status = 200 106 | for LIST in LISTS: 107 | try: 108 | # list data: where flag < 1 109 | ld = Review.objects.filter(BOOK=BOOK, LIST=LIST, 110 | flag__lt=1) # list data 111 | # list data passed: where flag > 0 112 | ld_pass = Review.objects.filter(BOOK=BOOK, LIST=LIST, 113 | flag__gt=0) # list data 114 | L_db = BookList.objects.get(BOOK=BOOK, LIST=LIST) 115 | except Exception as e: 116 | msg = f'获取数据异常:{e}' 117 | status = 501 118 | break 119 | 120 | L_db.word_num = len(ld) + len(ld_pass) 121 | 122 | # 背过的单词进度 123 | list_rate = len([r for r in ld.values_list('rate') if r[0] != -1 124 | ]) / L_db.word_num 125 | list_rate = 1 - list_rate if list_rate != 0.0 else 0 126 | 127 | if list_rate == 0 and len(ld) > 0: 128 | status = 404 129 | msg = '你好像还没背过这个 List 诶 😳' 130 | continue 131 | 132 | L_db.unlearned_num = len(ld) 133 | L_db.review_word_counts = ';'.join( 134 | set([str(t[0]) for t in ld.values_list('total_num')])) 135 | 136 | L_db.list_rate = list_rate 137 | # 计算近期记忆率 138 | recent_history = '' 139 | for word in ld: 140 | recent_history += word.history[-2:] 141 | L_db.recent_list_rate = recent_history.count('1') / len(recent_history) 142 | 143 | # 艾宾浩斯时间处理 144 | if 0 < L_db.ebbinghaus_counter < len(EBBINGHAUS_DAYS): 145 | ebbinghaus_counter = L_db.ebbinghaus_counter 146 | should_next_date = datetime.strptime( 147 | L_db.review_dates.split(';')[-1], '%Y-%m-%d') + timedelta( 148 | days=EBBINGHAUS_DAYS[ebbinghaus_counter]) 149 | if (today - should_next_date).days >= 0: 150 | print(should_next_date) 151 | # 今天 不早于 理论下一天 152 | L_db.ebbinghaus_counter += 1 153 | L_db.review_dates += ';' + today_str 154 | L_db.last_review_date = today_str 155 | elif today_str != L_db.review_dates_plus.split(';')[-1]: 156 | # 自愿复习 157 | L_db.review_dates_plus += ';' + today_str \ 158 | if L_db.review_dates_plus != "" else today_str 159 | elif L_db.ebbinghaus_counter == 0: 160 | L_db.last_review_date = today_str 161 | L_db.ebbinghaus_counter = 1 162 | L_db.review_dates = today_str 163 | else: 164 | # 已完成艾宾浩斯一周目 165 | L_db.ebbinghaus_counter = 0 166 | print('这个 list 背完了') 167 | 168 | try: 169 | L_db.save() 170 | except Exception as e: 171 | msg = f'保存数据异常:{e}' 172 | status = 502 173 | break 174 | # data = {'msg': msg, 'status': 200} 175 | # except Exception as e: 176 | # data = {'msg': e, 'status': 500} 177 | data = {'msg': msg, 'status': status} 178 | return JsonResponse(data) 179 | 180 | 181 | @csrf_exempt 182 | def update_note(request): 183 | '''接口:更新单词note''' 184 | post = request.POST 185 | msg = 'done' 186 | status = 200 187 | try: 188 | print(post) 189 | word = Words.objects.get(word=post.get('word')) 190 | word.note = post.get('note') 191 | word.save() 192 | except Exception as e: 193 | msg = e 194 | status = 501 195 | data = {'msg': msg, 'status': status} 196 | return JsonResponse(data) 197 | 198 | 199 | @csrf_exempt 200 | def update_word_flag(request): 201 | '''接口:更新单词flag''' 202 | post = request.POST 203 | msg = 'done' 204 | status = 200 205 | try: 206 | if post.get('yesterday_mode') == 'true': 207 | words = [Words.objects.get(word=post.get('word'))] 208 | else: 209 | words = [Words.objects.get(word=post.get('word'))] 210 | if post.get('flag') == '0' and int(post.get('last_flag')) > 0: 211 | # 如果是从正向标签退回默认,则对数据库所有 list 操作 212 | words += [ 213 | rw for rw in Review.objects.filter(word=post.get('word')) 214 | ] 215 | else: 216 | words.append( 217 | Review.objects.get(word=post.get('word'), 218 | LIST=post.get('list'), 219 | BOOK=post.get('book'))) 220 | for word in words: 221 | word.flag = post.get('flag') 222 | word.save() 223 | # if post.get('flag') == 0: 224 | # Review.objects.filter(word=post.get( 225 | # 'word')).update(flag=post.get('flag')) 226 | except Exception as e: 227 | msg = e 228 | status = 501 229 | data = {'msg': msg, 'status': status} 230 | return JsonResponse(data) 231 | 232 | 233 | @csrf_exempt 234 | def spider_other_dict(request): 235 | '''API: spider to crawl http://dict.cn/mini.php''' 236 | status, data = crawl_other_dict(request.POST.get('word'), 237 | request.POST.get('url')) 238 | return JsonResponse({'status': status, 'data': data}) 239 | 240 | 241 | @csrf_exempt 242 | def review_a_word(request): 243 | '''接口:在数据库更新单词记忆情况''' 244 | post = request.POST 245 | try: 246 | word = Words.objects.get(word=post.get('word')) 247 | if post.get('repeat') == 'true' or post.get( 248 | 'yesterday_mode') == 'true': 249 | word_dbs = [word] 250 | else: 251 | word_in_list = Review.objects.filter(word=post.get('word'), 252 | BOOK=post.get('book'), 253 | LIST=post.get('list'))[0] 254 | word_dbs = [word, word_in_list] 255 | except Exception as e: 256 | return JsonResponse({'msg': '数据库损坏!' + e, 'status': 500}) 257 | 258 | # update database 259 | if (post.get('note') != 'false'): 260 | word.note = post.get('note') 261 | word.last_forget_num = post.get('last_forget_num') 262 | 263 | for w in word_dbs: 264 | w.total_num += 1 265 | if post.get('remember') == 'true': 266 | w.history += '1' 267 | elif post.get('remember') == 'false': 268 | w.history += '0' 269 | w.forget_num += 1 270 | w.rate = word.forget_num / word.total_num 271 | w.save() 272 | data = {'msg': 'done', 'status': 200} 273 | return JsonResponse(data) 274 | 275 | 276 | def get_word(request): 277 | '''接口:获取单词''' 278 | pankeys = { 279 | 'total_num': 'panTotalNum', 280 | 'forget_num': 'panForgetNum', 281 | 'rate': 'panRate', 282 | 'history': 'panHistory', 283 | 'flag': 'panFlag', 284 | } 285 | sortType = ['乱序', '记忆序'] 286 | msg = '' 287 | mode = 'normal' 288 | 289 | BOOK = request.GET.get('book') 290 | LIST = request.GET.get('list') 291 | limit = request.GET.get('limit') 292 | print("...", limit) 293 | 294 | yesterday_mode = BOOK == '' and LIST == '' 295 | 296 | if yesterday_mode: 297 | mode = 'yesterday' 298 | day0 = datetime.now() - timedelta(days=4, hours=Delay_Hours) 299 | today = datetime.now() - timedelta(hours=Delay_Hours) 300 | date_range = [ 301 | datetime.strptime( 302 | f"{day0.year}-{day0.month}-{day0.day} {Delay_Hours}", 303 | '%Y-%m-%d %H'), 304 | datetime.strptime( 305 | f"{today.year}-{today.month}-{today.day} {Delay_Hours}", 306 | '%Y-%m-%d %H') 307 | ] 308 | list_info = Words.objects.filter( 309 | modify_time__range=date_range, last_forget_num__gt=0).order_by( 310 | "rate").order_by("-last_forget_num") 311 | msg = f"There are {len(list_info)} words that you need to review😋" 312 | list_info = list_info[:50] if limit == None else list_info[:int(limit)] 313 | 314 | else: 315 | LIST_li = [int(i) for i in LIST.split('-')] 316 | if len(LIST_li) == 1: 317 | list_info = Review.objects.filter(LIST=LIST, BOOK=BOOK, flag__lt=2) 318 | if BookList.objects.get(LIST=LIST, 319 | BOOK=BOOK).ebbinghaus_counter == 0: 320 | sortType = ['顺序'] 321 | elif len(LIST_li) == 2: 322 | list_info = Review.objects.filter(LIST__range=LIST_li, BOOK=BOOK) 323 | else: 324 | raise KeyError('LIST_li 长度异常') 325 | 326 | list_info = ormToJson(list_info) 327 | for l in list_info: 328 | l = l['fields'] 329 | try: 330 | w = l if yesterday_mode else ormToJson( 331 | [Words.objects.get(word=l['word'])])[0]['fields'] 332 | except Words.DoesNotExist: 333 | return JsonResponse({ 334 | "msg": f"Word not found:{l['word']}", 335 | 'status': 404 336 | }) 337 | 338 | for old, pan in pankeys.items(): 339 | w.update({pan: w.pop(old)}) 340 | l.update(w) 341 | 342 | yesterday = datetime.now() - timedelta(days=1, hours=Delay_Hours) 343 | recent_words = Words.objects.filter(modify_time__gt=date( 344 | yesterday.year, yesterday.month, yesterday.day)).values_list('word') 345 | 346 | data = dict(data=list_info, 347 | status=200, 348 | sort=sortType, 349 | begin_index=int(Books.objects.get( 350 | BOOK=BOOK).begin_index == 0) if BOOK != '' else 0, 351 | recent_words=[rw[0] for rw in recent_words], 352 | mode=mode, 353 | msg=msg) 354 | return JsonResponse(data) 355 | 356 | 357 | def get_calendar_data(request): 358 | '''接口:获取日历渲染数据''' 359 | books = Books.objects.all() 360 | book_info = {} 361 | for b in books: 362 | book_info[b.BOOK] = { 363 | 'abbr': b.BOOK_abbr, 364 | 'begin_index': 1 if b.begin_index == 0 else 0, 365 | } 366 | # db = BookList.objects.filter(~Q(ebbinghaus_counter=0)) 367 | db = BookList.objects.filter( 368 | Q(ebbinghaus_counter__gt=0) | ~Q(review_dates_plus='')) 369 | data = ormToJson(db) 370 | for d in data: 371 | d = d['fields'] 372 | d['abbr'] = book_info[d['BOOK']]['abbr'] 373 | d['begin_index'] = book_info[d['BOOK']]['begin_index'] 374 | 375 | data = {'data': data, 'EBBINGHAUS_DAYS': EBBINGHAUS_DAYS, 'status': 200} 376 | return JsonResponse(data) 377 | 378 | 379 | def review(request): 380 | '''页面:单词复习页''' 381 | LIST = request.GET.get('list') 382 | BOOK = request.GET.get('book') 383 | # if LIST is None or BOOK is None: 384 | # return redirect(f'/review/review?list={LIST}&book={BOOK}') 385 | return render(request, "review.pug", locals()) 386 | 387 | 388 | def calendar(request): 389 | '''页面:艾宾浩斯日历图''' 390 | return render(request, "calendar.pug") 391 | 392 | 393 | def homepage(request): 394 | '''页面:复习主页''' 395 | books = Books.objects.filter(hide=False)[::-1] 396 | dic = {} 397 | for b in books: 398 | dic[b.BOOK] = {'BOOK_zh': b.BOOK_zh, 'begin_index': b.begin_index} 399 | data = [] 400 | for BOOK, book_info in dic.items(): 401 | book = book_info['BOOK_zh'] 402 | index = book_info['begin_index'] 403 | # index = 1 if index == 0 else 0 404 | lists = sorted([ 405 | l[0] for l in ( 406 | set(Review.objects.filter(BOOK=BOOK).values_list('LIST'))) 407 | ]) 408 | list_info = [] 409 | for l in lists: 410 | try: 411 | ld = BookList.objects.get(BOOK=BOOK, LIST=l) 412 | except Exception as e: 413 | print(l, e) 414 | continue 415 | if ld.unlearned_num == -1: 416 | L = ld.word_num 417 | del_L = 0 418 | else: 419 | L = ld.unlearned_num 420 | del_L = ld.word_num - ld.unlearned_num 421 | # total = sorted([int(i) for i in ld.review_word_counts.split(';')]) 422 | plus = len(ld.review_dates_plus.split( 423 | ';')) if ld.review_dates_plus != "" else 0 424 | list_info.append( 425 | dict(i=l, 426 | len=L, 427 | del_len=del_L, 428 | rate=int(max(0, ld.list_rate) * 100), 429 | recent_rate=int(max(0, ld.recent_list_rate) * 100), 430 | times=len(ld.review_dates.split(';')) 431 | if ld.review_dates != "" else 0, 432 | plus='' if plus == 0 else '+' + str(plus), 433 | index=index)) 434 | data.append({'name': book, 'name_en': BOOK, 'lists': list_info}) 435 | 436 | return render(request, "homepage.pug", locals()) 437 | -------------------------------------------------------------------------------- /WordReview/apps/src/util.py: -------------------------------------------------------------------------------- 1 | from django.core import serializers 2 | import json 3 | 4 | 5 | def ormToJson(ormData): 6 | '''数据转换器''' 7 | jsonData = serializers.serialize("json", ormData) 8 | data = json.loads(jsonData) 9 | return data 10 | 11 | 12 | def valueList(model, key): 13 | return [name[0] for name in model.objects.values_list(key)] 14 | -------------------------------------------------------------------------------- /WordReview/config.py: -------------------------------------------------------------------------------- 1 | __all__ = ['config'] 2 | 3 | import configparser 4 | from pathlib import Path 5 | import os 6 | 7 | default_dict = { 8 | 'db_type': 'sqlite', 9 | 'auto_open_browser': 'yes', 10 | } 11 | 12 | _project_dir = Path(__file__).parent 13 | _config_file_path = _project_dir / 'config.conf' 14 | 15 | 16 | def check_config_file_exists(): 17 | if not os.path.exists(_config_file_path): 18 | from shutil import copyfile 19 | print("【步骤遗漏!】还没有复制配置文件哦...") 20 | copyfile(_project_dir / "config_sample.conf", _config_file_path) 21 | print( 22 | f'但我已经帮你复制好了:\n{_project_dir / "config_sample.conf"} -> {_config_file_path}' 23 | ) 24 | print("继续往下走吧~") 25 | 26 | 27 | check_config_file_exists() 28 | 29 | config = configparser.ConfigParser(default_dict) 30 | # print(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.conf')) 31 | config.read(_config_file_path, encoding='utf-8') 32 | -------------------------------------------------------------------------------- /WordReview/config_sample.conf: -------------------------------------------------------------------------------- 1 | # ====================================================== 2 | # 用户自定义配置区 3 | # ====================================================== 4 | [custom] 5 | # 是否自动打开浏览器 6 | auto_open_browser = yes 7 | 8 | # 使用数据库类型:`sqlite`(默认)、`mysql` 9 | db_type = sqlite 10 | 11 | # ====================================================== 12 | # 数据库使用配置 13 | # (除非你知道你在干嘛,否则请勿修改下面代码) 14 | # ====================================================== 15 | [mysql] 16 | mysql_name = 'word_db' 17 | mysql_user = 'word_user' 18 | mysql_password = 'word2020' 19 | mysql_host = 'localhost' 20 | mysql_port = '' -------------------------------------------------------------------------------- /WordReview/installer.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import platform 3 | import os 4 | from shutil import copyfile 5 | 6 | pathex = os.path.dirname(os.path.abspath(__file__)) 7 | print(pathex) 8 | spec = '''# -*- mode: python ; coding: utf-8 -*- 9 | 10 | import os 11 | block_cipher = None 12 | BASE_DIR = \'''' + pathex + '''\' 13 | 14 | a = Analysis(['manage.py'], 15 | pathex=[BASE_DIR], 16 | binaries=[], 17 | datas=[ 18 | (os.path.join(BASE_DIR, 'static'),'staticsfile'), 19 | (os.path.join(BASE_DIR, 'templates'), 'templates'), 20 | (os.path.join(BASE_DIR, 'apps'), 'apps'), 21 | #(os.path.join(BASE_DIR, 'config_sample.conf'), '.'), 22 | #(os.path.join(BASE_DIR, 'pypi/pypugjs'), 'pypugjs'), 23 | ], 24 | hiddenimports=[ 25 | 'pkg_resources.py2_warn', 26 | 'django.contrib.admin', 27 | 'django.contrib.auth', 28 | 'django.contrib.contenttypes', 29 | 'django.contrib.sessions', 30 | 'django.contrib.messages', 31 | 'django.contrib.staticfiles', 32 | 'sass_processor', 33 | 'sass_processor.apps', 34 | 'sass_processor.finders', 35 | 'pypugjs', 36 | 'pypugjs.ext.django', 37 | 'pypugjs.ext.django.templatetags', 38 | 'pandas.read_excel', 39 | 'dateutil', 40 | 'six', 41 | 'xlrd', 42 | 'requests', 43 | 'bs4', 44 | ], 45 | hookspath=[], 46 | runtime_hooks=[], 47 | excludes=[ 48 | 'pymysql', 49 | 'mysqlclient', 50 | ], 51 | win_no_prefer_redirects=False, 52 | win_private_assemblies=False, 53 | cipher=block_cipher, 54 | noarchive=False) 55 | 56 | def get_pandas_path(): 57 | import pandas 58 | pandas_path = pandas.__path__[0] 59 | return pandas_path 60 | dict_tree = Tree(get_pandas_path(), prefix='pandas', excludes=["*.pyc"]) 61 | a.datas += dict_tree 62 | a.binaries = filter(lambda x: 'pandas' not in x[0], a.binaries) 63 | ''' 64 | if True or platform.architecture()[1] == 'WindowsPE': 65 | spec += '''def get_numpy_path(): 66 | import numpy 67 | return numpy.__path__[0] 68 | dict_tree = Tree(get_numpy_path(), prefix='numpy', excludes=["*.pyc"]) 69 | a.datas += dict_tree 70 | a.binaries = filter(lambda x: 'numpy' not in x[0], a.binaries) ''' 71 | 72 | spec += ''' 73 | pyz = PYZ(a.pure, a.zipped_data, 74 | cipher=block_cipher) 75 | 76 | Key = ['mkl','libopenblas'] 77 | #Key = ['mkl', 'libopenblas', 'liblapack', 'libblas', 'libcblas'] 78 | def remove_from_list(input, keys): 79 | outlist = [] 80 | for item in input: 81 | name, _, _ = item 82 | flag = 0 83 | for key_word in keys: 84 | if name.find(key_word) > -1: 85 | flag = 1 86 | if flag != 1: 87 | outlist.append(item) 88 | return outlist 89 | a.binaries = remove_from_list(a.binaries, Key) 90 | 91 | exe = EXE(pyz, 92 | a.scripts, 93 | [], 94 | exclude_binaries=True, 95 | name='WordReview_D', 96 | debug=False, 97 | bootloader_ignore_signals=False, 98 | strip=False, 99 | upx=True, 100 | console=True ) 101 | coll = COLLECT(exe, 102 | a.binaries, 103 | a.zipfiles, 104 | a.datas, 105 | strip=False, 106 | upx=True, 107 | upx_exclude=[], 108 | name='WordReview_D') 109 | ''' 110 | 111 | spec_name = 'WordReview_D' 112 | with open(os.path.join(pathex, spec_name+'.spec'), 'w') as f: 113 | f.write(spec) 114 | 115 | res = subprocess.call( 116 | "pyinstaller --clean --noconfirm WordReview_D.spec", shell=True) 117 | if res == 1: 118 | os._exit(0) 119 | 120 | dist_path = os.path.join(pathex, f'dist/{spec_name}') 121 | copyfile(os.path.join(pathex, 'config_sample.conf'), 122 | os.path.join(dist_path, 'config.conf')) 123 | print('copy file config.conf') 124 | # with open(os.path.join(pathex, 'config_sample.conf'), 'r') as f: 125 | # conf = f.read() 126 | # with open(os.path.join(dist_path, 'config.conf'), 'w') as f: 127 | # f.write(conf) 128 | 129 | copyfile(os.path.join(pathex, 'staticsfile/scss/review.css'), 130 | os.path.join(dist_path, 'staticsfile/scss/review.css')) 131 | 132 | pug_path = os.path.join(dist_path, 'apps/review/templates/review.pug') 133 | with open(pug_path, 'r') as f: 134 | pug = f.read() 135 | with open(pug_path, 'w') as f: 136 | f.write(pug.replace( 137 | '''link(href="{% sass_src 'scss/review.scss' %}" rel="stylesheet" type="text/css")''', 138 | '''link(href="/static/scss/review.css" rel="stylesheet" type="text/css")''' 139 | ).replace( 140 | "{% load sass_tags %}", 141 | "//- {% load sass_tags %}")) 142 | 143 | # print("begin remove useless files, in order to reduce package size") 144 | 145 | # for useless in ['libblas.3.dylib', 'libcblas.3.dylib', 'liblapack.3.dylib']: 146 | # subprocess.call(f"rm {os.path.join(dist_path, useless)}", shell=True) 147 | -------------------------------------------------------------------------------- /WordReview/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | from config import config 6 | 7 | BASE_DIR = os.path.abspath(__file__) 8 | sys.path.append(os.path.join(BASE_DIR, "pypi")) 9 | 10 | if len(sys.argv) == 1: 11 | sys.argv.append('runserver') 12 | 13 | 14 | def Is_child_processing(): 15 | from multiprocessing.connection import Listener 16 | from queue import Queue 17 | from threading import Thread 18 | 19 | q = Queue() 20 | 21 | def lock_system_port(_port): 22 | nonlocal q # it's OK without this announce line 23 | try: 24 | listener = Listener(("", _port)) 25 | q.put(False) 26 | except Exception: # port be used by parent 27 | # traceback.print_exc() 28 | q.put(True) 29 | return # child don't listen 30 | 31 | while True: 32 | serv = listener.accept() # just bind the port. 33 | 34 | t = Thread(target=lock_system_port, args=(62771, )) 35 | t.daemon = True 36 | t.start() 37 | del t 38 | return q.get() 39 | 40 | 41 | def enable_browser_with_delay(argv, _t=None): 42 | '''open browser''' 43 | try: 44 | subcommand = argv[1] # manage.py runserver 45 | except IndexError: 46 | pass 47 | 48 | if subcommand == 'runserver' and '--noreload' not in argv: 49 | try: 50 | parser_port = argv[2] 51 | port_with_colon = parser_port[parser_port.index( 52 | ":"):] if ':' in parser_port else ":" + parser_port 53 | except (IndexError, ValueError): 54 | port_with_colon = ":8000" 55 | finally: 56 | import webbrowser 57 | import time 58 | if not _t: 59 | _t = 0.5 60 | time.sleep(_t) # you may no need delay, if your machine run faster 61 | # open project index page 62 | webbrowser.open_new("http://localhost" + port_with_colon) 63 | # webbrowser.open_new("http://localhost" + port_with_colon + "/app_name/") # open app index page 64 | 65 | 66 | def main(): 67 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'WordReview.settings') 68 | try: 69 | from django.core.management import execute_from_command_line 70 | except ImportError as exc: 71 | raise ImportError( 72 | "Couldn't import Django. Are you sure it's installed and " 73 | "available on your PYTHONPATH environment variable? Did you " 74 | "forget to activate a virtual environment?") from exc 75 | 76 | if Is_child_processing() and config.getboolean('custom', 77 | 'auto_open_browser'): 78 | import threading 79 | t = threading.Thread(target=enable_browser_with_delay, 80 | args=(sys.argv, 1)) 81 | t.start() 82 | del t 83 | 84 | execute_from_command_line(sys.argv) 85 | 86 | 87 | if __name__ == '__main__': 88 | main() 89 | -------------------------------------------------------------------------------- /WordReview/static/css/base.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | display: flex; 3 | justify-content: center; 4 | margin-top: 0px; 5 | } 6 | 7 | .cur-p{ 8 | cursor: pointer; 9 | } -------------------------------------------------------------------------------- /WordReview/static/css/calendar.css: -------------------------------------------------------------------------------- 1 | table{ 2 | margin-top: 3rem; 3 | } 4 | 5 | th{ 6 | text-align: center; 7 | } 8 | 9 | td{ 10 | text-align:center; 11 | width: 10%; 12 | height: 60px; 13 | padding:5px; 14 | } 15 | .td-div{ 16 | display: flex; 17 | flex-direction: column; 18 | justify-content: start; 19 | } 20 | 21 | .td-date{ 22 | color: grey; 23 | text-align:left; 24 | } 25 | 26 | .list{ 27 | text-align: center; 28 | vertical-align: middle; 29 | border-radius: .25rem; 30 | margin: 1px 2px; 31 | padding: 0 2px; 32 | min-width: 50px; 33 | } 34 | 35 | .undo{ 36 | border: 1px solid #007bff; 37 | background-color: #007bff; 38 | color: white; 39 | cursor: pointer; 40 | } 41 | .done{ 42 | border: 1px solid grey; 43 | } 44 | .plus{ 45 | border: 1px dashed rgb(192, 192, 192); 46 | color: grey; 47 | } 48 | 49 | #yesterday-mode{ 50 | width: 35%; 51 | padding: 10px; 52 | box-shadow: 0 .5rem 1rem rgba(0,0,0,.15); 53 | border-radius: .25rem!important; 54 | text-align: center; 55 | margin: 10px; 56 | cursor: pointer; 57 | align-self: center; 58 | } -------------------------------------------------------------------------------- /WordReview/static/font/fontawesome/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/WordReview/static/font/fontawesome/FontAwesome.otf -------------------------------------------------------------------------------- /WordReview/static/font/fontawesome/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:'FontAwesome';src:url('./fontawesome-webfont.eot?v=3.2.1');src:url('./fontawesome-webfont.eot?#iefix&v=3.2.1') format('embedded-opentype'),url('./fontawesome-webfont.woff?v=3.2.1') format('woff'),url('./fontawesome-webfont.ttf?v=3.2.1') format('truetype'),url('./fontawesome-webfont.svg#fontawesomeregular?v=3.2.1') format('svg');font-weight:normal;font-style:normal;}[class^="icon-"],[class*=" icon-"]{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;} 2 | [class^="icon-"]:before,[class*=" icon-"]:before{text-decoration:inherit;display:inline-block;speak:none;} 3 | .icon-large:before{vertical-align:-10%;font-size:1.3333333333333333em;} 4 | a [class^="icon-"],a [class*=" icon-"]{display:inline;} 5 | [class^="icon-"].icon-fixed-width,[class*=" icon-"].icon-fixed-width{display:inline-block;width:1.1428571428571428em;text-align:right;padding-right:0.2857142857142857em;}[class^="icon-"].icon-fixed-width.icon-large,[class*=" icon-"].icon-fixed-width.icon-large{width:1.4285714285714286em;} 6 | .icons-ul{margin-left:2.142857142857143em;list-style-type:none;}.icons-ul>li{position:relative;} 7 | .icons-ul .icon-li{position:absolute;left:-2.142857142857143em;width:2.142857142857143em;text-align:center;line-height:inherit;} 8 | [class^="icon-"].hide,[class*=" icon-"].hide{display:none;} 9 | .icon-muted{color:#eeeeee;} 10 | .icon-light{color:#ffffff;} 11 | .icon-dark{color:#333333;} 12 | .icon-border{border:solid 1px #eeeeee;padding:.2em .25em .15em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} 13 | .icon-2x{font-size:2em;}.icon-2x.icon-border{border-width:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} 14 | .icon-3x{font-size:3em;}.icon-3x.icon-border{border-width:3px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} 15 | .icon-4x{font-size:4em;}.icon-4x.icon-border{border-width:4px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} 16 | .icon-5x{font-size:5em;}.icon-5x.icon-border{border-width:5px;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px;} 17 | .pull-right{float:right;} 18 | .pull-left{float:left;} 19 | [class^="icon-"].pull-left,[class*=" icon-"].pull-left{margin-right:.3em;} 20 | [class^="icon-"].pull-right,[class*=" icon-"].pull-right{margin-left:.3em;} 21 | [class^="icon-"],[class*=" icon-"]{display:inline;width:auto;height:auto;line-height:normal;vertical-align:baseline;background-image:none;background-position:0% 0%;background-repeat:repeat;margin-top:0;} 22 | .icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:none;} 23 | .btn [class^="icon-"].icon-large,.nav [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large,.nav [class*=" icon-"].icon-large{line-height:.9em;} 24 | .btn [class^="icon-"].icon-spin,.nav [class^="icon-"].icon-spin,.btn [class*=" icon-"].icon-spin,.nav [class*=" icon-"].icon-spin{display:inline-block;} 25 | .nav-tabs [class^="icon-"],.nav-pills [class^="icon-"],.nav-tabs [class*=" icon-"],.nav-pills [class*=" icon-"],.nav-tabs [class^="icon-"].icon-large,.nav-pills [class^="icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large{line-height:.9em;} 26 | .btn [class^="icon-"].pull-left.icon-2x,.btn [class*=" icon-"].pull-left.icon-2x,.btn [class^="icon-"].pull-right.icon-2x,.btn [class*=" icon-"].pull-right.icon-2x{margin-top:.18em;} 27 | .btn [class^="icon-"].icon-spin.icon-large,.btn [class*=" icon-"].icon-spin.icon-large{line-height:.8em;} 28 | .btn.btn-small [class^="icon-"].pull-left.icon-2x,.btn.btn-small [class*=" icon-"].pull-left.icon-2x,.btn.btn-small [class^="icon-"].pull-right.icon-2x,.btn.btn-small [class*=" icon-"].pull-right.icon-2x{margin-top:.25em;} 29 | .btn.btn-large [class^="icon-"],.btn.btn-large [class*=" icon-"]{margin-top:0;}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x,.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-top:.05em;} 30 | .btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x{margin-right:.2em;} 31 | .btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-left:.2em;} 32 | .nav-list [class^="icon-"],.nav-list [class*=" icon-"]{line-height:inherit;} 33 | .icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:-35%;}.icon-stack [class^="icon-"],.icon-stack [class*=" icon-"]{display:block;text-align:center;position:absolute;width:100%;height:100%;font-size:1em;line-height:inherit;*line-height:2em;} 34 | .icon-stack .icon-stack-base{font-size:2em;*line-height:1em;} 35 | .icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear;} 36 | a .icon-stack,a .icon-spin{display:inline-block;text-decoration:none;} 37 | @-moz-keyframes spin{0%{-moz-transform:rotate(0deg);} 100%{-moz-transform:rotate(359deg);}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);} 100%{-webkit-transform:rotate(359deg);}}@-o-keyframes spin{0%{-o-transform:rotate(0deg);} 100%{-o-transform:rotate(359deg);}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg);} 100%{-ms-transform:rotate(359deg);}}@keyframes spin{0%{transform:rotate(0deg);} 100%{transform:rotate(359deg);}}.icon-rotate-90:before{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);} 38 | .icon-rotate-180:before{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);} 39 | .icon-rotate-270:before{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);} 40 | .icon-flip-horizontal:before{-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1);} 41 | .icon-flip-vertical:before{-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1);} 42 | a .icon-rotate-90:before,a .icon-rotate-180:before,a .icon-rotate-270:before,a .icon-flip-horizontal:before,a .icon-flip-vertical:before{display:inline-block;} 43 | .icon-glass:before{content:"\f000";} 44 | .icon-music:before{content:"\f001";} 45 | .icon-search:before{content:"\f002";} 46 | .icon-envelope-alt:before{content:"\f003";} 47 | .icon-heart:before{content:"\f004";} 48 | .icon-star:before{content:"\f005";} 49 | .icon-star-empty:before{content:"\f006";} 50 | .icon-user:before{content:"\f007";} 51 | .icon-film:before{content:"\f008";} 52 | .icon-th-large:before{content:"\f009";} 53 | .icon-th:before{content:"\f00a";} 54 | .icon-th-list:before{content:"\f00b";} 55 | .icon-ok:before{content:"\f00c";} 56 | .icon-remove:before{content:"\f00d";} 57 | .icon-zoom-in:before{content:"\f00e";} 58 | .icon-zoom-out:before{content:"\f010";} 59 | .icon-power-off:before,.icon-off:before{content:"\f011";} 60 | .icon-signal:before{content:"\f012";} 61 | .icon-gear:before,.icon-cog:before{content:"\f013";} 62 | .icon-trash:before{content:"\f014";} 63 | .icon-home:before{content:"\f015";} 64 | .icon-file-alt:before{content:"\f016";} 65 | .icon-time:before{content:"\f017";} 66 | .icon-road:before{content:"\f018";} 67 | .icon-download-alt:before{content:"\f019";} 68 | .icon-download:before{content:"\f01a";} 69 | .icon-upload:before{content:"\f01b";} 70 | .icon-inbox:before{content:"\f01c";} 71 | .icon-play-circle:before{content:"\f01d";} 72 | .icon-rotate-right:before,.icon-repeat:before{content:"\f01e";} 73 | .icon-refresh:before{content:"\f021";} 74 | .icon-list-alt:before{content:"\f022";} 75 | .icon-lock:before{content:"\f023";} 76 | .icon-flag:before{content:"\f024";} 77 | .icon-headphones:before{content:"\f025";} 78 | .icon-volume-off:before{content:"\f026";} 79 | .icon-volume-down:before{content:"\f027";} 80 | .icon-volume-up:before{content:"\f028";} 81 | .icon-qrcode:before{content:"\f029";} 82 | .icon-barcode:before{content:"\f02a";} 83 | .icon-tag:before{content:"\f02b";} 84 | .icon-tags:before{content:"\f02c";} 85 | .icon-book:before{content:"\f02d";} 86 | .icon-bookmark:before{content:"\f02e";} 87 | .icon-print:before{content:"\f02f";} 88 | .icon-camera:before{content:"\f030";} 89 | .icon-font:before{content:"\f031";} 90 | .icon-bold:before{content:"\f032";} 91 | .icon-italic:before{content:"\f033";} 92 | .icon-text-height:before{content:"\f034";} 93 | .icon-text-width:before{content:"\f035";} 94 | .icon-align-left:before{content:"\f036";} 95 | .icon-align-center:before{content:"\f037";} 96 | .icon-align-right:before{content:"\f038";} 97 | .icon-align-justify:before{content:"\f039";} 98 | .icon-list:before{content:"\f03a";} 99 | .icon-indent-left:before{content:"\f03b";} 100 | .icon-indent-right:before{content:"\f03c";} 101 | .icon-facetime-video:before{content:"\f03d";} 102 | .icon-picture:before{content:"\f03e";} 103 | .icon-pencil:before{content:"\f040";} 104 | .icon-map-marker:before{content:"\f041";} 105 | .icon-adjust:before{content:"\f042";} 106 | .icon-tint:before{content:"\f043";} 107 | .icon-edit:before{content:"\f044";} 108 | .icon-share:before{content:"\f045";} 109 | .icon-check:before{content:"\f046";} 110 | .icon-move:before{content:"\f047";} 111 | .icon-step-backward:before{content:"\f048";} 112 | .icon-fast-backward:before{content:"\f049";} 113 | .icon-backward:before{content:"\f04a";} 114 | .icon-play:before{content:"\f04b";} 115 | .icon-pause:before{content:"\f04c";} 116 | .icon-stop:before{content:"\f04d";} 117 | .icon-forward:before{content:"\f04e";} 118 | .icon-fast-forward:before{content:"\f050";} 119 | .icon-step-forward:before{content:"\f051";} 120 | .icon-eject:before{content:"\f052";} 121 | .icon-chevron-left:before{content:"\f053";} 122 | .icon-chevron-right:before{content:"\f054";} 123 | .icon-plus-sign:before{content:"\f055";} 124 | .icon-minus-sign:before{content:"\f056";} 125 | .icon-remove-sign:before{content:"\f057";} 126 | .icon-ok-sign:before{content:"\f058";} 127 | .icon-question-sign:before{content:"\f059";} 128 | .icon-info-sign:before{content:"\f05a";} 129 | .icon-screenshot:before{content:"\f05b";} 130 | .icon-remove-circle:before{content:"\f05c";} 131 | .icon-ok-circle:before{content:"\f05d";} 132 | .icon-ban-circle:before{content:"\f05e";} 133 | .icon-arrow-left:before{content:"\f060";} 134 | .icon-arrow-right:before{content:"\f061";} 135 | .icon-arrow-up:before{content:"\f062";} 136 | .icon-arrow-down:before{content:"\f063";} 137 | .icon-mail-forward:before,.icon-share-alt:before{content:"\f064";} 138 | .icon-resize-full:before{content:"\f065";} 139 | .icon-resize-small:before{content:"\f066";} 140 | .icon-plus:before{content:"\f067";} 141 | .icon-minus:before{content:"\f068";} 142 | .icon-asterisk:before{content:"\f069";} 143 | .icon-exclamation-sign:before{content:"\f06a";} 144 | .icon-gift:before{content:"\f06b";} 145 | .icon-leaf:before{content:"\f06c";} 146 | .icon-fire:before{content:"\f06d";} 147 | .icon-eye-open:before{content:"\f06e";} 148 | .icon-eye-close:before{content:"\f070";} 149 | .icon-warning-sign:before{content:"\f071";} 150 | .icon-plane:before{content:"\f072";} 151 | .icon-calendar:before{content:"\f073";} 152 | .icon-random:before{content:"\f074";} 153 | .icon-comment:before{content:"\f075";} 154 | .icon-magnet:before{content:"\f076";} 155 | .icon-chevron-up:before{content:"\f077";} 156 | .icon-chevron-down:before{content:"\f078";} 157 | .icon-retweet:before{content:"\f079";} 158 | .icon-shopping-cart:before{content:"\f07a";} 159 | .icon-folder-close:before{content:"\f07b";} 160 | .icon-folder-open:before{content:"\f07c";} 161 | .icon-resize-vertical:before{content:"\f07d";} 162 | .icon-resize-horizontal:before{content:"\f07e";} 163 | .icon-bar-chart:before{content:"\f080";} 164 | .icon-twitter-sign:before{content:"\f081";} 165 | .icon-facebook-sign:before{content:"\f082";} 166 | .icon-camera-retro:before{content:"\f083";} 167 | .icon-key:before{content:"\f084";} 168 | .icon-gears:before,.icon-cogs:before{content:"\f085";} 169 | .icon-comments:before{content:"\f086";} 170 | .icon-thumbs-up-alt:before{content:"\f087";} 171 | .icon-thumbs-down-alt:before{content:"\f088";} 172 | .icon-star-half:before{content:"\f089";} 173 | .icon-heart-empty:before{content:"\f08a";} 174 | .icon-signout:before{content:"\f08b";} 175 | .icon-linkedin-sign:before{content:"\f08c";} 176 | .icon-pushpin:before{content:"\f08d";} 177 | .icon-external-link:before{content:"\f08e";} 178 | .icon-signin:before{content:"\f090";} 179 | .icon-trophy:before{content:"\f091";} 180 | .icon-github-sign:before{content:"\f092";} 181 | .icon-upload-alt:before{content:"\f093";} 182 | .icon-lemon:before{content:"\f094";} 183 | .icon-phone:before{content:"\f095";} 184 | .icon-unchecked:before,.icon-check-empty:before{content:"\f096";} 185 | .icon-bookmark-empty:before{content:"\f097";} 186 | .icon-phone-sign:before{content:"\f098";} 187 | .icon-twitter:before{content:"\f099";} 188 | .icon-facebook:before{content:"\f09a";} 189 | .icon-github:before{content:"\f09b";} 190 | .icon-unlock:before{content:"\f09c";} 191 | .icon-credit-card:before{content:"\f09d";} 192 | .icon-rss:before{content:"\f09e";} 193 | .icon-hdd:before{content:"\f0a0";} 194 | .icon-bullhorn:before{content:"\f0a1";} 195 | .icon-bell:before{content:"\f0a2";} 196 | .icon-certificate:before{content:"\f0a3";} 197 | .icon-hand-right:before{content:"\f0a4";} 198 | .icon-hand-left:before{content:"\f0a5";} 199 | .icon-hand-up:before{content:"\f0a6";} 200 | .icon-hand-down:before{content:"\f0a7";} 201 | .icon-circle-arrow-left:before{content:"\f0a8";} 202 | .icon-circle-arrow-right:before{content:"\f0a9";} 203 | .icon-circle-arrow-up:before{content:"\f0aa";} 204 | .icon-circle-arrow-down:before{content:"\f0ab";} 205 | .icon-globe:before{content:"\f0ac";} 206 | .icon-wrench:before{content:"\f0ad";} 207 | .icon-tasks:before{content:"\f0ae";} 208 | .icon-filter:before{content:"\f0b0";} 209 | .icon-briefcase:before{content:"\f0b1";} 210 | .icon-fullscreen:before{content:"\f0b2";} 211 | .icon-group:before{content:"\f0c0";} 212 | .icon-link:before{content:"\f0c1";} 213 | .icon-cloud:before{content:"\f0c2";} 214 | .icon-beaker:before{content:"\f0c3";} 215 | .icon-cut:before{content:"\f0c4";} 216 | .icon-copy:before{content:"\f0c5";} 217 | .icon-paperclip:before,.icon-paper-clip:before{content:"\f0c6";} 218 | .icon-save:before{content:"\f0c7";} 219 | .icon-sign-blank:before{content:"\f0c8";} 220 | .icon-reorder:before{content:"\f0c9";} 221 | .icon-list-ul:before{content:"\f0ca";} 222 | .icon-list-ol:before{content:"\f0cb";} 223 | .icon-strikethrough:before{content:"\f0cc";} 224 | .icon-underline:before{content:"\f0cd";} 225 | .icon-table:before{content:"\f0ce";} 226 | .icon-magic:before{content:"\f0d0";} 227 | .icon-truck:before{content:"\f0d1";} 228 | .icon-pinterest:before{content:"\f0d2";} 229 | .icon-pinterest-sign:before{content:"\f0d3";} 230 | .icon-google-plus-sign:before{content:"\f0d4";} 231 | .icon-google-plus:before{content:"\f0d5";} 232 | .icon-money:before{content:"\f0d6";} 233 | .icon-caret-down:before{content:"\f0d7";} 234 | .icon-caret-up:before{content:"\f0d8";} 235 | .icon-caret-left:before{content:"\f0d9";} 236 | .icon-caret-right:before{content:"\f0da";} 237 | .icon-columns:before{content:"\f0db";} 238 | .icon-sort:before{content:"\f0dc";} 239 | .icon-sort-down:before{content:"\f0dd";} 240 | .icon-sort-up:before{content:"\f0de";} 241 | .icon-envelope:before{content:"\f0e0";} 242 | .icon-linkedin:before{content:"\f0e1";} 243 | .icon-rotate-left:before,.icon-undo:before{content:"\f0e2";} 244 | .icon-legal:before{content:"\f0e3";} 245 | .icon-dashboard:before{content:"\f0e4";} 246 | .icon-comment-alt:before{content:"\f0e5";} 247 | .icon-comments-alt:before{content:"\f0e6";} 248 | .icon-bolt:before{content:"\f0e7";} 249 | .icon-sitemap:before{content:"\f0e8";} 250 | .icon-umbrella:before{content:"\f0e9";} 251 | .icon-paste:before{content:"\f0ea";} 252 | .icon-lightbulb:before{content:"\f0eb";} 253 | .icon-exchange:before{content:"\f0ec";} 254 | .icon-cloud-download:before{content:"\f0ed";} 255 | .icon-cloud-upload:before{content:"\f0ee";} 256 | .icon-user-md:before{content:"\f0f0";} 257 | .icon-stethoscope:before{content:"\f0f1";} 258 | .icon-suitcase:before{content:"\f0f2";} 259 | .icon-bell-alt:before{content:"\f0f3";} 260 | .icon-coffee:before{content:"\f0f4";} 261 | .icon-food:before{content:"\f0f5";} 262 | .icon-file-text-alt:before{content:"\f0f6";} 263 | .icon-building:before{content:"\f0f7";} 264 | .icon-hospital:before{content:"\f0f8";} 265 | .icon-ambulance:before{content:"\f0f9";} 266 | .icon-medkit:before{content:"\f0fa";} 267 | .icon-fighter-jet:before{content:"\f0fb";} 268 | .icon-beer:before{content:"\f0fc";} 269 | .icon-h-sign:before{content:"\f0fd";} 270 | .icon-plus-sign-alt:before{content:"\f0fe";} 271 | .icon-double-angle-left:before{content:"\f100";} 272 | .icon-double-angle-right:before{content:"\f101";} 273 | .icon-double-angle-up:before{content:"\f102";} 274 | .icon-double-angle-down:before{content:"\f103";} 275 | .icon-angle-left:before{content:"\f104";} 276 | .icon-angle-right:before{content:"\f105";} 277 | .icon-angle-up:before{content:"\f106";} 278 | .icon-angle-down:before{content:"\f107";} 279 | .icon-desktop:before{content:"\f108";} 280 | .icon-laptop:before{content:"\f109";} 281 | .icon-tablet:before{content:"\f10a";} 282 | .icon-mobile-phone:before{content:"\f10b";} 283 | .icon-circle-blank:before{content:"\f10c";} 284 | .icon-quote-left:before{content:"\f10d";} 285 | .icon-quote-right:before{content:"\f10e";} 286 | .icon-spinner:before{content:"\f110";} 287 | .icon-circle:before{content:"\f111";} 288 | .icon-mail-reply:before,.icon-reply:before{content:"\f112";} 289 | .icon-github-alt:before{content:"\f113";} 290 | .icon-folder-close-alt:before{content:"\f114";} 291 | .icon-folder-open-alt:before{content:"\f115";} 292 | .icon-expand-alt:before{content:"\f116";} 293 | .icon-collapse-alt:before{content:"\f117";} 294 | .icon-smile:before{content:"\f118";} 295 | .icon-frown:before{content:"\f119";} 296 | .icon-meh:before{content:"\f11a";} 297 | .icon-gamepad:before{content:"\f11b";} 298 | .icon-keyboard:before{content:"\f11c";} 299 | .icon-flag-alt:before{content:"\f11d";} 300 | .icon-flag-checkered:before{content:"\f11e";} 301 | .icon-terminal:before{content:"\f120";} 302 | .icon-code:before{content:"\f121";} 303 | .icon-reply-all:before{content:"\f122";} 304 | .icon-mail-reply-all:before{content:"\f122";} 305 | .icon-star-half-full:before,.icon-star-half-empty:before{content:"\f123";} 306 | .icon-location-arrow:before{content:"\f124";} 307 | .icon-crop:before{content:"\f125";} 308 | .icon-code-fork:before{content:"\f126";} 309 | .icon-unlink:before{content:"\f127";} 310 | .icon-question:before{content:"\f128";} 311 | .icon-info:before{content:"\f129";} 312 | .icon-exclamation:before{content:"\f12a";} 313 | .icon-superscript:before{content:"\f12b";} 314 | .icon-subscript:before{content:"\f12c";} 315 | .icon-eraser:before{content:"\f12d";} 316 | .icon-puzzle-piece:before{content:"\f12e";} 317 | .icon-microphone:before{content:"\f130";} 318 | .icon-microphone-off:before{content:"\f131";} 319 | .icon-shield:before{content:"\f132";} 320 | .icon-calendar-empty:before{content:"\f133";} 321 | .icon-fire-extinguisher:before{content:"\f134";} 322 | .icon-rocket:before{content:"\f135";} 323 | .icon-maxcdn:before{content:"\f136";} 324 | .icon-chevron-sign-left:before{content:"\f137";} 325 | .icon-chevron-sign-right:before{content:"\f138";} 326 | .icon-chevron-sign-up:before{content:"\f139";} 327 | .icon-chevron-sign-down:before{content:"\f13a";} 328 | .icon-html5:before{content:"\f13b";} 329 | .icon-css3:before{content:"\f13c";} 330 | .icon-anchor:before{content:"\f13d";} 331 | .icon-unlock-alt:before{content:"\f13e";} 332 | .icon-bullseye:before{content:"\f140";} 333 | .icon-ellipsis-horizontal:before{content:"\f141";} 334 | .icon-ellipsis-vertical:before{content:"\f142";} 335 | .icon-rss-sign:before{content:"\f143";} 336 | .icon-play-sign:before{content:"\f144";} 337 | .icon-ticket:before{content:"\f145";} 338 | .icon-minus-sign-alt:before{content:"\f146";} 339 | .icon-check-minus:before{content:"\f147";} 340 | .icon-level-up:before{content:"\f148";} 341 | .icon-level-down:before{content:"\f149";} 342 | .icon-check-sign:before{content:"\f14a";} 343 | .icon-edit-sign:before{content:"\f14b";} 344 | .icon-external-link-sign:before{content:"\f14c";} 345 | .icon-share-sign:before{content:"\f14d";} 346 | .icon-compass:before{content:"\f14e";} 347 | .icon-collapse:before{content:"\f150";} 348 | .icon-collapse-top:before{content:"\f151";} 349 | .icon-expand:before{content:"\f152";} 350 | .icon-euro:before,.icon-eur:before{content:"\f153";} 351 | .icon-gbp:before{content:"\f154";} 352 | .icon-dollar:before,.icon-usd:before{content:"\f155";} 353 | .icon-rupee:before,.icon-inr:before{content:"\f156";} 354 | .icon-yen:before,.icon-jpy:before{content:"\f157";} 355 | .icon-renminbi:before,.icon-cny:before{content:"\f158";} 356 | .icon-won:before,.icon-krw:before{content:"\f159";} 357 | .icon-bitcoin:before,.icon-btc:before{content:"\f15a";} 358 | .icon-file:before{content:"\f15b";} 359 | .icon-file-text:before{content:"\f15c";} 360 | .icon-sort-by-alphabet:before{content:"\f15d";} 361 | .icon-sort-by-alphabet-alt:before{content:"\f15e";} 362 | .icon-sort-by-attributes:before{content:"\f160";} 363 | .icon-sort-by-attributes-alt:before{content:"\f161";} 364 | .icon-sort-by-order:before{content:"\f162";} 365 | .icon-sort-by-order-alt:before{content:"\f163";} 366 | .icon-thumbs-up:before{content:"\f164";} 367 | .icon-thumbs-down:before{content:"\f165";} 368 | .icon-youtube-sign:before{content:"\f166";} 369 | .icon-youtube:before{content:"\f167";} 370 | .icon-xing:before{content:"\f168";} 371 | .icon-xing-sign:before{content:"\f169";} 372 | .icon-youtube-play:before{content:"\f16a";} 373 | .icon-dropbox:before{content:"\f16b";} 374 | .icon-stackexchange:before{content:"\f16c";} 375 | .icon-instagram:before{content:"\f16d";} 376 | .icon-flickr:before{content:"\f16e";} 377 | .icon-adn:before{content:"\f170";} 378 | .icon-bitbucket:before{content:"\f171";} 379 | .icon-bitbucket-sign:before{content:"\f172";} 380 | .icon-tumblr:before{content:"\f173";} 381 | .icon-tumblr-sign:before{content:"\f174";} 382 | .icon-long-arrow-down:before{content:"\f175";} 383 | .icon-long-arrow-up:before{content:"\f176";} 384 | .icon-long-arrow-left:before{content:"\f177";} 385 | .icon-long-arrow-right:before{content:"\f178";} 386 | .icon-apple:before{content:"\f179";} 387 | .icon-windows:before{content:"\f17a";} 388 | .icon-android:before{content:"\f17b";} 389 | .icon-linux:before{content:"\f17c";} 390 | .icon-dribbble:before{content:"\f17d";} 391 | .icon-skype:before{content:"\f17e";} 392 | .icon-foursquare:before{content:"\f180";} 393 | .icon-trello:before{content:"\f181";} 394 | .icon-female:before{content:"\f182";} 395 | .icon-male:before{content:"\f183";} 396 | .icon-gittip:before{content:"\f184";} 397 | .icon-sun:before{content:"\f185";} 398 | .icon-moon:before{content:"\f186";} 399 | .icon-archive:before{content:"\f187";} 400 | .icon-bug:before{content:"\f188";} 401 | .icon-vk:before{content:"\f189";} 402 | .icon-weibo:before{content:"\f18a";} 403 | .icon-renren:before{content:"\f18b";} 404 | -------------------------------------------------------------------------------- /WordReview/static/font/fontawesome/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/WordReview/static/font/fontawesome/fontawesome-webfont.eot -------------------------------------------------------------------------------- /WordReview/static/font/fontawesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/WordReview/static/font/fontawesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /WordReview/static/font/fontawesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/WordReview/static/font/fontawesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /WordReview/static/js/calendar.js: -------------------------------------------------------------------------------- 1 | var EBBINGHAUS_DAYS; 2 | var today; 3 | var calendar_begin; 4 | var calendar_end; 5 | var showWeekNum = 4; // 一次显示多少个星期 6 | 7 | function addDays(date, num) { 8 | var newDate = new Date(); 9 | newDate.setTime(date.getTime() + (num * 24 * 60 * 60 * 1000) + 1); 10 | return newDate; 11 | } 12 | 13 | function dayDelta(d1, d2) { 14 | let d = (d1 - d2) / (1000 * 60 * 60 * 24); 15 | return parseInt(d); 16 | } 17 | 18 | function getDataRow(data, i, tag = 'td') { 19 | let row = document.createElement('tr'); 20 | // row.setAttribute('style', 'min-height:30px;') 21 | for (let j = i * 7; j < (i + 1) * 7; j++) { 22 | let cell = document.createElement(tag); 23 | let date = addDays(calendar_begin, j) 24 | let bigDiv = document.createElement('div'); 25 | bigDiv.innerHTML = '
' + (date.getMonth() + 1) + '月' + date.getDate() + '日' + '
'; 26 | 27 | let text = '
'; 28 | for (let k = 0; k < data[j].length; k++) { 29 | let d = data[j][k]; 30 | let l = (d.LIST + 1).toString(); 31 | // l = l.length == 1 ? '0' + l : l; 32 | let cls = d.state; 33 | let href = 'book=' + d.BOOK + '&list=' + d.LIST; 34 | text += '
' + d.abbr + l + '' + d.count + '
'; 35 | } 36 | bigDiv.innerHTML += text + '= 0 || j == 0) { break; } 81 | let index_tmp = dayDelta(next_day, calendar_begin) + 1; 82 | if (index_tmp < 0) { 83 | console.warn('过期:', pushCalendarData(list, j + 1, 'undo')); 84 | continue; 85 | } 86 | calendar[index_tmp].push(pushCalendarData(list, j + 1, 'undo')); 87 | last_review_date = next_day; 88 | } 89 | // 词表已背的日期 block 90 | var dateClass = ['done', 'plus']; 91 | for (let k = 0; k < dateClass.length; k++) { 92 | let history = [list.review_dates, list.review_dates_plus][k]; 93 | if (history == "") { continue; } 94 | history = history.split(';'); 95 | for (let j = 0; j < history.length; j++) { 96 | let old_date = new Date(history[j]); 97 | let d = dayDelta(old_date, calendar_begin) + 1; 98 | if (d < 0) { continue; } 99 | // console.log(dateClass[i]) 100 | let counter; 101 | if (dateClass[k] == 'plus') { counter = '+' + (j + 1); } 102 | else { counter = j + 1 } 103 | calendar[d].push( 104 | pushCalendarData(list, counter, dateClass[k])); 105 | } 106 | } 107 | 108 | } 109 | drawTable(calendar); 110 | } 111 | 112 | $(function () { 113 | // 页面渲染 114 | $.ajax({ 115 | url: '/review/get_calendar_data', 116 | type: 'GET', 117 | data: {} 118 | }).done(function (response) { 119 | if (response.status === 200) { 120 | EBBINGHAUS_DAYS = response.EBBINGHAUS_DAYS; 121 | renderCalendar(response.data); 122 | } 123 | }) 124 | 125 | $('#tbMain').on('click', '.undo', function (e) { 126 | window.location = '/review/review?' + $(this).attr('href') 127 | }) 128 | }) 129 | 130 | 131 | $(function () { 132 | $('#yesterday-mode').on('click', function (e) { 133 | document.location.href = $(this).attr('href'); 134 | }) 135 | }); 136 | 137 | $(document).keyup(function (e) { 138 | // console.log(e.keyCode); 139 | if (89 == e.keyCode) { 140 | $('#yesterday-mode').click(); 141 | } 142 | }); -------------------------------------------------------------------------------- /WordReview/static/js/homepage.js: -------------------------------------------------------------------------------- 1 | $.ajax({ 2 | // timeout: 1, // set timeout but failed 3 | url: 4 | "https://en.wikiquote.org/w/api.php?format=json&action=parse&prop=text&page=Main%20Page", 5 | dataType: "jsonp", 6 | }).done(function (response) { 7 | let $qotd = $("#tmpl-qotd"); 8 | let minH = 100; 9 | $qotd.html(response.parse.text["*"]); 10 | $qotd.html( 11 | $("#mf-qotd") 12 | .html() 13 | .replace(/href="\//g, 'href="https://en.wikiquote.org/') 14 | ); 15 | 16 | // delete nodes 17 | $qotd.find("small").empty(); 18 | $qotd.find("div").find("div")[0].innerHTML = ""; 19 | 20 | //left align 21 | $qotd.find("td").find("td").eq(2).find("tr").eq(0).css("text-align", "left"); 22 | 23 | // adjust img 24 | let $img = $qotd.find("img"); 25 | $img.css("margin-left", "5px"); 26 | let w = $img.width(); 27 | let h = $img.height(); 28 | $img.height(minH); 29 | $img.width((minH / h) * w); 30 | 31 | let H = $qotd.height(); 32 | if (H > minH) { 33 | $img.height(H); 34 | $img.width((H / h) * w); 35 | } 36 | }); 37 | 38 | $.ajax({ 39 | url: "https://api.github.com/repos/Benature/WordReview/commits", 40 | }).done(function (response) { 41 | let latest = response[0].commit; 42 | let date = latest.committer.date.replace("T", " ").replace("Z", ""); 43 | $("#github-commit").text( 44 | "上一次源码更新于" + date + ",更新附言为「" + latest.message + "」" 45 | ); 46 | }); 47 | 48 | $(function () { 49 | $(".list-block").on("click", function (e) { 50 | document.location.href = $(this).attr("href"); 51 | }); 52 | }); 53 | 54 | $(document).keyup(function (e) { 55 | // console.log(e.keyCode); 56 | if (89 == e.keyCode) { 57 | $("#yesterday-mode").click(); 58 | } else if (67 == e.keyCode) { 59 | document.location = "/calendar/"; 60 | } 61 | }); 62 | -------------------------------------------------------------------------------- /WordReview/static/js/thirdParty/hotkeys.min.js: -------------------------------------------------------------------------------- 1 | /*! hotkeys-js v3.7.6 | MIT (c) 2020 kenny wong | http://jaywcjlove.github.io/hotkeys */ 2 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).hotkeys=t()}(this,function(){"use strict";var e="undefined"!=typeof navigator&&0)[^>]*$|\{\{\! /,b={},f={},e,p={key:0,data:{}},i=0,c=0,l=[];function g(g,d,h,e){var c={data:e||(e===0||e===false)?e:d?d.data:{},_wrap:d?d._wrap:null,tmpl:null,parent:d||null,nodes:[],calls:u,nest:w,wrap:x,html:v,update:t};g&&a.extend(c,g,{nodes:[],parent:d});if(h){c.tmpl=h;c._ctnt=c._ctnt||c.tmpl(a,c);c.key=++i;(l.length?f:b)[i]=c}return c}a.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(f,d){a.fn[f]=function(n){var g=[],i=a(n),k,h,m,l,j=this.length===1&&this[0].parentNode;e=b||{};if(j&&j.nodeType===11&&j.childNodes.length===1&&i.length===1){i[d](this[0]);g=this}else{for(h=0,m=i.length;h0?this.clone(true):this).get();a(i[h])[d](k);g=g.concat(k)}c=0;g=this.pushStack(g,f,i.selector)}l=e;e=null;a.tmpl.complete(l);return g}});a.fn.extend({tmpl:function(d,c,b){return a.tmpl(this[0],d,c,b)},tmplItem:function(){return a.tmplItem(this[0])},template:function(b){return a.template(b,this[0])},domManip:function(d,m,k){if(d[0]&&a.isArray(d[0])){var g=a.makeArray(arguments),h=d[0],j=h.length,i=0,f;while(i").join(">").split('"').join(""").split("'").join("'")}});a.extend(a.tmpl,{tag:{tmpl:{_default:{$2:"null"},open:"if($notnull_1){__=__.concat($item.nest($1,$2));}"},wrap:{_default:{$2:"null"},open:"$item.calls(__,$1,$2);__=[];",close:"call=$item.calls();__=call._.concat($item.wrap(call,__));"},each:{_default:{$2:"$index, $value"},open:"if($notnull_1){$.each($1a,function($2){with(this){",close:"}});}"},"if":{open:"if(($notnull_1) && $1a){",close:"}"},"else":{_default:{$1:"true"},open:"}else if(($notnull_1) && $1a){"},html:{open:"if($notnull_1){__.push($1a);}"},"=":{_default:{$1:"$data"},open:"if($notnull_1){__.push($.encode($1a));}"},"!":{open:""}},complete:function(){b={}},afterManip:function(f,b,d){var e=b.nodeType===11?a.makeArray(b.childNodes):b.nodeType===1?[b]:[];d.call(f,b);m(e);c++}});function j(e,g,f){var b,c=f?a.map(f,function(a){return typeof a==="string"?e.key?a.replace(/(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g,"$1 "+d+'="'+e.key+'" $2'):a:j(a,e,a._ctnt)}):e;if(g)return c;c=c.join("");c.replace(/^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/,function(f,c,e,d){b=a(e).get();m(b);if(c)b=k(c).concat(b);if(d)b=b.concat(k(d))});return b?b:k(c)}function k(c){var b=document.createElement("div");b.innerHTML=c;return a.makeArray(b.childNodes)}function o(b){return new Function("jQuery","$item","var $=jQuery,call,__=[],$data=$item.data;with($data){__.push('"+a.trim(b).replace(/([\\'])/g,"\\$1").replace(/[\r\t\n]/g," ").replace(/\$\{([^\}]*)\}/g,"{{= $1}}").replace(/\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,function(m,l,k,g,b,c,d){var j=a.tmpl.tag[k],i,e,f;if(!j)throw"Unknown template tag: "+k;i=j._default||[];if(c&&!/\w$/.test(b)){b+=c;c=""}if(b){b=h(b);d=d?","+h(d)+")":c?")":"";e=c?b.indexOf(".")>-1?b+h(c):"("+b+").call($item"+d:b;f=c?e:"(typeof("+b+")==='function'?("+b+").call($item):("+b+"))"}else f=e=i.$1||"null";g=h(g);return"');"+j[l?"close":"open"].split("$notnull_1").join(b?"typeof("+b+")!=='undefined' && ("+b+")!=null":"true").split("$1a").join(f).split("$1").join(e).split("$2").join(g||i.$2||"")+"__.push('"})+"');}return __;")}function n(c,b){c._wrap=j(c,true,a.isArray(b)?b:[q.test(b)?b:a(b).html()]).join("")}function h(a){return a?a.replace(/\\'/g,"'").replace(/\\\\/g,"\\"):null}function s(b){var a=document.createElement("div");a.appendChild(b.cloneNode(true));return a.innerHTML}function m(o){var n="_"+c,k,j,l={},e,p,h;for(e=0,p=o.length;e=0;h--)m(j[h]);m(k)}function m(j){var p,h=j,k,e,m;if(m=j.getAttribute(d)){while(h.parentNode&&(h=h.parentNode).nodeType===1&&!(p=h.getAttribute(d)));if(p!==m){h=h.parentNode?h.nodeType===11?0:h.getAttribute(d)||0:0;if(!(e=b[m])){e=f[m];e=g(e,b[h]||f[h]);e.key=++i;b[i]=e}c&&o(m)}j.removeAttribute(d)}else if(c&&(e=a.data(j,"tmplItem"))){o(e.key);b[e.key]=e;h=a.data(j.parentNode,"tmplItem");h=h?h.key:0}if(e){k=e;while(k&&k.key!=h){k.nodes.push(j);k=k.parent}delete e._ctnt;delete e._wrap;a.data(j,"tmplItem",e)}function o(a){a=a+n;e=l[a]=l[a]||g(e,b[e.parent.key+n]||e.parent)}}}function u(a,d,c,b){if(!a)return l.pop();l.push({_:a,tmpl:d,item:this,data:c,options:b})}function w(d,c,b){return a.tmpl(a.template(d),c,b,this)}function x(b,d){var c=b.options||{};c.wrapped=d;return a.tmpl(a.template(b.tmpl),b.data,c,b.item)}function v(d,c){var b=this._wrap;return a.map(a(a.isArray(b)?b.join(""):b).filter(d||"*"),function(a){return c?a.innerText||a.textContent:a.outerHTML||s(a)})}function t(){var b=this.nodes;a.tmpl(null,null,null,this).insertBefore(b[0]);a(b).remove()}})(jQuery); -------------------------------------------------------------------------------- /WordReview/static/js/thirdParty/layer/layer.js: -------------------------------------------------------------------------------- 1 | /*! layer-v3.1.1 Web弹层组件 MIT License http://layer.layui.com/ By 贤心 */ 2 | ;!function(e,t){"use strict";var i,n,a=e.layui&&layui.define,o={getPath:function(){var e=document.currentScript?document.currentScript.src:function(){for(var e,t=document.scripts,i=t.length-1,n=i;n>0;n--)if("interactive"===t[n].readyState){e=t[n].src;break}return e||t[i].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),config:{},end:{},minIndex:0,minLeft:[],btn:["确定","取消"],type:["dialog","page","iframe","loading","tips"],getStyle:function(t,i){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](i)},link:function(t,i,n){if(r.path){var a=document.getElementsByTagName("head")[0],s=document.createElement("link");"string"==typeof i&&(n=i);var l=(n||t).replace(/\.|\//g,""),f="layuicss-"+l,c=0;s.rel="stylesheet",s.href=r.path+t,s.id=f,document.getElementById(f)||a.appendChild(s),"function"==typeof i&&!function u(){return++c>80?e.console&&console.error("layer.css: Invalid"):void(1989===parseInt(o.getStyle(document.getElementById(f),"width"))?i():setTimeout(u,100))}()}}},r={v:"3.1.1",ie:function(){var t=navigator.userAgent.toLowerCase();return!!(e.ActiveXObject||"ActiveXObject"in e)&&((t.match(/msie\s(\d+)/)||[])[1]||"11")}(),index:e.layer&&e.layer.v?1e5:0,path:o.getPath,config:function(e,t){return e=e||{},r.cache=o.config=i.extend({},o.config,e),r.path=o.config.path||r.path,"string"==typeof e.extend&&(e.extend=[e.extend]),o.config.path&&r.ready(),e.extend?(a?layui.addcss("modules/layer/"+e.extend):o.link("theme/"+e.extend),this):this},ready:function(e){var t="layer",i="",n=(a?"modules/layer/":"theme/")+"default/layer.css?v="+r.v+i;return a?layui.addcss(n,e,t):o.link(n,e,t),this},alert:function(e,t,n){var a="function"==typeof t;return a&&(n=t),r.open(i.extend({content:e,yes:n},a?{}:t))},confirm:function(e,t,n,a){var s="function"==typeof t;return s&&(a=n,n=t),r.open(i.extend({content:e,btn:o.btn,yes:n,btn2:a},s?{}:t))},msg:function(e,n,a){var s="function"==typeof n,f=o.config.skin,c=(f?f+" "+f+"-msg":"")||"layui-layer-msg",u=l.anim.length-1;return s&&(a=n),r.open(i.extend({content:e,time:3e3,shade:!1,skin:c,title:!1,closeBtn:!1,btn:!1,resize:!1,end:a},s&&!o.config.skin?{skin:c+" layui-layer-hui",anim:u}:function(){return n=n||{},(n.icon===-1||n.icon===t&&!o.config.skin)&&(n.skin=c+" "+(n.skin||"layui-layer-hui")),n}()))},load:function(e,t){return r.open(i.extend({type:3,icon:e||0,resize:!1,shade:.01},t))},tips:function(e,t,n){return r.open(i.extend({type:4,content:[e,t],closeBtn:!1,time:3e3,shade:!1,resize:!1,fixed:!1,maxWidth:210},n))}},s=function(e){var t=this;t.index=++r.index,t.config=i.extend({},t.config,o.config,e),document.body?t.creat():setTimeout(function(){t.creat()},30)};s.pt=s.prototype;var l=["layui-layer",".layui-layer-title",".layui-layer-main",".layui-layer-dialog","layui-layer-iframe","layui-layer-content","layui-layer-btn","layui-layer-close"];l.anim=["layer-anim-00","layer-anim-01","layer-anim-02","layer-anim-03","layer-anim-04","layer-anim-05","layer-anim-06"],s.pt.config={type:0,shade:.3,fixed:!0,move:l[1],title:"信息",offset:"auto",area:"auto",closeBtn:1,time:0,zIndex:19891014,maxWidth:360,anim:0,isOutAnim:!0,icon:-1,moveType:1,resize:!0,scrollbar:!0,tips:2},s.pt.vessel=function(e,t){var n=this,a=n.index,r=n.config,s=r.zIndex+a,f="object"==typeof r.title,c=r.maxmin&&(1===r.type||2===r.type),u=r.title?'
'+(f?r.title[0]:r.title)+"
":"";return r.zIndex=s,t([r.shade?'
':"",'
'+(e&&2!=r.type?"":u)+'
'+(0==r.type&&r.icon!==-1?'':"")+(1==r.type&&e?"":r.content||"")+'
'+function(){var e=c?'':"";return r.closeBtn&&(e+=''),e}()+""+(r.btn?function(){var e="";"string"==typeof r.btn&&(r.btn=[r.btn]);for(var t=0,i=r.btn.length;t'+r.btn[t]+"";return'
'+e+"
"}():"")+(r.resize?'':"")+"
"],u,i('
')),n},s.pt.creat=function(){var e=this,t=e.config,a=e.index,s=t.content,f="object"==typeof s,c=i("body");if(!t.id||!i("#"+t.id)[0]){switch("string"==typeof t.area&&(t.area="auto"===t.area?["",""]:[t.area,""]),t.shift&&(t.anim=t.shift),6==r.ie&&(t.fixed=!1),t.type){case 0:t.btn="btn"in t?t.btn:o.btn[0],r.closeAll("dialog");break;case 2:var s=t.content=f?t.content:[t.content||"http://layer.layui.com","auto"];t.content='';break;case 3:delete t.title,delete t.closeBtn,t.icon===-1&&0===t.icon,r.closeAll("loading");break;case 4:f||(t.content=[t.content,"body"]),t.follow=t.content[1],t.content=t.content[0]+'',delete t.title,t.tips="object"==typeof t.tips?t.tips:[t.tips,!0],t.tipsMore||r.closeAll("tips")}if(e.vessel(f,function(n,r,u){c.append(n[0]),f?function(){2==t.type||4==t.type?function(){i("body").append(n[1])}():function(){s.parents("."+l[0])[0]||(s.data("display",s.css("display")).show().addClass("layui-layer-wrap").wrap(n[1]),i("#"+l[0]+a).find("."+l[5]).before(r))}()}():c.append(n[1]),i(".layui-layer-move")[0]||c.append(o.moveElem=u),e.layero=i("#"+l[0]+a),t.scrollbar||l.html.css("overflow","hidden").attr("layer-full",a)}).auto(a),i("#layui-layer-shade"+e.index).css({"background-color":t.shade[1]||"#000",opacity:t.shade[0]||t.shade}),2==t.type&&6==r.ie&&e.layero.find("iframe").attr("src",s[0]),4==t.type?e.tips():e.offset(),t.fixed&&n.on("resize",function(){e.offset(),(/^\d+%$/.test(t.area[0])||/^\d+%$/.test(t.area[1]))&&e.auto(a),4==t.type&&e.tips()}),t.time<=0||setTimeout(function(){r.close(e.index)},t.time),e.move().callback(),l.anim[t.anim]){var u="layer-anim "+l.anim[t.anim];e.layero.addClass(u).one("webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend",function(){i(this).removeClass(u)})}t.isOutAnim&&e.layero.data("isOutAnim",!0)}},s.pt.auto=function(e){var t=this,a=t.config,o=i("#"+l[0]+e);""===a.area[0]&&a.maxWidth>0&&(r.ie&&r.ie<8&&a.btn&&o.width(o.innerWidth()),o.outerWidth()>a.maxWidth&&o.width(a.maxWidth));var s=[o.innerWidth(),o.innerHeight()],f=o.find(l[1]).outerHeight()||0,c=o.find("."+l[6]).outerHeight()||0,u=function(e){e=o.find(e),e.height(s[1]-f-c-2*(0|parseFloat(e.css("padding-top"))))};switch(a.type){case 2:u("iframe");break;default:""===a.area[1]?a.maxHeight>0&&o.outerHeight()>a.maxHeight?(s[1]=a.maxHeight,u("."+l[5])):a.fixed&&s[1]>=n.height()&&(s[1]=n.height(),u("."+l[5])):u("."+l[5])}return t},s.pt.offset=function(){var e=this,t=e.config,i=e.layero,a=[i.outerWidth(),i.outerHeight()],o="object"==typeof t.offset;e.offsetTop=(n.height()-a[1])/2,e.offsetLeft=(n.width()-a[0])/2,o?(e.offsetTop=t.offset[0],e.offsetLeft=t.offset[1]||e.offsetLeft):"auto"!==t.offset&&("t"===t.offset?e.offsetTop=0:"r"===t.offset?e.offsetLeft=n.width()-a[0]:"b"===t.offset?e.offsetTop=n.height()-a[1]:"l"===t.offset?e.offsetLeft=0:"lt"===t.offset?(e.offsetTop=0,e.offsetLeft=0):"lb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=0):"rt"===t.offset?(e.offsetTop=0,e.offsetLeft=n.width()-a[0]):"rb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=n.width()-a[0]):e.offsetTop=t.offset),t.fixed||(e.offsetTop=/%$/.test(e.offsetTop)?n.height()*parseFloat(e.offsetTop)/100:parseFloat(e.offsetTop),e.offsetLeft=/%$/.test(e.offsetLeft)?n.width()*parseFloat(e.offsetLeft)/100:parseFloat(e.offsetLeft),e.offsetTop+=n.scrollTop(),e.offsetLeft+=n.scrollLeft()),i.attr("minLeft")&&(e.offsetTop=n.height()-(i.find(l[1]).outerHeight()||0),e.offsetLeft=i.css("left")),i.css({top:e.offsetTop,left:e.offsetLeft})},s.pt.tips=function(){var e=this,t=e.config,a=e.layero,o=[a.outerWidth(),a.outerHeight()],r=i(t.follow);r[0]||(r=i("body"));var s={width:r.outerWidth(),height:r.outerHeight(),top:r.offset().top,left:r.offset().left},f=a.find(".layui-layer-TipsG"),c=t.tips[0];t.tips[1]||f.remove(),s.autoLeft=function(){s.left+o[0]-n.width()>0?(s.tipLeft=s.left+s.width-o[0],f.css({right:12,left:"auto"})):s.tipLeft=s.left},s.where=[function(){s.autoLeft(),s.tipTop=s.top-o[1]-10,f.removeClass("layui-layer-TipsB").addClass("layui-layer-TipsT").css("border-right-color",t.tips[1])},function(){s.tipLeft=s.left+s.width+10,s.tipTop=s.top,f.removeClass("layui-layer-TipsL").addClass("layui-layer-TipsR").css("border-bottom-color",t.tips[1])},function(){s.autoLeft(),s.tipTop=s.top+s.height+10,f.removeClass("layui-layer-TipsT").addClass("layui-layer-TipsB").css("border-right-color",t.tips[1])},function(){s.tipLeft=s.left-o[0]-10,s.tipTop=s.top,f.removeClass("layui-layer-TipsR").addClass("layui-layer-TipsL").css("border-bottom-color",t.tips[1])}],s.where[c-1](),1===c?s.top-(n.scrollTop()+o[1]+16)<0&&s.where[2]():2===c?n.width()-(s.left+s.width+o[0]+16)>0||s.where[3]():3===c?s.top-n.scrollTop()+s.height+o[1]+16-n.height()>0&&s.where[0]():4===c&&o[0]+16-s.left>0&&s.where[1](),a.find("."+l[5]).css({"background-color":t.tips[1],"padding-right":t.closeBtn?"30px":""}),a.css({left:s.tipLeft-(t.fixed?n.scrollLeft():0),top:s.tipTop-(t.fixed?n.scrollTop():0)})},s.pt.move=function(){var e=this,t=e.config,a=i(document),s=e.layero,l=s.find(t.move),f=s.find(".layui-layer-resize"),c={};return t.move&&l.css("cursor","move"),l.on("mousedown",function(e){e.preventDefault(),t.move&&(c.moveStart=!0,c.offset=[e.clientX-parseFloat(s.css("left")),e.clientY-parseFloat(s.css("top"))],o.moveElem.css("cursor","move").show())}),f.on("mousedown",function(e){e.preventDefault(),c.resizeStart=!0,c.offset=[e.clientX,e.clientY],c.area=[s.outerWidth(),s.outerHeight()],o.moveElem.css("cursor","se-resize").show()}),a.on("mousemove",function(i){if(c.moveStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1],l="fixed"===s.css("position");if(i.preventDefault(),c.stX=l?0:n.scrollLeft(),c.stY=l?0:n.scrollTop(),!t.moveOut){var f=n.width()-s.outerWidth()+c.stX,u=n.height()-s.outerHeight()+c.stY;af&&(a=f),ou&&(o=u)}s.css({left:a,top:o})}if(t.resize&&c.resizeStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1];i.preventDefault(),r.style(e.index,{width:c.area[0]+a,height:c.area[1]+o}),c.isResize=!0,t.resizing&&t.resizing(s)}}).on("mouseup",function(e){c.moveStart&&(delete c.moveStart,o.moveElem.hide(),t.moveEnd&&t.moveEnd(s)),c.resizeStart&&(delete c.resizeStart,o.moveElem.hide())}),e},s.pt.callback=function(){function e(){var e=a.cancel&&a.cancel(t.index,n);e===!1||r.close(t.index)}var t=this,n=t.layero,a=t.config;t.openLayer(),a.success&&(2==a.type?n.find("iframe").on("load",function(){a.success(n,t.index)}):a.success(n,t.index)),6==r.ie&&t.IE6(n),n.find("."+l[6]).children("a").on("click",function(){var e=i(this).index();if(0===e)a.yes?a.yes(t.index,n):a.btn1?a.btn1(t.index,n):r.close(t.index);else{var o=a["btn"+(e+1)]&&a["btn"+(e+1)](t.index,n);o===!1||r.close(t.index)}}),n.find("."+l[7]).on("click",e),a.shadeClose&&i("#layui-layer-shade"+t.index).on("click",function(){r.close(t.index)}),n.find(".layui-layer-min").on("click",function(){var e=a.min&&a.min(n);e===!1||r.min(t.index,a)}),n.find(".layui-layer-max").on("click",function(){i(this).hasClass("layui-layer-maxmin")?(r.restore(t.index),a.restore&&a.restore(n)):(r.full(t.index,a),setTimeout(function(){a.full&&a.full(n)},100))}),a.end&&(o.end[t.index]=a.end)},o.reselect=function(){i.each(i("select"),function(e,t){var n=i(this);n.parents("."+l[0])[0]||1==n.attr("layer")&&i("."+l[0]).length<1&&n.removeAttr("layer").show(),n=null})},s.pt.IE6=function(e){i("select").each(function(e,t){var n=i(this);n.parents("."+l[0])[0]||"none"===n.css("display")||n.attr({layer:"1"}).hide(),n=null})},s.pt.openLayer=function(){var e=this;r.zIndex=e.config.zIndex,r.setTop=function(e){var t=function(){r.zIndex++,e.css("z-index",r.zIndex+1)};return r.zIndex=parseInt(e[0].style.zIndex),e.on("mousedown",t),r.zIndex}},o.record=function(e){var t=[e.width(),e.height(),e.position().top,e.position().left+parseFloat(e.css("margin-left"))];e.find(".layui-layer-max").addClass("layui-layer-maxmin"),e.attr({area:t})},o.rescollbar=function(e){l.html.attr("layer-full")==e&&(l.html[0].style.removeProperty?l.html[0].style.removeProperty("overflow"):l.html[0].style.removeAttribute("overflow"),l.html.removeAttr("layer-full"))},e.layer=r,r.getChildFrame=function(e,t){return t=t||i("."+l[4]).attr("times"),i("#"+l[0]+t).find("iframe").contents().find(e)},r.getFrameIndex=function(e){return i("#"+e).parents("."+l[4]).attr("times")},r.iframeAuto=function(e){if(e){var t=r.getChildFrame("html",e).outerHeight(),n=i("#"+l[0]+e),a=n.find(l[1]).outerHeight()||0,o=n.find("."+l[6]).outerHeight()||0;n.css({height:t+a+o}),n.find("iframe").css({height:t})}},r.iframeSrc=function(e,t){i("#"+l[0]+e).find("iframe").attr("src",t)},r.style=function(e,t,n){var a=i("#"+l[0]+e),r=a.find(".layui-layer-content"),s=a.attr("type"),f=a.find(l[1]).outerHeight()||0,c=a.find("."+l[6]).outerHeight()||0;a.attr("minLeft");s!==o.type[3]&&s!==o.type[4]&&(n||(parseFloat(t.width)<=260&&(t.width=260),parseFloat(t.height)-f-c<=64&&(t.height=64+f+c)),a.css(t),c=a.find("."+l[6]).outerHeight(),s===o.type[2]?a.find("iframe").css({height:parseFloat(t.height)-f-c}):r.css({height:parseFloat(t.height)-f-c-parseFloat(r.css("padding-top"))-parseFloat(r.css("padding-bottom"))}))},r.min=function(e,t){var a=i("#"+l[0]+e),s=a.find(l[1]).outerHeight()||0,f=a.attr("minLeft")||181*o.minIndex+"px",c=a.css("position");o.record(a),o.minLeft[0]&&(f=o.minLeft[0],o.minLeft.shift()),a.attr("position",c),r.style(e,{width:180,height:s,left:f,top:n.height()-s,position:"fixed",overflow:"hidden"},!0),a.find(".layui-layer-min").hide(),"page"===a.attr("type")&&a.find(l[4]).hide(),o.rescollbar(e),a.attr("minLeft")||o.minIndex++,a.attr("minLeft",f)},r.restore=function(e){var t=i("#"+l[0]+e),n=t.attr("area").split(",");t.attr("type");r.style(e,{width:parseFloat(n[0]),height:parseFloat(n[1]),top:parseFloat(n[2]),left:parseFloat(n[3]),position:t.attr("position"),overflow:"visible"},!0),t.find(".layui-layer-max").removeClass("layui-layer-maxmin"),t.find(".layui-layer-min").show(),"page"===t.attr("type")&&t.find(l[4]).show(),o.rescollbar(e)},r.full=function(e){var t,a=i("#"+l[0]+e);o.record(a),l.html.attr("layer-full")||l.html.css("overflow","hidden").attr("layer-full",e),clearTimeout(t),t=setTimeout(function(){var t="fixed"===a.css("position");r.style(e,{top:t?0:n.scrollTop(),left:t?0:n.scrollLeft(),width:n.width(),height:n.height()},!0),a.find(".layui-layer-min").hide()},100)},r.title=function(e,t){var n=i("#"+l[0]+(t||r.index)).find(l[1]);n.html(e)},r.close=function(e){var t=i("#"+l[0]+e),n=t.attr("type"),a="layer-anim-close";if(t[0]){var s="layui-layer-wrap",f=function(){if(n===o.type[1]&&"object"===t.attr("conType")){t.children(":not(."+l[5]+")").remove();for(var a=t.find("."+s),r=0;r<2;r++)a.unwrap();a.css("display",a.data("display")).removeClass(s)}else{if(n===o.type[2])try{var f=i("#"+l[4]+e)[0];f.contentWindow.document.write(""),f.contentWindow.close(),t.find("."+l[5])[0].removeChild(f)}catch(c){}t[0].innerHTML="",t.remove()}"function"==typeof o.end[e]&&o.end[e](),delete o.end[e]};t.data("isOutAnim")&&t.addClass("layer-anim "+a),i("#layui-layer-moves, #layui-layer-shade"+e).remove(),6==r.ie&&o.reselect(),o.rescollbar(e),t.attr("minLeft")&&(o.minIndex--,o.minLeft.push(t.attr("minLeft"))),r.ie&&r.ie<10||!t.data("isOutAnim")?f():setTimeout(function(){f()},200)}},r.closeAll=function(e){i.each(i("."+l[0]),function(){var t=i(this),n=e?t.attr("type")===e:1;n&&r.close(t.attr("times")),n=null})};var f=r.cache||{},c=function(e){return f.skin?" "+f.skin+" "+f.skin+"-"+e:""};r.prompt=function(e,t){var a="";if(e=e||{},"function"==typeof e&&(t=e),e.area){var o=e.area;a='style="width: '+o[0]+"; height: "+o[1]+';"',delete e.area}var s,l=2==e.formType?'":function(){return''}(),f=e.success;return delete e.success,r.open(i.extend({type:1,btn:["确定","取消"],content:l,skin:"layui-layer-prompt"+c("prompt"),maxWidth:n.width(),success:function(e){s=e.find(".layui-layer-input"),s.focus(),"function"==typeof f&&f(e)},resize:!1,yes:function(i){var n=s.val();""===n?s.focus():n.length>(e.maxlength||500)?r.tips("最多输入"+(e.maxlength||500)+"个字数",s,{tips:1}):t&&t(n,i,s)}},e))},r.tab=function(e){e=e||{};var t=e.tab||{},n="layui-this",a=e.success;return delete e.success,r.open(i.extend({type:1,skin:"layui-layer-tab"+c("tab"),resize:!1,title:function(){var e=t.length,i=1,a="";if(e>0)for(a=''+t[0].title+"";i"+t[i].title+"";return a}(),content:'
    '+function(){var e=t.length,i=1,a="";if(e>0)for(a='
  • '+(t[0].content||"no content")+"
  • ";i'+(t[i].content||"no content")+"";return a}()+"
",success:function(t){var o=t.find(".layui-layer-title").children(),r=t.find(".layui-layer-tabmain").children();o.on("mousedown",function(t){t.stopPropagation?t.stopPropagation():t.cancelBubble=!0;var a=i(this),o=a.index();a.addClass(n).siblings().removeClass(n),r.eq(o).show().siblings().hide(),"function"==typeof e.change&&e.change(o)}),"function"==typeof a&&a(t)}},e))},r.photos=function(t,n,a){function o(e,t,i){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,t(n)},void(n.onerror=function(e){n.onerror=null,i(e)}))}var s={};if(t=t||{},t.photos){var l=t.photos.constructor===Object,f=l?t.photos:{},u=f.data||[],d=f.start||0;s.imgIndex=(0|d)+1,t.img=t.img||"img";var y=t.success;if(delete t.success,l){if(0===u.length)return r.msg("没有图片")}else{var p=i(t.photos),h=function(){u=[],p.find(t.img).each(function(e){var t=i(this);t.attr("layer-index",e),u.push({alt:t.attr("alt"),pid:t.attr("layer-pid"),src:t.attr("layer-src")||t.attr("src"),thumb:t.attr("src")})})};if(h(),0===u.length)return;if(n||p.on("click",t.img,function(){var e=i(this),n=e.attr("layer-index");r.photos(i.extend(t,{photos:{start:n,data:u,tab:t.tab},full:t.full}),!0),h()}),!n)return}s.imgprev=function(e){s.imgIndex--,s.imgIndex<1&&(s.imgIndex=u.length),s.tabimg(e)},s.imgnext=function(e,t){s.imgIndex++,s.imgIndex>u.length&&(s.imgIndex=1,t)||s.tabimg(e)},s.keyup=function(e){if(!s.end){var t=e.keyCode;e.preventDefault(),37===t?s.imgprev(!0):39===t?s.imgnext(!0):27===t&&r.close(s.index)}},s.tabimg=function(e){if(!(u.length<=1))return f.start=s.imgIndex-1,r.close(s.index),r.photos(t,!0,e)},s.event=function(){s.bigimg.hover(function(){s.imgsee.show()},function(){s.imgsee.hide()}),s.bigimg.find(".layui-layer-imgprev").on("click",function(e){e.preventDefault(),s.imgprev()}),s.bigimg.find(".layui-layer-imgnext").on("click",function(e){e.preventDefault(),s.imgnext()}),i(document).on("keyup",s.keyup)},s.loadi=r.load(1,{shade:!("shade"in t)&&.9,scrollbar:!1}),o(u[d].src,function(n){r.close(s.loadi),s.index=r.open(i.extend({type:1,id:"layui-layer-photos",area:function(){var a=[n.width,n.height],o=[i(e).width()-100,i(e).height()-100];if(!t.full&&(a[0]>o[0]||a[1]>o[1])){var r=[a[0]/o[0],a[1]/o[1]];r[0]>r[1]?(a[0]=a[0]/r[0],a[1]=a[1]/r[0]):r[0]'+(u[d].alt||
'+(u.length>1?'':"")+'
'+(u[d].alt||"")+""+s.imgIndex+"/"+u.length+"
",success:function(e,i){s.bigimg=e.find(".layui-layer-phimg"),s.imgsee=e.find(".layui-layer-imguide,.layui-layer-imgbar"),s.event(e),t.tab&&t.tab(u[d],e),"function"==typeof y&&y(e)},end:function(){s.end=!0,i(document).off("keyup",s.keyup)}},t))},function(){r.close(s.loadi),r.msg("当前图片地址异常
是否继续查看下一张?",{time:3e4,btn:["下一张","不看了"],yes:function(){u.length>1&&s.imgnext(!0,!0)}})})}},o.run=function(t){i=t,n=i(e),l.html=i("html"),r.open=function(e){var t=new s(e);return t.index}},e.layui&&layui.define?(r.ready(),layui.define("jquery",function(t){r.path=layui.cache.dir,o.run(layui.$),e.layer=r,t("layer",r)})):"function"==typeof define&&define.amd?define(["jquery"],function(){return o.run(e.jQuery),r}):function(){o.run(e.jQuery),r.ready()}()}(window); -------------------------------------------------------------------------------- /WordReview/static/js/thirdParty/layer/mobile/layer.js: -------------------------------------------------------------------------------- 1 | /*! layer mobile-v2.0.0 Web弹层组件 MIT License http://layer.layui.com/mobile By 贤心 */ 2 | ;!function(e){"use strict";var t=document,n="querySelectorAll",i="getElementsByClassName",a=function(e){return t[n](e)},s={type:0,shade:!0,shadeClose:!0,fixed:!0,anim:"scale"},l={extend:function(e){var t=JSON.parse(JSON.stringify(s));for(var n in e)t[n]=e[n];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var r=0,o=["layui-m-layer"],c=function(e){var t=this;t.config=l.extend(e),t.view()};c.prototype.view=function(){var e=this,n=e.config,s=t.createElement("div");e.id=s.id=o[0]+r,s.setAttribute("class",o[0]+" "+o[0]+(n.type||0)),s.setAttribute("index",r);var l=function(){var e="object"==typeof n.title;return n.title?'

'+(e?n.title[0]:n.title)+"

":""}(),c=function(){"string"==typeof n.btn&&(n.btn=[n.btn]);var e,t=(n.btn||[]).length;return 0!==t&&n.btn?(e=''+n.btn[0]+"",2===t&&(e=''+n.btn[1]+""+e),'
'+e+"
"):""}();if(n.fixed||(n.top=n.hasOwnProperty("top")?n.top:100,n.style=n.style||"",n.style+=" top:"+(t.body.scrollTop+n.top)+"px"),2===n.type&&(n.content='

'+(n.content||"")+"

"),n.skin&&(n.anim="up"),"msg"===n.skin&&(n.shade=!1),s.innerHTML=(n.shade?"
':"")+'
"+l+'
'+n.content+"
"+c+"
",!n.type||2===n.type){var d=t[i](o[0]+n.type),y=d.length;y>=1&&layer.close(d[0].getAttribute("index"))}document.body.appendChild(s);var u=e.elem=a("#"+e.id)[0];n.success&&n.success(u),e.index=r++,e.action(n,u)},c.prototype.action=function(e,t){var n=this;e.time&&(l.timer[n.index]=setTimeout(function(){layer.close(n.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),layer.close(n.index)):e.yes?e.yes(n.index):layer.close(n.index)};if(e.btn)for(var s=t[i]("layui-m-layerbtn")[0].children,r=s.length,o=0;odiv{line-height:22px;padding-top:7px;margin-bottom:20px;font-size:14px}.layui-m-layerbtn{display:box;display:-moz-box;display:-webkit-box;width:100%;height:50px;line-height:50px;font-size:0;border-top:1px solid #D0D0D0;background-color:#F2F2F2}.layui-m-layerbtn span{display:block;-moz-box-flex:1;box-flex:1;-webkit-box-flex:1;font-size:14px;cursor:pointer}.layui-m-layerbtn span[yes]{color:#40AFFE}.layui-m-layerbtn span[no]{border-right:1px solid #D0D0D0;border-radius:0 0 0 5px}.layui-m-layerbtn span:active{background-color:#F6F6F6}.layui-m-layerend{position:absolute;right:7px;top:10px;width:30px;height:30px;border:0;font-weight:400;background:0 0;cursor:pointer;-webkit-appearance:none;font-size:30px}.layui-m-layerend::after,.layui-m-layerend::before{position:absolute;left:5px;top:15px;content:'';width:18px;height:1px;background-color:#999;transform:rotate(45deg);-webkit-transform:rotate(45deg);border-radius:3px}.layui-m-layerend::after{transform:rotate(-45deg);-webkit-transform:rotate(-45deg)}body .layui-m-layer .layui-m-layer-footer{position:fixed;width:95%;max-width:100%;margin:0 auto;left:0;right:0;bottom:10px;background:0 0}.layui-m-layer-footer .layui-m-layercont{padding:20px;border-radius:5px 5px 0 0;background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn{display:block;height:auto;background:0 0;border-top:none}.layui-m-layer-footer .layui-m-layerbtn span{background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn span[no]{color:#FD482C;border-top:1px solid #c2c2c2;border-radius:0 0 5px 5px}.layui-m-layer-footer .layui-m-layerbtn span[yes]{margin-top:10px;border-radius:5px}body .layui-m-layer .layui-m-layer-msg{width:auto;max-width:90%;margin:0 auto;bottom:-150px;background-color:rgba(0,0,0,.7);color:#fff}.layui-m-layer-msg .layui-m-layercont{padding:10px 20px} -------------------------------------------------------------------------------- /WordReview/static/js/thirdParty/layer/theme/default/icon-ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/WordReview/static/js/thirdParty/layer/theme/default/icon-ext.png -------------------------------------------------------------------------------- /WordReview/static/js/thirdParty/layer/theme/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/WordReview/static/js/thirdParty/layer/theme/default/icon.png -------------------------------------------------------------------------------- /WordReview/static/js/thirdParty/layer/theme/default/layer.css: -------------------------------------------------------------------------------- 1 | .layui-layer-imgbar,.layui-layer-imgtit a,.layui-layer-tab .layui-layer-title span,.layui-layer-title{text-overflow:ellipsis;white-space:nowrap}html #layuicss-layer{display:none;position:absolute;width:1989px}.layui-layer,.layui-layer-shade{position:fixed;_position:absolute;pointer-events:auto}.layui-layer-shade{top:0;left:0;width:100%;height:100%;_height:expression(document.body.offsetHeight+"px")}.layui-layer{-webkit-overflow-scrolling:touch;top:150px;left:0;margin:0;padding:0;background-color:#fff;-webkit-background-clip:content;border-radius:2px;box-shadow:1px 1px 50px rgba(0,0,0,.3)}.layui-layer-close{position:absolute}.layui-layer-content{position:relative}.layui-layer-border{border:1px solid #B2B2B2;border:1px solid rgba(0,0,0,.1);box-shadow:1px 1px 5px rgba(0,0,0,.2)}.layui-layer-load{background:url(loading-1.gif) center center no-repeat #eee}.layui-layer-ico{background:url(icon.png) no-repeat}.layui-layer-btn a,.layui-layer-dialog .layui-layer-ico,.layui-layer-setwin a{display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-move{display:none;position:fixed;*position:absolute;left:0;top:0;width:100%;height:100%;cursor:move;opacity:0;filter:alpha(opacity=0);background-color:#fff;z-index:2147483647}.layui-layer-resize{position:absolute;width:15px;height:15px;right:0;bottom:0;cursor:se-resize}.layer-anim{-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.3s;animation-duration:.3s}@-webkit-keyframes layer-bounceIn{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes layer-bounceIn{0%{opacity:0;-webkit-transform:scale(.5);-ms-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-00{-webkit-animation-name:layer-bounceIn;animation-name:layer-bounceIn}@-webkit-keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);-ms-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);-ms-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-01{-webkit-animation-name:layer-zoomInDown;animation-name:layer-zoomInDown}@-webkit-keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);-ms-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}.layer-anim-02{-webkit-animation-name:layer-fadeInUpBig;animation-name:layer-fadeInUpBig}@-webkit-keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);-ms-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);-ms-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-03{-webkit-animation-name:layer-zoomInLeft;animation-name:layer-zoomInLeft}@-webkit-keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}@keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);-ms-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);-ms-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}.layer-anim-04{-webkit-animation-name:layer-rollIn;animation-name:layer-rollIn}@keyframes layer-fadeIn{0%{opacity:0}100%{opacity:1}}.layer-anim-05{-webkit-animation-name:layer-fadeIn;animation-name:layer-fadeIn}@-webkit-keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);transform:translateX(10px)}}@keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);-ms-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}}.layer-anim-06{-webkit-animation-name:layer-shake;animation-name:layer-shake}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.layui-layer-title{padding:0 80px 0 20px;height:42px;line-height:42px;border-bottom:1px solid #eee;font-size:14px;color:#333;overflow:hidden;background-color:#F8F8F8;border-radius:2px 2px 0 0}.layui-layer-setwin{position:absolute;right:15px;*right:0;top:15px;font-size:0;line-height:initial}.layui-layer-setwin a{position:relative;width:16px;height:16px;margin-left:10px;font-size:12px;_overflow:hidden}.layui-layer-setwin .layui-layer-min cite{position:absolute;width:14px;height:2px;left:0;top:50%;margin-top:-1px;background-color:#2E2D3C;cursor:pointer;_overflow:hidden}.layui-layer-setwin .layui-layer-min:hover cite{background-color:#2D93CA}.layui-layer-setwin .layui-layer-max{background-position:-32px -40px}.layui-layer-setwin .layui-layer-max:hover{background-position:-16px -40px}.layui-layer-setwin .layui-layer-maxmin{background-position:-65px -40px}.layui-layer-setwin .layui-layer-maxmin:hover{background-position:-49px -40px}.layui-layer-setwin .layui-layer-close1{background-position:1px -40px;cursor:pointer}.layui-layer-setwin .layui-layer-close1:hover{opacity:.7}.layui-layer-setwin .layui-layer-close2{position:absolute;right:-28px;top:-28px;width:30px;height:30px;margin-left:0;background-position:-149px -31px;*right:-18px;_display:none}.layui-layer-setwin .layui-layer-close2:hover{background-position:-180px -31px}.layui-layer-btn{text-align:right;padding:0 15px 12px;pointer-events:auto;user-select:none;-webkit-user-select:none}.layui-layer-btn a{height:28px;line-height:28px;margin:5px 5px 0;padding:0 15px;border:1px solid #dedede;background-color:#fff;color:#333;border-radius:2px;font-weight:400;cursor:pointer;text-decoration:none}.layui-layer-btn a:hover{opacity:.9;text-decoration:none}.layui-layer-btn a:active{opacity:.8}.layui-layer-btn .layui-layer-btn0{border-color:#1E9FFF;background-color:#1E9FFF;color:#fff}.layui-layer-btn-l{text-align:left}.layui-layer-btn-c{text-align:center}.layui-layer-dialog{min-width:260px}.layui-layer-dialog .layui-layer-content{position:relative;padding:20px;line-height:24px;word-break:break-all;overflow:hidden;font-size:14px;overflow-x:hidden;overflow-y:auto}.layui-layer-dialog .layui-layer-content .layui-layer-ico{position:absolute;top:16px;left:15px;_left:-40px;width:30px;height:30px}.layui-layer-ico1{background-position:-30px 0}.layui-layer-ico2{background-position:-60px 0}.layui-layer-ico3{background-position:-90px 0}.layui-layer-ico4{background-position:-120px 0}.layui-layer-ico5{background-position:-150px 0}.layui-layer-ico6{background-position:-180px 0}.layui-layer-rim{border:6px solid #8D8D8D;border:6px solid rgba(0,0,0,.3);border-radius:5px;box-shadow:none}.layui-layer-msg{min-width:180px;border:1px solid #D3D4D3;box-shadow:none}.layui-layer-hui{min-width:100px;background-color:#000;filter:alpha(opacity=60);background-color:rgba(0,0,0,.6);color:#fff;border:none}.layui-layer-hui .layui-layer-content{padding:12px 25px;text-align:center}.layui-layer-dialog .layui-layer-padding{padding:20px 20px 20px 55px;text-align:left}.layui-layer-page .layui-layer-content{position:relative;overflow:auto}.layui-layer-iframe .layui-layer-btn,.layui-layer-page .layui-layer-btn{padding-top:10px}.layui-layer-nobg{background:0 0}.layui-layer-iframe iframe{display:block;width:100%}.layui-layer-loading{border-radius:100%;background:0 0;box-shadow:none;border:none}.layui-layer-loading .layui-layer-content{width:60px;height:24px;background:url(loading-0.gif) no-repeat}.layui-layer-loading .layui-layer-loading1{width:37px;height:37px;background:url(loading-1.gif) no-repeat}.layui-layer-ico16,.layui-layer-loading .layui-layer-loading2{width:32px;height:32px;background:url(loading-2.gif) no-repeat}.layui-layer-tips{background:0 0;box-shadow:none;border:none}.layui-layer-tips .layui-layer-content{position:relative;line-height:22px;min-width:12px;padding:8px 15px;font-size:12px;_float:left;border-radius:2px;box-shadow:1px 1px 3px rgba(0,0,0,.2);background-color:#000;color:#fff}.layui-layer-tips .layui-layer-close{right:-2px;top:-1px}.layui-layer-tips i.layui-layer-TipsG{position:absolute;width:0;height:0;border-width:8px;border-color:transparent;border-style:dashed;*overflow:hidden}.layui-layer-tips i.layui-layer-TipsB,.layui-layer-tips i.layui-layer-TipsT{left:5px;border-right-style:solid;border-right-color:#000}.layui-layer-tips i.layui-layer-TipsT{bottom:-8px}.layui-layer-tips i.layui-layer-TipsB{top:-8px}.layui-layer-tips i.layui-layer-TipsL,.layui-layer-tips i.layui-layer-TipsR{top:5px;border-bottom-style:solid;border-bottom-color:#000}.layui-layer-tips i.layui-layer-TipsR{left:-8px}.layui-layer-tips i.layui-layer-TipsL{right:-8px}.layui-layer-lan[type=dialog]{min-width:280px}.layui-layer-lan .layui-layer-title{background:#4476A7;color:#fff;border:none}.layui-layer-lan .layui-layer-btn{padding:5px 10px 10px;text-align:right;border-top:1px solid #E9E7E7}.layui-layer-lan .layui-layer-btn a{background:#fff;border-color:#E9E7E7;color:#333}.layui-layer-lan .layui-layer-btn .layui-layer-btn1{background:#C9C5C5}.layui-layer-molv .layui-layer-title{background:#009f95;color:#fff;border:none}.layui-layer-molv .layui-layer-btn a{background:#009f95;border-color:#009f95}.layui-layer-molv .layui-layer-btn .layui-layer-btn1{background:#92B8B1}.layui-layer-iconext{background:url(icon-ext.png) no-repeat}.layui-layer-prompt .layui-layer-input{display:block;width:230px;height:36px;margin:0 auto;line-height:30px;padding-left:10px;border:1px solid #e6e6e6;color:#333}.layui-layer-prompt textarea.layui-layer-input{width:300px;height:100px;line-height:20px;padding:6px 10px}.layui-layer-prompt .layui-layer-content{padding:20px}.layui-layer-prompt .layui-layer-btn{padding-top:0}.layui-layer-tab{box-shadow:1px 1px 50px rgba(0,0,0,.4)}.layui-layer-tab .layui-layer-title{padding-left:0;overflow:visible}.layui-layer-tab .layui-layer-title span{position:relative;float:left;min-width:80px;max-width:260px;padding:0 20px;text-align:center;overflow:hidden;cursor:pointer}.layui-layer-tab .layui-layer-title span.layui-this{height:43px;border-left:1px solid #eee;border-right:1px solid #eee;background-color:#fff;z-index:10}.layui-layer-tab .layui-layer-title span:first-child{border-left:none}.layui-layer-tabmain{line-height:24px;clear:both}.layui-layer-tabmain .layui-layer-tabli{display:none}.layui-layer-tabmain .layui-layer-tabli.layui-this{display:block}.layui-layer-photos{-webkit-animation-duration:.8s;animation-duration:.8s}.layui-layer-photos .layui-layer-content{overflow:hidden;text-align:center}.layui-layer-photos .layui-layer-phimg img{position:relative;width:100%;display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-imgbar,.layui-layer-imguide{display:none}.layui-layer-imgnext,.layui-layer-imgprev{position:absolute;top:50%;width:27px;_width:44px;height:44px;margin-top:-22px;outline:0;blr:expression(this.onFocus=this.blur())}.layui-layer-imgprev{left:10px;background-position:-5px -5px;_background-position:-70px -5px}.layui-layer-imgprev:hover{background-position:-33px -5px;_background-position:-120px -5px}.layui-layer-imgnext{right:10px;_right:8px;background-position:-5px -50px;_background-position:-70px -50px}.layui-layer-imgnext:hover{background-position:-33px -50px;_background-position:-120px -50px}.layui-layer-imgbar{position:absolute;left:0;bottom:0;width:100%;height:32px;line-height:32px;background-color:rgba(0,0,0,.8);background-color:#000\9;filter:Alpha(opacity=80);color:#fff;overflow:hidden;font-size:0}.layui-layer-imgtit *{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:12px}.layui-layer-imgtit a{max-width:65%;overflow:hidden;color:#fff}.layui-layer-imgtit a:hover{color:#fff;text-decoration:underline}.layui-layer-imgtit em{padding-left:10px;font-style:normal}@-webkit-keyframes layer-bounceOut{100%{opacity:0;-webkit-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes layer-bounceOut{100%{opacity:0;-webkit-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);-ms-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-close{-webkit-animation-name:layer-bounceOut;animation-name:layer-bounceOut;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.2s;animation-duration:.2s}@media screen and (max-width:1100px){.layui-layer-iframe{overflow-y:auto;-webkit-overflow-scrolling:touch}} -------------------------------------------------------------------------------- /WordReview/static/js/thirdParty/layer/theme/default/loading-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/WordReview/static/js/thirdParty/layer/theme/default/loading-0.gif -------------------------------------------------------------------------------- /WordReview/static/js/thirdParty/layer/theme/default/loading-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/WordReview/static/js/thirdParty/layer/theme/default/loading-1.gif -------------------------------------------------------------------------------- /WordReview/static/js/thirdParty/layer/theme/default/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/WordReview/static/js/thirdParty/layer/theme/default/loading-2.gif -------------------------------------------------------------------------------- /WordReview/static/js/util.js: -------------------------------------------------------------------------------- 1 | function getQueryString(name) { 2 | var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); 3 | var r = window.location.search.substr(1).match(reg); 4 | if (r != null) return unescape(r[2]); return null; 5 | } 6 | 7 | function copy2Clipboard(content, idN) { 8 | // var input = document.createElement('TEXTAREA'); 9 | var input = document.getElementById(idN); 10 | input.value = content; // 修改文本框的内容 11 | input.select(); // 选中文本 12 | if (document.execCommand('copy')) { 13 | } else { 14 | console.error('复制失败'); 15 | } 16 | input.blur(); 17 | } 18 | 19 | function readText(word, source = 'baidu') { 20 | if (source == 'baidu') { 21 | document.getElementById('bd-tts').innerHTML = 22 | ''; 25 | document.getElementById('bd-tts-audio').play(); 26 | } else if (source == 'browser') { 27 | let speechInstance = new SpeechSynthesisUtterance(word); 28 | speechSynthesis.speak(speechInstance); 29 | } 30 | } -------------------------------------------------------------------------------- /WordReview/static/media/muyi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/WordReview/static/media/muyi.png -------------------------------------------------------------------------------- /WordReview/static/media/vocabulary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/WordReview/static/media/vocabulary.png -------------------------------------------------------------------------------- /WordReview/static/scss/review.scss: -------------------------------------------------------------------------------- 1 | #meaning-box{ 2 | width: 300px; 3 | min-height: 200px; 4 | /* border: 1px solid grey; */ 5 | margin: 20px; 6 | justify-content: center; 7 | display: flex; 8 | padding: 20px; 9 | cursor: pointer; 10 | } 11 | 12 | #tmpl-index, #tmpl-phonetic{ 13 | text-align: center; 14 | color: grey; 15 | font-size: 13px; 16 | } 17 | 18 | #tmpl-total-num{ 19 | width: 100%; 20 | text-align: center; 21 | color: white; 22 | } 23 | 24 | .btn-div{ 25 | justify-content: space-around; 26 | display: flex; 27 | margin-top: 30px; 28 | width: 320px; 29 | } 30 | 31 | .progress-div{ 32 | display: flex; 33 | justify-content: center; 34 | } 35 | 36 | .bg-palevioletred{ 37 | background-color: palevioletred; 38 | } 39 | 40 | .btn-jump{ 41 | box-shadow: 0 .5rem 1rem rgba(0,0,0,.15); 42 | border-radius: .25rem!important; 43 | width: 18px; 44 | text-align: center; 45 | cursor: pointer; 46 | background-color: grey; 47 | color: white; 48 | height: 27px; 49 | } 50 | 51 | .last-forget{ 52 | color: red; 53 | } 54 | 55 | .last-remember{ 56 | color: grey; 57 | } 58 | 59 | .sort-array{ 60 | margin: 0 5px; 61 | cursor: pointer; 62 | } 63 | 64 | #jump-index{ 65 | background-color: transparent !important; 66 | width: 50px; 67 | } 68 | 69 | #btn-quick-jump{ 70 | color: grey; 71 | } 72 | 73 | .side-card{ 74 | width: 220px; 75 | // min-height: 5rem; 76 | height: inherit; 77 | } 78 | 79 | #echarts-left{ 80 | width: 200px; 81 | height: 15rem; 82 | } 83 | 84 | #echarts-bottom{ 85 | width: 230px; 86 | height: 10rem; 87 | } 88 | 89 | #tmpl-sentence{ 90 | margin-left: 5px; 91 | width: 540px; 92 | #s{ 93 | em{ 94 | color: red; 95 | } 96 | } 97 | } 98 | 99 | 100 | 101 | #tmpl-note{ 102 | z-index: -1; 103 | margin: 22px 0px 0px 20px; 104 | padding: 10px 10px; 105 | color: #c46943; 106 | font-size: 12px; 107 | cursor: text; 108 | 109 | width: 90%; 110 | min-height: 50px; 111 | max-height: 150px; 112 | _height: 120px; 113 | outline: 0; 114 | border: 1px solid #a0b3d6; 115 | border-radius: .25rem; 116 | word-wrap: break-word; 117 | overflow-x: hidden; 118 | overflow-y: auto; 119 | _overflow-y: visible; 120 | } 121 | 122 | .d-n-note{ 123 | display: none; 124 | } 125 | 126 | #active-note{ 127 | cursor: pointer; 128 | min-height: 150px; 129 | } 130 | 131 | .sentence-zh{ 132 | color: grey !important; 133 | font-size: 12px; 134 | } 135 | 136 | 137 | i{ 138 | padding-right: 5px; 139 | } 140 | 141 | .icon-disabled{ 142 | color: #dadada; 143 | -webkit-text-stroke: 0.5px #959595; 144 | } 145 | 146 | .icon-star-div{ 147 | cursor: pointer; 148 | .icon-enabled{ 149 | color: #ffca28 !important; 150 | -webkit-text-stroke: 0px !important; 151 | } 152 | .icon-pan-enabled{ 153 | color: #f5eddc; 154 | -webkit-text-stroke: 0.5px #ffca28; 155 | } 156 | } 157 | 158 | .icon-ok-div{ 159 | cursor: pointer; 160 | .icon-enabled{ 161 | color: #73c991 !important; 162 | -webkit-text-stroke: 0px !important; 163 | } 164 | .icon-pan-enabled{ 165 | color: #c0e7ce; 166 | -webkit-text-stroke: 0.5px #73c991; 167 | } 168 | } 169 | 170 | .icon-circle-div{ 171 | font-size: 13px; 172 | cursor: pointer; 173 | .icon-enabled{ 174 | color: #73c991 !important; 175 | -webkit-text-stroke: 0px !important; 176 | } 177 | .icon-pan-enabled{ 178 | color: #c0e7ce; 179 | -webkit-text-stroke: 0.5px #73c991; 180 | } 181 | } 182 | 183 | .icon-cloud-div{ 184 | font-size: 12px; 185 | cursor: pointer; 186 | .icon-enabled{ 187 | color: #69a0e7 !important; 188 | -webkit-text-stroke: 0px !important; 189 | } 190 | .icon-pan-enabled{ 191 | color: #aed4eb; 192 | -webkit-text-stroke: 0.5px #69a0e7; 193 | } 194 | } 195 | 196 | .word-block{ 197 | margin: 0 3px; 198 | 199 | div{ 200 | text-align: center; 201 | } 202 | 203 | .word-break{ 204 | font-size: 20px; 205 | } 206 | .word-explain{ 207 | font-size: 16px; 208 | color: #c46943; 209 | // text-align: center; 210 | background: #f3f4f4; 211 | border-radius: 0.25rem; 212 | padding: 0px 2px; 213 | } 214 | } 215 | 216 | .break-words{ 217 | display: flex; 218 | justify-content: center; 219 | flex-direction: row; 220 | } 221 | 222 | .word-display{ 223 | font-size: 2.5rem; 224 | } 225 | 226 | .mnemonic-explain{ 227 | font-size: 10pt; 228 | } 229 | 230 | #bottom-box{ 231 | div{ 232 | width: 100%; 233 | // max-width: 400px; 234 | margin: 0px 5px; 235 | } 236 | } 237 | 238 | .nav-item{ 239 | .enabled{ 240 | color: black !important; 241 | } 242 | } 243 | 244 | #tmpl-derivative{ 245 | color: grey; 246 | } 247 | #tmpl-antonym{ 248 | color: palevioletred; 249 | } 250 | #tmpl-synonym{ 251 | color: dodgerblue; 252 | } 253 | 254 | .side-card{ 255 | .recent{ 256 | background-color: yellow; 257 | font-weight: bolder; 258 | text-shadow: 0.5px 0.5px 0.1px #1d0101, 259 | // color: red; 260 | } 261 | } 262 | 263 | .sticky{ 264 | position: sticky; 265 | top: 0px; 266 | z-index: 100; 267 | } 268 | 269 | // .word-mnemonic{ 270 | // // margin-top: 10px; 271 | // font-size: 14px; 272 | // } 273 | 274 | .mnemonic-card{ 275 | margin-bottom: 15px !important; 276 | box-shadow: 0 .5rem 0.7rem rgba(42, 21, 104, 0.25); 277 | background-color: #fefeff; 278 | border-radius: .25rem!important; 279 | font-size: 14px; 280 | padding: 5px; 281 | 282 | i{ 283 | margin-left: 5px; 284 | } 285 | 286 | .mnemonic-card-footer{ 287 | font-size: 10px; 288 | text-align: right; 289 | } 290 | } -------------------------------------------------------------------------------- /WordReview/templates/base.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title {% block title %}{% endblock %} 5 | 6 | //- {% load sass_tags %} 7 | meta(http-equiv="Access-Control-Allow-Origin" content="*") 8 | 9 | link(href="/static/css/thirdParty/bootstrap.min.css" rel="stylesheet") 10 | link(href="/static/font/fontawesome/font-awesome.min.css" rel="stylesheet") 11 | link(href="/static/css/base.css" rel="stylesheet") 12 | script(src="/static/js/thirdParty/jquery/jquery-3.5.1.min.js") 13 | script(src="/static/js/thirdParty/jquery/jquery.tmpl.min.js") 14 | script(src="/static/js/thirdParty/layer/layer.js") 15 | 16 | script(src="/static/js/util.js") 17 | {% block css %}{% endblock %} 18 | 19 | body 20 | {% block content %}{% endblock %} -------------------------------------------------------------------------------- /data/sample/sample.csv: -------------------------------------------------------------------------------- 1 | List,Unit,Index,word,mean 2 | 0,0,0,regress, v. 倒退;回归;退化 3 | 0,0,1,progress," n. 进步;进展;进程;前进;行进 4 | v. 进步;改进;进展;前进;行进;(时间上)推移,流逝 " 5 | 0,0,2,ingress, n. 进入;进入权;入境权 6 | 0,0,3,improvise, v. 临时拼凑;临时做;即兴创作(音乐、台词、演讲词等) 7 | 0,0,4,benefactor, n. 施主;捐款人;赞助人 8 | 0,0,5,manufacturer, n. 生产者;制造者;生产商 9 | 0,0,6,shelter," n. 居所;住处;遮蔽,庇护,避难(避雨、躲避危险或攻击);(尤指用以躲避风雨或攻击的)遮蔽物,庇护处,避难处 10 | v. 保护;掩蔽;躲避(风雨或危险) " 11 | 0,0,7,shield," n. 盾(牌);保护人;保护物;掩护物;屏障;(保护机器和操作者的)护罩,防护屏,挡板 12 | v. 保护某人或某物(免遭危险、伤害或不快);给…加防护罩 " 13 | 0,0,8,shell," n. (蛋、坚果、某些种子和某些动物的)壳;壳状物;炮弹 14 | v. 炮击;给…去壳 " 15 | 0,0,9,fetter," v. 束缚;限制,抑制(某人的自由);给(囚犯)上脚镣 16 | n. 束缚;桎梏;羁绊;脚镣 " 17 | 0,1,0,fiend, n. 恶魔般的人;残忍的人;令人憎恶的人;…迷;…狂;爱好者;魔鬼;恶魔 18 | 0,1,1,fiendish, adj. 恶魔般的;残忍的;令人憎恶的;(常令人不快地)巧妙复杂的;极其困难的 19 | 0,1,2,womanish, adj. 脂粉气的;娘娘腔的;更适合女性的 20 | 0,1,3,mannish, adj. 像男人的;男子气的;男性化的 21 | 0,1,4,womanly, adj. 女性特有的;女子般的;适合女人的 22 | 0,1,5,manly, adj. 有男子汉气概的;强壮的 23 | 0,2,0,spout," n. (容器的)嘴;(喷出的)水柱,液体柱 24 | v. 喷出;喷射;喷水;滔滔不绝地说;喋喋不休地说 " 25 | 0,2,1,sprout," v. 发芽;抽芽;抽条;生长;出现;(使)涌现出;长出(某物);(某物)长出 26 | n. 苗;新芽;嫩枝 " 27 | 0,2,2,chrysanthemum, n. 菊花 28 | 0,2,3,pestilence, n. 瘟疫 29 | 0,2,4,morose, adj. 阴郁的;脾气不好的;闷闷不乐的 30 | 0,2,5,precarious, adj. 不稳的;不确定的;不保险的;危险的;摇摇欲坠的;不稳固的 31 | 0,2,6,avalanche, n. 雪崩;山崩 32 | 1,0,0,adjure, v. 要求;命令 33 | 1,0,1,abjure, v. 公开保证放弃,声明放弃(信念、行为、活动) 34 | 1,0,2,conjure, v. 变魔术;变戏法;使…变戏法般地出现(或消失) 35 | 1,0,3,perjure, v. 作伪证;发假誓 36 | 1,0,4,jury, n. 陪审团;(比赛的)评判委员会,裁判委员会 37 | 1,0,5,perjury, n. 伪证;伪誓;伪证罪 38 | 1,0,6,espouse, v. 支持,拥护,赞成(信仰、政策等) 39 | 1,0,7,spouse," n. 配偶 40 | vt. 和…结婚 " 41 | 1,0,8,spawn," v. 产卵;引发;引起;导致;造成 42 | n. (鱼、蛙等的)卵 " 43 | 1,0,9,forgo, v. 放弃,弃绝(想做的事或想得之物) 44 | 1,0,10,forsake, v. 抛弃,遗弃,离开(尤指不履行责任);摒弃,离开(尤指喜爱的事物) 45 | 1,0,11,forswear, v. 放弃;发誓戒除 46 | 1,0,12,renounce, v. 声明放弃;宣布放弃;宣布与…决裂;宣布摒弃;宣布断绝与…的关系 47 | 1,0,13,relinquish, v. (尤指不情愿地)放弃 48 | 1,1,0,omen, n. 预兆;前兆;征兆 49 | 1,1,1,abominate, v. 憎恨;憎恶;厌恶;极其讨厌 50 | 1,1,2,ominous, adj. 预兆的;恶兆的;不吉利的 51 | 1,1,3,abhor, v. (尤指因道德原因而)憎恨,厌恶,憎恶 52 | 1,1,4,detest, v. 厌恶;憎恨;讨厌 53 | 1,1,5,loathe, v. 极不喜欢;厌恶 54 | 1,1,6,loath, adj. 不情愿;不乐意;勉强 55 | 1,1,7,oath, n. 宣誓;誓言;(表示愤怒、惊异等的)咒骂,诅咒的话 56 | 1,1,8,horrible, adj. 极坏的;十分讨厌的;可恶的;令人震惊的;恐怖的;不友善的;讨厌的;不厚道的 57 | 1,1,9,deforest, v. 砍掉(某地)的树林;毁掉(某地)的森林 58 | 1,1,10,dehydrate, v. 使(食物)脱水;(身体)失水,脱水;使(身体)脱水 59 | 1,2,0,abstinence, n. (因道德、宗教或健康原因对饮食、酒、色等的)节制;禁欲 60 | 1,2,1,abstain, v. (投票时)弃权;戒;戒除;离开;回避  61 | 1,2,2,abstain, v. (投票时)弃权;戒;戒除;离开;回避  62 | 1,2,3,abstract," adj. 抽象的(与个别情况相对);纯理论的;抽象的(与具体经验相对);抽象(派)的 63 | n. 抽象派艺术作品;(文献等的)摘要,概要 64 | v. 把…抽象出;提取;抽取;分离;写出(书等的)摘要 " 65 | 1,2,4,abscond, v. 逃走;逃遁;(携款)潜逃 66 | 1,2,5,absolve, v. 宣告…无罪;判定…无责;赦免…的罪 67 | 1,2,6,maintain, v. 维持;保持;维修;保养;坚持(意见);固执己见 68 | 1,2,7,detain, v. 拘留;扣押;耽搁;留住;阻留 69 | 1,2,8,retain, v. 保持;持有;保留;继续拥有;继续容纳;聘请(律师等) 70 | 1,2,9,pertain, v. 存在;适用 71 | 1,2,10,distain, v. 使变色,弄脏,伤害名誉 72 | 1,2,11,disdain," n. 鄙视;蔑视;鄙弃 73 | v. 鄙视;蔑视;鄙弃;不屑(做某事) " 74 | 1,2,12,pertinent, adj. 有关的;恰当的;相宜的 75 | 1,3,0,acerbic, adj. 尖刻的;严厉的 76 | 1,3,1,exacerbate, v. 使恶化;使加剧;使加重 77 | 1,3,2,acrid, adj. (气、味)辛辣的,难闻的,刺激的 78 | 1,3,3,acrimonious, adj. 尖刻的;讥讽的;激烈的 79 | 1,3,4,acme, n. 顶峰;顶点;典范 80 | 1,3,5,acumen, n. 精明;敏锐 81 | 2,0,0,superfluous, adj. 过剩的;过多的;多余的 82 | 2,0,1,affluence, n. 富裕;富足 83 | 2,0,2,effluvia," n. 废气;臭气;恶臭;无声放电;(臭气、液体等的)流出,外流,放出,散出 84 | effluvium的复数 " 85 | 2,0,3,fluctuate, v. (大小、数量、质量等)波动;(在…之间)起伏不定 86 | 2,0,4,flux," n. 不断的变动;不停的变化;通量;流动 87 | v. 熔化;熔解;流出 " 88 | 2,0,5,influx, n. (人、资金或事物的)涌入,流入 89 | 2,1,0,affront," n. 侮辱;冒犯 90 | v. 侮辱;冒犯 " 91 | 2,1,1,confront, v. 使…无法回避;降临于;处理,解决(问题或困境);面对;对抗;与(某人)对峙 92 | 2,1,2,effrontery, n. 厚颜无耻的行为;傲慢鲁莽的举止 93 | 2,1,3,revile, v. 辱骂;斥责 94 | 2,1,4,vilify, v. 污蔑;诽谤;诋毁;中伤 95 | 2,1,5,abuse," n. 滥用;妄用;虐待;辱骂;恶语 96 | v. 滥用(以致危害健康);滥用,妄用(权力、所知所闻);虐待;性虐待;伤害 " 97 | 2,1,6,evil," adj. 恶毒的;邪恶的;有害的;道德败坏的;恶魔的;罪恶的 98 | n. 邪恶;罪恶;恶行;害处;坏处;弊端 " 99 | 2,1,7,vile, adj. 糟糕透顶的;可恶的;极坏的;邪恶的;令人完全不能接受的 100 | 2,1,8,abusive, adj. 辱骂的;恶语的;毁谤的;虐待的 101 | 2,1,9,disabuse, v. 去掉(某人)的错误想法;使省悟 102 | 2,1,10,peruse, v. 细读;研读 103 | 2,1,11,correct," adj. 准确无误的;精确的;正确的;恰当的;合适的;(举止言谈)符合公认准则的,得体的 104 | v. 改正;纠正;修正;批改;改;指出错误 " 105 | 2,2,0,alleviate, v. 减轻;缓和;缓解 106 | 2,2,1,allay, v. 减轻(尤指情绪) 107 | 2,2,2,levity, n. 轻率的举止;轻浮;轻佻 108 | 2,2,3,levy," n. 征收额;(尤指)税款 109 | v. 征收;征(税) " 110 | 2,2,4,gravity, n. 重力;地球引力;严重性;严肃;庄严 111 | 2,2,5,rescind, v. 废除;取消;撤销 112 | 2,2,6,scissors," n. 剪刀 113 | v. 剪断;删除 114 | scissor的第三人称单数 " 115 | 2,2,7,slay, v. (在战争或搏斗中)杀,杀死;杀害;残害;谋杀;深深打动;迷住 116 | 2,2,8,flay, v. 剥(死人或动物的皮);毒打,狠狠鞭打(直至皮开肉绽);严厉批评 117 | 2,2,9,waylay, v. 拦截(尤其是为了谈话或袭击);拦路 118 | 3,0,0,aground," adv. (指船)搁浅 119 | adj. 搁浅的;在地面上的 " 120 | 3,0,1,aloof, adj. 冷漠;冷淡 121 | 3,0,2,afoot, adj. 计划中;进行中 122 | 3,0,3,alight," adj. 燃烧;着火;容光焕发;兴奋 123 | v. 降落;飞落;从(公共汽车、火车等)下来 " 124 | 3,0,4,akin, adj. 相似的;类似的 125 | 3,0,5,apathy, n. 冷漠;淡漠 126 | 3,0,6,sympathy, n. 同情;赞同;支持;意气相投;志同道合 127 | 3,0,7,antipathy, n. 厌恶;反感 128 | 3,0,8,frigid, adj. 性冷淡的;达不到性高潮的;寒冷的;严寒的;冷淡的 129 | 3,0,9,offish, 冷漠的;冷淡的 130 | 3,0,10,passion, n. 强烈情感;激情;盛怒;激愤;强烈的爱(尤指两性间的) 131 | 3,1,0,altruism, n. 利他主义;利他;无私 132 | 3,1,1,egoism, n. 利己主义;自高自大;自负;自我主义 133 | 3,1,2,egocentric, adj. 以自我为中心的;自私自利的 134 | 3,1,3,alternate," adj. 交替的;轮流的;间隔的;每隔(…天等)的 135 | v. 使交替;使轮流;交替;轮流 136 | n. 代替者;代理人;候补者 " 137 | 3,1,4,altercate," 争论,口角 " 138 | 3,1,5,adulterate, v. (在饮食中)掺杂,掺假 139 | 3,1,6,alternative," n. 可供选择的事物 140 | adj. 可供替代的;非传统的;另类的 " 141 | 3,2,0,amalgam, n. 混合物;综合体;汞合金,汞齐(尤用于补牙) 142 | 3,2,1,amalgamate, v. (使)合并,联合;使混合;使合并 143 | 3,3,0,ambush," n. 伏击;埋伏 144 | v. 伏击 " 145 | 3,3,1,lush," adj. 茂盛的;茂密的;草木繁茂的;华丽舒适的;豪华的 146 | n. 酒;酒鬼;(英)勒什 147 | vi. 喝醉 " 148 | 3,3,2,blush," v. (因尴尬或害羞)脸红,涨红了脸;(因某事)羞愧,尴尬 149 | n. (因难堪、羞愧)面部泛起的红晕 " 150 | 3,3,3,plush," n. (丝或棉的)长毛绒 151 | adj. 舒适的;豪华的 " 152 | 3,3,4,gush," v. (从…中)喷出,涌出,冒出;大量涌出,大量泄出(液体);过分称赞;夸张地表现对…的感情;装腔作势 153 | n. 喷出;涌出;冒出;(感情的)迸发,爆发,发作 " 154 | 3,3,5,hush," v. 安静;别说话;别叫喊;使安静下来;使停止说话(或叫喊等) 155 | n. 寂静;鸦雀无声 " 156 | 4,0,0,amorous, adj. 表示性爱的;含情脉脉的 157 | 4,0,1,amour, n. (尤指秘密的)恋情;风流韵事 158 | 4,0,2,amiable, adj. 和蔼可亲的;亲切友好的 159 | 4,0,3,amicable, adj. 心平气和的;友善的 160 | 4,0,4,amity, n. 和睦;友好 161 | 4,0,5,enmity, n. 敌意;敌对;仇恨 162 | 4,0,6,enamor, v. 使倾心,使迷恋 163 | 4,0,7,pamper, v. 细心照顾;精心护理;娇惯;纵容 164 | 4,0,8,hostile, adj. 敌意的;敌对的;坚决否定;强烈反对;有阻碍的;不利的 165 | 4,1,0,ambition, n. 追求的目标;夙愿;野心;雄心;志向;抱负 166 | 4,1,1,amphibian, n. 两栖动物 167 | 4,1,2,ambiguous, adj. 模棱两可的;含混不清的;不明确的 168 | 4,1,3,ambivalent, adj. (忧喜参半、好坏参半等)矛盾情绪的 169 | 4,1,4,ambidextrous, adj. 左右手都灵巧的;左右开弓的 170 | 4,1,5,ambience, n. 环境;气氛;格调 -------------------------------------------------------------------------------- /data/sample/sample.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/data/sample/sample.xlsx -------------------------------------------------------------------------------- /doc/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 更新日志 2 | 3 | - update: 导入数据时对表头进行检查([Issue#13](https://github.com/Benature/WordReview/issues/13))@08-19 4 | - feature: 新增听写功能(输入模式)([Issue#10](https://github.com/Benature/WordReview/issues/10))@07-09 5 | 6 | 7 |
2020.06 8 | 9 | - update: 导入报错在命令行输出完整报错 @06-16 10 | 11 |
12 | 13 |
2020.05 14 | 15 | - feature: 英文助记法爬取+渲染 @05-22 16 | - update: 单词标记从正向标记恢复为普通标记时,对数据库所有相同单词刷新标记 @05-20 17 | - update: `昨日重现`支持自定义每次词表长度 @05-19 18 | - feature: `昨日重现`复习词表 @05-09 19 | - feature: 单词标记增加`很熟悉` @05-09 20 | - update: 同一个 List 可以重复进行艾宾浩斯复习安排 @05-09 21 | - update: 导入单词`Unit`列取消为必选 @05-09 22 | - feature: 复习页进度条 @05-09 23 | - feature: 复习页增加关于记忆率的分布色条 @05-08 24 | - feature: 艾宾浩斯复习安排之外的自主复习日期记录与渲染 @05-08 25 | - update: 目录结构变更 @05-07 26 | - feature: 区分渲染数据库`Words`和`Review`的`flag`难度标记 @05-07 27 | - feature: `相关词`栏内对近期复习单词做高亮 @05-05 28 | 29 |
30 | 31 |
2020.04 32 | 33 | - feature: wiki quote 每日名言(主页) @04-29 34 | - feature: 新增两个单词网页(助记、近义词)跳转 @04-28 35 | - update: 剪贴板复制后自动离焦`input` @04-28 36 | - update: 接口例句红色高亮([Issue#7](https://github.com/Benature/WordReview/issues/7#issuecomment-620127755)) @04-28 37 | - fix: `font awesome` 路径错误([Issue#7](https://github.com/Benature/WordReview/issues/7)) @04-27 38 | - update: 网页导入数据库支持例句、助记法、音标、近、反、派词([Issue#7](https://github.com/Benature/WordReview/issues/7)) @04-27 39 | - fix: 艾宾浩斯日历安排采用《杨鹏 17 天》形式 @04-26 40 | - feature: 新增`近`、`反`、`派`词字段与渲染 @04-24 41 | - fix: 0.2.0 版本 dmg 报错 @04-21 42 | - release: version 0.2.0 @04-19 43 | - update: 导航栏样式增加激活状态(`.enabled`) @04-18 44 | - update: 配置文件更换为`.conf`文件(以支持默认参数) @04-18 45 | - feature: 命令行启动后自动打开浏览器 ([Issue#4](https://github.com/Benature/WordReview/issues/4)) @04-16 46 | - feature: 新增[在线预览](https://benature.github.io/WordReview/) @04-16 47 | - feature: 对的后端爬虫 API ([Issue#2](https://github.com/Benature/WordReview/issues/2)) @04-15 48 | - update: Note 区高度自适应 @04-13 49 | - update: 修改 Note 区显示渲染逻辑 @04-13 50 | - feature: 对记忆之沙内容进行快捷键复制 @04-13 51 | - feature: 单词的`flag`新增`已掌握` @04-13 52 | - feature: Chrome Extension 谷歌浏览器插件:记忆之沙助记法显示 @04-12 53 | - update: 错不过三:不认识三次后强制不再背该词 @04-11 54 | - update: 无论状态,错一次后需重背一次该词 @04-11 55 | - feature: 例句支持关键词高亮 @04-11 56 | - update: 未显示释义不能选择是否认识 @04-11 57 | - update: 优化复习页面布局 @04-10 58 | - feature: 新增`助记法`与`音标`字段 @04-10 59 | - feature: 新增预习(学习状态) @04-08 60 | - update: 优化获取单词键值对处理 @04-08 61 | - feature: 新增 [WebsterVocabularyBuilder](https://www.zhihu.com/question/27538740) 是否收录字段 @04-08 62 | - fix: 日历页面任务过期太久引发页面渲染失败 @04-05 63 | - update: `note` 光标离焦后自动更新,不必点击`我记得`或`不认识` @04-05 64 | - feature: 主页显示近期记忆率 @04-05 65 | - feature: 词根词缀词源拆词渲染 @04-05 66 | - feature: `太简单`与`重难词`标记 @04-05 67 | - feature: 离开`review`页面前询问(防止手误离开页面) @04-02 68 | - feature: 增加 **例句** 显示 @04-02 69 | - update: 单个单词进度条改为左记右忘 @04-01 70 | - feature: 词表初始化排序设置支持叠加排序 @04-01 71 | 72 |
73 | 74 |
2020.03 75 | 76 | - update: 增加添加笔记快捷键`N` @03-24 77 | - update: 重现模式在 `背词数目==已背单词+50` 后自动关闭一次(防止无脑过词) @03-24 78 | - update: 增加重现模式快捷键`R` @03-18 79 | - update: `note` 输入框无视快捷键 @03-18 80 | - update: 重现模式在背词数目超过词表长度 50 次后自动关闭一次(防止无脑过词) @03-17 81 | - update: 历史曲线 X 轴 label 从数字改为单词 @03-01 82 | 83 |
84 | 85 |
2020.02 86 | 87 | - feature: 背单词的重现模式 ([PR#1](https://github.com/Benature/WordReview/pull/1)) @02-29 88 | - release: 打包可执行文件 @02-28 89 | - feature: 新增导入单词本页面 @02-27 90 | - feature: 笔记框默认隐藏,点击显示(增加有无笔记之对比) @02-27 91 | - update: 优化线型图显示(0 起) @02-27 92 | - fix: 日历显示月份 bug 修复 @02-27 93 | - more: 略······ 94 | 95 |
96 | -------------------------------------------------------------------------------- /doc/database_init.md: -------------------------------------------------------------------------------- 1 | # 数据库初始化导入 2 | 3 | 导入数据的方法有以下两种: 4 | 5 | ## 网页导入 6 | 7 | 关于导入的文件格式,请参考[这个文件](https://github.com/Benature/WordReview/raw/ben/data/sample/sample.xlsx),或者在线预览看[这里](../data/sample/sample.csv)。注意表头要一致哦,区分大小写。 8 | 9 | | 表头 | 含义 | 必/选 | 10 | | :--------: | :--------: | :----: | 11 | | List | List 序号 | 必填 | 12 | | Unit | Unit 序号 | 可选 | 13 | | Index | Index 序号 | 必填 | 14 | | word | 英文单词 | 必填 | 15 | | mean | 单词释义 | 必填 | 16 | | mnemonic | 助记法 | *可选* | 17 | | phonetic | 音标 | *可选* | 18 | | antonym | 反义词 | *可选* | 19 | | synonym | 近义词 | *可选* | 20 | | derivative | 派生词 | *可选* | 21 | 22 | [网页版的导入的具体说明,另开一页,请点这里。](https://benature.notion.site/Word-Review-fa8ddbab5afa43278e2005ce8b7b9a2a) 23 | 24 | 25 | ## 自定义导入 / 更新 26 | 27 | 自定义导入需要编写代码,参考`apps/review/src/init_db.py`,然后打开 ,当你网页加载完成,说明数据库导入结束了,或者你也可以看下 terminal,原始代码是导入一个单词都打印出来了,你可以看到哗啦啦的一片(不要多次访问这个页面,否则重复导入数据)。 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | >上面方法仅支持基础字段,如果想增加导入的信息(如音标、例句等),可以在`apps/review/src/init_db.py`参考代码并修改,或者也可以联系我来帮你加,推荐通过 [Issue](https://github.com/Benature/WordReview/issues) 来反馈。 36 | 37 | --- 38 | 39 | 一些资源整合: 40 | - [IELTS 绿皮书](https://blog.csdn.net/M_sdn/article/details/85532520?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task) 41 | - [再要你命三千](https://github.com/liurui39660/3000) 42 | - [TOEFL 曲根单词](https://github.com/yihui-he/TOEFL-10000-0) 43 | - [一个强大开源中英字典](https://github.com/skywind3000/ECDICT) 44 | 45 | >如果🔍搜索资料有困难的话,可以在 [Issue](https://github.com/Benature/WordReview/issues) 说一下,我可以试着帮忙找下。 46 | 47 | 欢迎大家补充 48 | -------------------------------------------------------------------------------- /doc/demo.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | wordArray = [{ 4 | model: "review.review", 5 | pk: 27, 6 | fields: { 7 | word: "abandon", 8 | total_num: 4, 9 | forget_num: 0, 10 | rate: 0, 11 | LIST: 0, 12 | UNIT: 0, 13 | INDEX: 0, 14 | BOOK: "GRE3000", 15 | history: "1111", 16 | flag: 0, 17 | mean: " v. (不顾责任、义务等)离弃,遗弃,抛弃;(不得已而)舍弃,丢弃,离开;停止(支持或帮助);放弃(信念). 放任;放纵 ", 18 | note: "", 19 | sentence: "added spices to the stew with complete abandon 肆无忌惮地向炖菜里面加调料\nabandon oneself to emotion 感情用事,abandon herself to a life of complete idleness 放纵自己过着闲散的生活\nabandon the ship/homes 弃船,离家↵the bad weather forced NASA to abandon the launch 坏天气迫使 NASA 停止了发射", 20 | webster: false, 21 | mnemonic: "【根】a- [ad-, to] + ban [v.&n. 禁令] + -don [v.&n.], to ban, 禁令下达后的结果 → v. 放弃;停止做某事;放弃 (理智、责任) → v.&n. 放纵", 22 | phonetic: "[ə'bændən]", 23 | panTotalNum: 4, 24 | panForgetNum: 0, 25 | panRate: 0, 26 | panHistory: "1111", 27 | panFlag: 0, 28 | } 29 | }, 30 | { 31 | model: "review.review", 32 | pk: 28, 33 | fields: { 34 | word: "abase", 35 | total_num: 4, 36 | forget_num: 2, 37 | rate: 0.25, 38 | LIST: 0, 39 | UNIT: 0, 40 | INDEX: 1, 41 | BOOK: "GRE3000", 42 | history: "0101", 43 | flag: 0, 44 | mean: " v. 表现卑微;卑躬屈节;屈从 ", 45 | note: "", 46 | sentence: "was unwilling to abase himself by pleading guilty to a crime that he did not commit 不愿意屈就自己去承认一个莫须有的罪名", 47 | webster: false, 48 | mnemonic: "【根】a- [ad-, to] + base [底部], to base, 向底部 (降) → v. 降低 (地位、职位、威望或尊严)", 49 | phonetic: "[ə'beɪs]", 50 | panTotalNum: 10, 51 | panForgetNum: 2, 52 | panRate: 0.2, 53 | panHistory: "0101111111", 54 | panFlag: 0, 55 | } 56 | }, 57 | { 58 | model: "review.review", 59 | pk: 88, 60 | fields: { 61 | word: "adventitious", 62 | total_num: 5, 63 | forget_num: 5, 64 | rate: 1, 65 | LIST: 0, 66 | UNIT: 6, 67 | INDEX: 1, 68 | BOOK: "GRE3000", 69 | history: "00000", 70 | flag: 0, 71 | mean: "外来的,后天的,非内在的:not inherent or innate", 72 | note: "", 73 | sentence: "Moral considerations are adventitious to the study of art. 道德的考量对于艺术研究来说是不必要的。", 74 | webster: false, 75 | mnemonic: "【根】ad- [to] + vent [come] + it + -ious [a.], “come” (instead of being innate) → a. 外来的,后天的,非内在的", 76 | phonetic: "[ˌædven'tɪʃəs]", 77 | panTotalNum: 8, 78 | panForgetNum: 7, 79 | panRate: 0.875, 80 | panHistory: "00000001", 81 | panFlag: 0 82 | } 83 | }, 84 | ]; 85 | begin_index = 0; 86 | rawWordLength = wordArray.length; 87 | let i = 0; 88 | $('.sort-array').each(function () { 89 | if (i == 0) { 90 | $(this).click(); 91 | i++; 92 | } 93 | }) 94 | console.log(wordArray[wordIndex]) 95 | 96 | // 复习完一个单词 97 | $('.jump-btn-fake').on('click', function (e) { 98 | e.preventDefault(); 99 | $('#jump-forward').click(); 100 | layer.msg('你点了' + $(this).text()) 101 | }) 102 | 103 | }) 104 | -------------------------------------------------------------------------------- /doc/img/demo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/doc/img/demo1.png -------------------------------------------------------------------------------- /doc/img/demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/doc/img/demo2.png -------------------------------------------------------------------------------- /doc/img/demo_calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/doc/img/demo_calendar.png -------------------------------------------------------------------------------- /doc/img/demo_homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/doc/img/demo_homepage.png -------------------------------------------------------------------------------- /doc/install.md: -------------------------------------------------------------------------------- 1 | >文档尚不完善,如有问题欢迎在 [Issue](https://github.com/Benature/WordReview/issues) 提出(●゚ω゚●) 2 | >*不要不好意思,什么问题都可以问,大家都有刚开始的时候嘛* 3 | 4 | # 1. 当然是先克隆代码啦 5 | 6 | ```shell 7 | git clone https://github.com/Benature/WordReview.git 8 | ``` 9 | 10 |
🙋提问:这个 `git` 是什么东西? 11 | 12 | 如果你不知道 git 是什么,请到[此处](https://git-scm.com/downloads)下载安装。 13 | 14 | 关于安装的选项,可以自行搜索`win/mac 安装 git`等字样,找一篇点击量高的博客参考即可。 15 | 16 |
17 |
18 | 19 | 进入项目文件夹内,复制一份`./config_sample.conf`文件,改名为`./config.conf`。 20 | 21 | # 2. Python 环境 22 | 23 |
选择一:开发者流程 24 | 25 | 1. Install `Miniconda` (recommanded) or `Anaconda` at first. 26 | 2. create a virtual environment 27 | 名字随便定,这里以`word`为例 28 | 29 | ```shell 30 | conda create -n word python=3 31 | ``` 32 | 33 | >如果你没有其他 django 的项目,偷懒起见可以不创建虚拟环境,以及下面关于虚拟环境的步骤。 34 | 35 | 3. activate the environment 36 | 37 | 在命令行继续输入: 38 | (windows) 39 | 40 | ```shell 41 | activate word 42 | ``` 43 | (linux) 44 | 45 | ```shell 46 | source activate word 47 | ``` 48 | 49 | 此时命令行左边应该有显示`(word)` 50 | 51 | 4. install requirements 52 | 53 | ```shell 54 | pip install -r requirements.txt 55 | ``` 56 | 57 |
58 | 59 |
选择二:小白流程 60 | 61 | 你只要能运行 Python 就好了! 62 | 63 | 在命令行跑这个👇 64 | 65 | ```shell 66 | pip install django pypugjs pymysql django-compressor django-sass-processor libsass mysqlclient -i http://mirrors.aliyun.com/pypi/simple/ 67 | ``` 68 | 69 |
70 | 71 |
另:你可能会遇到的问题 72 | 73 | - pip 命令不见了(像下面这种报错) 74 | 75 | ```shell 76 | pip: command not found 77 | ``` 78 | 79 | 那么请看[这里](https://benature.github.io/python-code/pip-cmd-not-found/) 80 | 81 | 83 | 84 | - 如果看到报错提示需要 Visual Studio 依赖 85 | 86 | 可以考虑参考[这位同学](https://github.com/Benature/WordReview/issues/6#issuecomment-619561082)的经验。 87 | 88 |
89 | 90 | 91 | # 3. 数据库 92 | 93 | 二选一即可(小白推荐`sqlite`) 94 | 95 | ## 3.1. 选择一:sqlite3 96 | 97 | 98 | `config.py`文件下,找到下面这个变量,定义为`sqlite`。(默认就是这个,一般不用动了) 99 | 100 | ```conf 101 | # 使用数据库类型:`mysql`、`sqlite` 102 | db_type = sqlite 103 | ``` 104 | 105 | 107 | >如果需要直接操作数据库,可以借助 GUI 工具,工具有哪些可以[在这里找找看](https://www.bing.com/search?q=sqlite+GUI)。 108 | 109 | ## 3.2. 选择二:MySQL 110 | 111 |
MySQL 操作这么繁琐一看就劝退喽 112 | 113 | ### 3.2.1. Install 114 | 115 |
MacOS 116 | 1. 下载 117 | download from , select `macOS 10.14 (x86, 64-bit), DMG Archive`(.dmg file) 118 | 119 | >顺路会看到一个叫 workbench 的,可视化工具,就像看 excel 看数据库,which is recommended. 120 | 121 | 2. 安装 122 | clike `next` all the way. 123 | 124 | 3. 设置环境变量 125 | 126 | 如果`mysql -Version`命令会报错,补一下环境变量 127 | 128 | ```shell 129 | vim ~/.bash_profile 130 | # 增加以下这行 131 | PATH=$PATH:/usr/local/mysql/bin 132 | ``` 133 | 134 |
135 | 136 |
Windows 137 | 同样在下载,略。 138 |
139 | 140 |
Ubuntu 141 | 142 | ```shell 143 | # download the configuration 144 | wget https://dev.mysql.com/get/mysql-apt-config_0.8.14-1_all.deb 145 | sudo dpkg -i mysql-apt-config_0.8.14-1_all.deb 146 | # default is fine, select OK and return 147 | 148 | sudo apt update 149 | sudo apt-get install mysql-server 150 | # set password(spa2020) 151 | # use strong password encryption 152 | 153 | sudo mysql_secure_installation 154 | # enter password 155 | # n (不换root密码) 156 | # Remove anonymous users? : y(删除匿名用户) 157 | # Disallow root login remotely?: n(是否禁止 root 远程登录) 158 | # Remove test database and access to it? : y(删除测试数据库) 159 | # Reload privilege tables now? : y(立即重新加载特权表) 160 | 161 | mysql -V # check version 162 | # mysql Ver 8.0.19 for Linux on x86_64 (MySQL Community Server - GPL) 163 | ``` 164 | 165 |
166 | 167 |
WSL 168 | 参见[此文](https://benature.github.io/linux/wsl-install-mysql8/) 169 |
170 | 171 | 172 | >macOS 和 Windows 下可以装个数据库 GUI app 173 | > - MySQL Workbench (free & recommend) 174 | > ~~如同处理 excel,不用学 mysql 命令也能操作数据库啦~~ 175 | 176 | ### 3.2.2. Mysql configuration 177 | 178 | 登录进入 mysql 命令行,密码是安装时候设置的那个。 179 | 180 | ```shell 181 | mysql -uroot -p 182 | ``` 183 | 184 | ```sql 185 | show databases; 186 | use mysql; 187 | create database tg_word_db character set utf8; 188 | create user 'tg_word_user'@'localhost' identified by 'tg_word2020'; -- 新建用户 189 | grant all privileges ON tg_word_db.* TO 'tg_word_user'@'localhost'; -- 授权 190 | flush privileges; -- 刷新系统权限表 191 | ``` 192 | 193 | >如果你在这里自定义了数据库名和用户名的话,需要去`config.py`内修改对应的数据库配置 194 | 195 | `config.py`文件下,找到下面这个变量,定义为`mysql`。(默认就是这个,一般不用动了) 196 | 197 | ```conf 198 | # 使用数据库类型:`mysql`、`sqlite` 199 | db_type = mysql 200 | ``` 201 | 202 |
203 | 204 | 205 | # 4. 前戏 206 | 207 | 在这个**仓库根目录**下 208 | 209 | ```shell 210 | # 首先确保在虚拟环境下 211 | conda activate tgword # 小白跳过 212 | ``` 213 | 214 | 1. 数据库迁移 215 | 216 | ```shell 217 | python manage.py makemigrations 218 | python manage.py migrate 219 | ``` 220 | 221 | 2. 运行 server 222 | 223 | ```shell 224 | python manage.py runserver 225 | ``` 226 | 227 | 3. debug 🤦‍♂️ 228 | 然后大概率会报错👇,因为有个包有问题(实名甩锅) 229 | 230 | ```error 231 | mysqlclient 1.3.13 or newer is required; 232 | ``` 233 | 234 | 根据自己情况修改`/path/to/xxxconda`部分,修改文件 235 | 236 | ```shell 237 | vim /path/to/xxxconda/lib/python3.7/site-packages/django/db/backends/mysql/ base.py 238 | ``` 239 | 240 | >这里用的是`vim`编辑器(mac 自带但 windows 不自带的),选择你顺手的编辑器就可以了,不一 定要在命令行操作。 241 | 242 | 找到下面两行,注释之 243 | 244 | ```python 245 | #if version < (1, 3, 13): 246 | # raise ImproperlyConfigured('mysqlclient 1.3.13 or newer is required; you have %s.' % Database.__version__) 247 | ``` 248 | 249 | 4. 开始背单词! 250 | 251 | ```shell 252 | conda activate # 小白流程不用这条命令 253 | python manage.py runserver 254 | ``` 255 | 256 | 打开[localhost:8000](localhost:8000/),开始背单词之旅吧 🤓 257 | 258 | 当然在此之前你大概需要导入单词数据,那么请看[这里](./database_init.md) 259 | 260 | --- 261 | 262 | 当你想要更新代码的时候,请 263 | 264 | ```shell 265 | git pull 266 | python manage.py makemigrations 267 | python manage.py migrate 268 | ``` 269 | 270 | 更多说明请回到[这里](https://github.com/Benature/WordReview#%E4%BD%BF%E7%94%A8)查看 -------------------------------------------------------------------------------- /extension/img/vocabulary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Benature/WordReview/c492912f30dd134b619e7274d4f514d8ae97b577/extension/img/vocabulary.png -------------------------------------------------------------------------------- /extension/js/background.js: -------------------------------------------------------------------------------- 1 | var content = '' 2 | chrome.extension.onMessage.addListener(function (request, sender, sendMessage) { 3 | if (request.action == "wordsand") { 4 | 5 | const req = new XMLHttpRequest(); 6 | const baseUrl = "http://www.wordsand.cn/lookup.asp?word=" + request.word; 7 | // const baseUrl = "http://www.wordsand.cn/lookup.asp"; 8 | // const urlParams = `data=abandon`; 9 | 10 | req.open("POST", baseUrl, true); 11 | // req.send(urlParams); 12 | req.send(); 13 | 14 | req.onreadystatechange = function () { // Call a function when the state changes. 15 | if (this.readyState === XMLHttpRequest.DONE && this.status === 200) { 16 | // console.log((this)) 17 | content = this.response 18 | sendMessage({ 19 | status: 'done', 20 | content: this.response, 21 | }); 22 | // chrome.storage.sync.set({ wordsand: this.response }, function () { 23 | // console.log("set storage"); 24 | // }); 25 | } 26 | } 27 | return true; // asynchronously return message: https://developer.chrome.com/extensions/messaging 28 | } 29 | }); 30 | 31 | console.log(new Date()); 32 | 33 | -------------------------------------------------------------------------------- /extension/js/content-script.js: -------------------------------------------------------------------------------- 1 | var word = ''; 2 | var noteFocus = false; 3 | var content = ""; 4 | 5 | function renderWordSand(word_now) { 6 | chrome.runtime.sendMessage({ 7 | action: "wordsand", 8 | word: word_now, 9 | }, function (response) { 10 | if (response.status == 'done') { 11 | if (/([\s\S]*?)<\/td>/g.test(response.content)) { 12 | content = RegExp.$1 13 | .replace(/
[\s\S]+?<\/center>/, '') 14 | .replace(//, '') 15 | .replace('
', ''); 16 | // content = '
' + content + '
' 17 | document.getElementById('word-sand').innerHTML = content; 18 | if ($.trim($('#word-sand').text()) != '') { 19 | $('#word-sand').css('display', ''); 20 | $('#tmpl-sentence').css('max-width', '400px'); 21 | } 22 | } 23 | } else { 24 | console.error('word sand 失败', word_now) 25 | } 26 | }); 27 | word = word_now; 28 | } 29 | 30 | function updateWordSand(delay = 500) { 31 | setTimeout(function () { 32 | let word_now = $('#tmpl-word').text() 33 | console.log('crx', word_now) 34 | if (word_now != word) { 35 | renderWordSand(word_now) 36 | } 37 | }, delay);// 先等待原始页面渲染 38 | } 39 | 40 | // 第一个单词渲染 41 | updateWordSand(1500); 42 | 43 | $(function () { 44 | $(document).keyup(function (e) { 45 | if ((188 == e.keyCode && !e.shiftKey) || // < 46 | (190 == e.keyCode && !e.shiftKey) || // > 47 | (37 == e.keyCode && e.shiftKey) || // shift + left arrow 48 | (39 == e.keyCode && e.shiftKey) || // shift + right arrow 49 | (32 == e.keyCode || 191 == e.keyCode)) { // blank or / 50 | updateWordSand(); 51 | } 52 | }) 53 | }) 54 | 55 | $(document).ready(function () { 56 | $("#tmpl-note").focus(function () { 57 | noteFocus = true; 58 | }); 59 | $("#tmpl-note").blur(function () { 60 | noteFocus = false; 61 | }); 62 | }) -------------------------------------------------------------------------------- /extension/js/options.js: -------------------------------------------------------------------------------- 1 | console.log('options.js') -------------------------------------------------------------------------------- /extension/js/popup.js: -------------------------------------------------------------------------------- 1 | console.log('popup.js') -------------------------------------------------------------------------------- /extension/js/util.js: -------------------------------------------------------------------------------- 1 | function copy2Clipboard(content, idN) { 2 | // var input = document.createElement('TEXTAREA'); 3 | var input = document.getElementById(idN); 4 | input.value = content; // 修改文本框的内容 5 | input.select(); // 选中文本 6 | if (document.execCommand('copy')) { 7 | } else { 8 | console.error('复制失败'); 9 | } 10 | } -------------------------------------------------------------------------------- /extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WordReview", 3 | "version": "0.0.3", 4 | "description": "WordReview 补充功能:记忆之沙助记法显示", 5 | "permissions": [ 6 | "activeTab", 7 | "declarativeContent", 8 | "storage", 9 | "tabs", 10 | "http://www.wordsand.cn/" 11 | ], 12 | "background": { 13 | "scripts": [ 14 | "js/jquery-3.5.1.min.js", 15 | "js/background.js" 16 | ], 17 | "css": [ 18 | "css/base.css" 19 | ], 20 | "persistent": false 21 | }, 22 | "page_action": { 23 | "default_icon": "img/vocabulary.png", 24 | "default_title": "木一背单词", 25 | "default_popup": "popup.html", 26 | "permissions": [ 27 | "declarativeContent" 28 | ] 29 | }, 30 | "icons": { 31 | "16": "img/vocabulary.png", 32 | "32": "img/vocabulary.png", 33 | "48": "img/vocabulary.png", 34 | "128": "img/vocabulary.png" 35 | }, 36 | "content_scripts": [ 37 | { 38 | "matches": [ 39 | "*://127.0.0.1/*" 40 | ], 41 | "js": [ 42 | "js/jquery-3.5.1.min.js", 43 | "js/util.js", 44 | "js/content-script.js" 45 | ], 46 | "run_at": "document_start" 47 | } 48 | ], 49 | "options_page": "options.html", 50 | "options_ui": { 51 | "page": "options.html", 52 | "open_in_tab": false 53 | }, 54 | "manifest_version": 2 55 | } -------------------------------------------------------------------------------- /extension/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 我在家里很安全 | 配置 8 | 22 | 23 | 24 | 25 | 26 |
27 |
28 | 29 | 30 | 31 | 32 |
33 |
34 | 35 |
36 | 37 | 38 |
39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /extension/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 18 | 19 | 20 | 21 |
22 | 23 | Github 24 | 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 单词复习 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
«
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
»
85 |
86 |
87 |
88 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | 97 | 98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
Note 106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | 120 |
121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | django==3.1.14 2 | django-mysql==3.3.0 3 | django-sass-processor==0.8 4 | django-compressor 5 | libsass==0.19.4 6 | mysqlclient==1.4.6 7 | pymysql==0.9.3 8 | pypugjs==5.9.12 9 | xlrd 10 | pandas 11 | requests 12 | bs4 13 | lxml 14 | openpyxl==3.1.2 --------------------------------------------------------------------------------