├── .gitignore
├── LICENSE
├── README.md
├── common
├── __init__.py
├── apps.py
├── config.py
├── forms.py
├── management
│ └── commands
│ │ ├── delete_job.py
│ │ └── list_jobs.py
├── models.py
├── static
│ ├── css
│ │ ├── boofilsic.css
│ │ ├── boofilsic.min.css
│ │ ├── boofilsic_box.css
│ │ ├── boofilsic_browse.css
│ │ └── boofilsic_edit.css
│ ├── fonts
│ │ └── MaterialIcons.woff2
│ ├── img
│ │ ├── fediverse.svg
│ │ ├── logo.svg
│ │ ├── logo_blue.png
│ │ ├── logo_square.jpg
│ │ ├── logo_square.png
│ │ └── logo_square.svg
│ ├── js
│ │ ├── create_update_review.js
│ │ ├── detail.js
│ │ ├── home.js
│ │ ├── key_value_input.js
│ │ ├── mastodon.js
│ │ ├── rating-star-readonly.js
│ │ ├── scrape.js
│ │ ├── sort_layout.js
│ │ └── topic.js
│ ├── opensearch.xml
│ ├── react
│ │ ├── axios.js
│ │ ├── editor.css
│ │ ├── editor.js
│ │ ├── group.js
│ │ └── index.css
│ └── sass
│ │ ├── _AsideSection.sass
│ │ ├── _Blockquote.sass
│ │ ├── _Button.sass
│ │ ├── _Code.sass
│ │ ├── _Color.sass
│ │ ├── _Divider.sass
│ │ ├── _Footer.sass
│ │ ├── _Form.sass
│ │ ├── _Global.sass
│ │ ├── _Grid.sass
│ │ ├── _Icon.sass
│ │ ├── _Image.sass
│ │ ├── _Label.sass
│ │ ├── _List.sass
│ │ ├── _MainSection.sass
│ │ ├── _Modal.sass
│ │ ├── _Navbar.sass
│ │ ├── _Pagination.sass
│ │ ├── _SingleSection.sass
│ │ ├── _Spacing.sass
│ │ ├── _Table.sass
│ │ ├── _Typography.sass
│ │ ├── _Utility.sass
│ │ ├── _Vendor.sass
│ │ └── boofilsic.sass
├── templates
│ ├── 503.html
│ ├── common
│ │ └── error.html
│ ├── partial
│ │ ├── _announcement.html
│ │ ├── _footer.html
│ │ ├── _navbar.html
│ │ └── _sidebar.html
│ └── widgets
│ │ ├── hstore.html
│ │ ├── image.html
│ │ ├── multi_select.html
│ │ └── tag.html
├── templatetags
│ ├── __init__.py
│ ├── admin_url.py
│ ├── duration.py
│ ├── highlight.py
│ ├── mastodon.py
│ ├── nav.py
│ ├── oauth_token.py
│ ├── prettydate.py
│ ├── strip_scheme.py
│ ├── thumb.py
│ └── truncate.py
├── tests.py
├── urls.py
├── utils.py
└── views.py
├── group
├── __init__.py
├── admin.py
├── apps.py
├── feeds.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ ├── 0002_alter_group_name.py
│ ├── 0003_comment_comment_reply.py
│ ├── 0004_likecomment.py
│ └── __init__.py
├── models.py
├── schema.py
├── static
│ └── catalog.js
├── templates
│ ├── group
│ │ ├── base.html
│ │ ├── create.html
│ │ ├── group.html
│ │ ├── group_edit.html
│ │ ├── home.html
│ │ ├── new_topic.html
│ │ ├── profile.html
│ │ ├── react_base.html
│ │ ├── react_new_topic.html
│ │ ├── sidebar.html
│ │ ├── sidebar_group.html
│ │ └── topic.html
│ ├── libs
│ │ └── common.html
│ └── react_print.html
├── templatetags
│ ├── __init__.py
│ └── neogroup.py
├── tests.py
├── urls.py
└── views.py
├── manage.py
├── mastodon
├── __init__.py
├── admin.py
├── api.py
├── apps.py
├── auth.py
├── decorators.py
├── management
│ └── commands
│ │ └── wrong_sites.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
└── utils.py
├── neogroup
├── __init__.py
├── asgi.py
├── context_processors.py
├── settings.py
├── urls.py
└── wsgi.py
├── requirements.txt
├── static_source
├── hypernova-bootstrap.js
├── package.json
├── src
│ ├── App.jsx
│ ├── common
│ │ ├── axios.js
│ │ ├── color.scss
│ │ ├── isServer.js
│ │ ├── sidebar.scss
│ │ ├── utils.jsx
│ │ ├── utils.scss
│ │ ├── withHypernova.js
│ │ └── wrapInBaseContainer.jsx
│ ├── components
│ │ ├── Author
│ │ │ ├── index.jsx
│ │ │ └── style.scss
│ │ ├── Card
│ │ │ ├── Group
│ │ │ │ ├── index.jsx
│ │ │ │ └── style.scss
│ │ │ ├── SimpleGroup
│ │ │ │ ├── index.jsx
│ │ │ │ └── style.scss
│ │ │ ├── Topic
│ │ │ │ ├── index.jsx
│ │ │ │ └── style.scss
│ │ │ ├── User
│ │ │ │ ├── index.jsx
│ │ │ │ └── style.scss
│ │ │ └── index.jsx
│ │ ├── Comment
│ │ │ ├── index.jsx
│ │ │ └── style.scss
│ │ ├── Editor
│ │ │ ├── editor.jsx
│ │ │ └── editor.scss
│ │ ├── Like
│ │ │ ├── index.jsx
│ │ │ └── style.scss
│ │ ├── Nav
│ │ │ ├── index.jsx
│ │ │ └── style.scss
│ │ ├── Pagination
│ │ │ ├── index.jsx
│ │ │ └── style.scss
│ │ ├── Quote
│ │ │ ├── index.jsx
│ │ │ └── style.scss
│ │ ├── ReplyForm
│ │ │ ├── index.jsx
│ │ │ └── style.scss
│ │ ├── Sidebar
│ │ │ ├── GroupHome
│ │ │ │ ├── index.jsx
│ │ │ │ └── style.scss
│ │ │ ├── Home
│ │ │ │ ├── index.jsx
│ │ │ │ └── style.scss
│ │ │ ├── Topic
│ │ │ │ ├── index.jsx
│ │ │ │ └── style.scss
│ │ │ └── index.jsx
│ │ └── index.js
│ ├── hypernova.js
│ ├── index.jsx
│ ├── index.scss
│ ├── pages
│ │ ├── Group
│ │ │ ├── index.jsx
│ │ │ └── style.scss
│ │ ├── Topic
│ │ │ ├── index.jsx
│ │ │ └── style.scss
│ │ └── index.js
│ └── reset.css
├── vite.config.js
└── yarn.lock
└── users
├── __init__.py
├── account.py
├── admin.py
├── apps.py
├── data.py
├── forms.py
├── management
└── commands
│ ├── backfill_mastodon.py
│ ├── disable_user.py
│ ├── refresh_following.py
│ └── refresh_mastodon.py
├── migrations
├── 0001_initial.py
├── 0002_alter_user_username.py
└── __init__.py
├── models.py
├── static
└── js
│ ├── followers_list.js
│ └── following_list.js
├── tasks.py
├── templates
└── users
│ ├── data.html
│ ├── data_import_status.html
│ ├── home_anonymous.html
│ ├── login.html
│ ├── manage_report.html
│ ├── preferences.html
│ ├── register.html
│ ├── relation_list.html
│ └── report.html
├── tests.py
├── urls.py
└── views.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
131 | # local folder
132 | .history/
133 | neodb/
134 | /static/
135 | markdownx/
136 | /media/
137 | dump.rdb
138 | *.crt
139 | *.key
140 | *.pid
141 | /run.sh
142 | node_modules/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Asahi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 开篇
2 |
3 | NeoGroup,灵感来源于 NeoDB,NeoDB 里几乎涵盖了豆瓣全部的书影音功能,但是唯独缺少了小组和同城功能,作为这两个功能的重度使用者,决定做点什么,所以也模仿 NeoDB,开发了一个基于 Mastodon 登录的去中心化小组产品
4 |
5 | # TODO
6 |
7 | - [ ] i18n
8 | - [ ] 首页增加关注的用户的发布的帖子
9 |
10 | ## 前端 Dev
11 |
12 | 使用了 [django-react-templatetags](https://github.com/Frojd/django-react-templatetags) 提供的 django 插件进行前后端(伪)分离渲染,源文件在 `/static_source` 目录,编译至 `/common/static/react/` 文件夹。开发前首先确保安装了 node.js 和 yarn ,然后从根目录执行以下命令:
13 |
14 | ```bash
15 | cd static_source
16 | yarn
17 | yarn dev
18 | ```
19 |
20 | 以上执行均无问题的话(执行过程可能 OOM 需要[手动设置一下](https://stackoverflow.com/questions/48387040/how-do-i-determine-the-correct-max-old-space-size-for-node-js) ),就可以开始开发啦~
21 |
22 | ---
23 |
24 | ### 以新建一个 `Nav` 导航栏组件为例
25 |
26 | 首先在 `static_source/src/components/` 下新建一个 `Nav.jsx` 文件( 样式可写在同目录下的 style.scss 里 import ):
27 |
28 | https://github.com/anig1scur/neogroup/blob/style/static_source/src/components/Nav/index.jsx
29 |
30 |
31 | 然后在 [App](https://github.com/anig1scur/neogroup/blob/style/static_source/src/App.jsx) 这里对外导出。
32 |
33 |
34 | 确保 `{% load react %}` 以后可以在 django 模板中使用[如下语句](https://github.com/anig1scur/neogroup/blob/582b697f9ffe0f8fb1d69702c450f5423841cef6/group/templates/group/react_base.html#L28)进行一个组件的渲染:
35 |
36 | ```html
37 |
38 | {% react_render component="Nav" %}
39 |
40 | ```
41 |
42 | 也可以[传 props ](https://github.com/anig1scur/neogroup/blob/style/group/templates/group/react_topic.html#L12) 到组件中,更多使用请看 [django-react-templatetags](https://github.com/Frojd/django-react-templatetags)
43 |
44 |
45 | ### Server Side Rendering
46 |
47 | ```sh
48 | cd static_source
49 | node hypernova-bootstrap.js
50 | ```
51 |
--------------------------------------------------------------------------------
/common/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xasahi/neogroup/da9c66430532f527c5735b3b7960a7b2e4649ba6/common/__init__.py
--------------------------------------------------------------------------------
/common/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class CommonConfig(AppConfig):
5 | name = "common"
6 |
--------------------------------------------------------------------------------
/common/config.py:
--------------------------------------------------------------------------------
1 | # how many items are showed in one search result page
2 | ITEMS_PER_PAGE = 20
3 |
4 | # how many pages links in the pagination
5 | PAGE_LINK_NUMBER = 7
6 |
7 | # max tags on list page
8 | TAG_NUMBER_ON_LIST = 5
9 |
10 | # how many books have in each set at the home page
11 | BOOKS_PER_SET = 5
12 |
13 | # how many movies have in each set at the home page
14 | MOVIES_PER_SET = 5
15 |
16 | # how many music items have in each set at the home page
17 | MUSIC_PER_SET = 5
18 |
19 | # how many games have in each set at the home page
20 | GAMES_PER_SET = 5
21 |
--------------------------------------------------------------------------------
/common/management/commands/delete_job.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 | import pprint
3 | from redis import Redis
4 | from rq.job import Job
5 | from rq import Queue
6 |
7 |
8 | class Command(BaseCommand):
9 | help = "Delete a job"
10 |
11 | def add_arguments(self, parser):
12 | parser.add_argument("job_id", type=str, help="Job ID")
13 |
14 | def handle(self, *args, **options):
15 | redis = Redis()
16 | job_id = str(options["job_id"])
17 | job = Job.fetch(job_id, connection=redis)
18 | job.delete()
19 | self.stdout.write(self.style.SUCCESS(f"Deleted {job}"))
20 |
--------------------------------------------------------------------------------
/common/management/commands/list_jobs.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 | import pprint
3 | from redis import Redis
4 | from rq.job import Job
5 | from rq import Queue
6 |
7 |
8 | class Command(BaseCommand):
9 | help = "Show jobs in queue"
10 |
11 | def add_arguments(self, parser):
12 | parser.add_argument("queue", type=str, help="Queue")
13 |
14 | def handle(self, *args, **options):
15 | redis = Redis()
16 | queue = Queue(str(options["queue"]), connection=redis)
17 | for registry in [
18 | queue.started_job_registry,
19 | queue.deferred_job_registry,
20 | queue.finished_job_registry,
21 | queue.failed_job_registry,
22 | queue.scheduled_job_registry,
23 | ]:
24 | self.stdout.write(self.style.SUCCESS(f"Registry {registry}"))
25 | for job_id in registry.get_job_ids():
26 | try:
27 | job = Job.fetch(job_id, connection=redis)
28 | pprint.pp(job)
29 | except Exception as e:
30 | print(f"Error fetching {job_id}")
31 |
--------------------------------------------------------------------------------
/common/models.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xasahi/neogroup/da9c66430532f527c5735b3b7960a7b2e4649ba6/common/models.py
--------------------------------------------------------------------------------
/common/static/css/boofilsic_box.css:
--------------------------------------------------------------------------------
1 | .box {
2 | position: absolute;
3 | left: 50%;
4 | top: 50%;
5 | transform: translate(-50%, -50%);
6 | padding: 80px 100px;
7 | padding-bottom: 60px;
8 | background-color: var(--bright);
9 | text-align: center;
10 | min-width: 400px;
11 | }
12 |
13 | .box .sec-msg {
14 | color: var(--light);
15 | font-size: smaller;
16 | }
17 |
18 | .box .main-msg {
19 | margin-bottom: 5px;
20 | }
21 |
22 | .box .logo {
23 | width: 140px;
24 | margin-bottom: 60px;
25 | }
26 |
27 | .box p {
28 | text-align: justify;
29 | }
30 |
31 | /* body {
32 | filter: grayscale(1);
33 | } */
--------------------------------------------------------------------------------
/common/static/fonts/MaterialIcons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xasahi/neogroup/da9c66430532f527c5735b3b7960a7b2e4649ba6/common/static/fonts/MaterialIcons.woff2
--------------------------------------------------------------------------------
/common/static/img/fediverse.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/common/static/img/logo_blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xasahi/neogroup/da9c66430532f527c5735b3b7960a7b2e4649ba6/common/static/img/logo_blue.png
--------------------------------------------------------------------------------
/common/static/img/logo_square.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xasahi/neogroup/da9c66430532f527c5735b3b7960a7b2e4649ba6/common/static/img/logo_square.jpg
--------------------------------------------------------------------------------
/common/static/img/logo_square.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xasahi/neogroup/da9c66430532f527c5735b3b7960a7b2e4649ba6/common/static/img/logo_square.png
--------------------------------------------------------------------------------
/common/static/js/create_update_review.js:
--------------------------------------------------------------------------------
1 | $(document).ready( function() {
2 |
3 | $(".markdownx-preview").hide();
4 | $(".markdownx textarea").attr("placeholder", "从剪贴板粘贴或者拖拽文件至编辑框即可插入图片");
5 |
6 | $(".review-form__preview-button").on('click', function() {
7 | if ($(".markdownx-preview").is(":visible")) {
8 | $(".review-form__preview-button").text("预览");
9 | $(".markdownx-preview").hide();
10 | $(".markdownx textarea").show();
11 | } else {
12 | $(".review-form__preview-button").text("编辑");
13 | $(".markdownx-preview").show();
14 | $(".markdownx textarea").hide();
15 | }
16 | });
17 |
18 | let ratingLabels = $(".rating-star");
19 | $(ratingLabels).each( function(index, value) {
20 | let ratingScore = $(this).data("rating-score") / 2;
21 | $(this).starRating({
22 | initialRating: ratingScore,
23 | readOnly: true,
24 | });
25 | });
26 |
27 | });
--------------------------------------------------------------------------------
/common/static/js/key_value_input.js:
--------------------------------------------------------------------------------
1 | function keyValueInput(valueKeyWidget, hiddenInput) {
2 | let placeholderKey = valueKeyWidget.attr('placeholder-key');
3 | let placeholderValue = valueKeyWidget.attr('placeholder-value');
4 | if (placeholderKey == null) {
5 | placeholderKey = '';
6 | }
7 |
8 | if (placeholderValue == null) {
9 | placeholderValue = '';
10 | }
11 | // assign existing pairs to hidden input
12 | setHiddenInput(valueKeyWidget);
13 |
14 | let newInputPair = $('');
15 | valueKeyWidget.append(newInputPair.clone());
16 | // add new input pair
17 | valueKeyWidget.on('input', ':nth-last-child(1)', function () {
18 | if ($(this).val() && $(this).prev().val()) {
19 | valueKeyWidget.append($(newInputPair).clone());
20 | }
21 | });
22 | valueKeyWidget.on('input', ':nth-last-child(2)', function () {
23 | if ($(this).val() && $(this).next().val()) {
24 | valueKeyWidget.append($(newInputPair).clone());
25 | }
26 | });
27 | valueKeyWidget.on('input', ':nth-last-child(4)', function () {
28 | if (!$(this).val() && !$(this).next().val() && valueKeyWidget.children("input").length > 2) {
29 | $(this).next().remove();
30 | $(this).remove();
31 | }
32 | });
33 |
34 | valueKeyWidget.on('input', ':nth-last-child(3)', function () {
35 | if (!$(this).val() && !$(this).prev().val() && valueKeyWidget.children("input").length > 2) {
36 | $(this).prev().remove();
37 | $(this).remove();
38 | }
39 | });
40 |
41 | valueKeyWidget.on('input', function () {
42 | setHiddenInput(this);
43 | });
44 |
45 | function setHiddenInput(elem) {
46 | let keys = $(elem).children(":nth-child(odd)").map(function () {
47 | if ($(this).val()) {
48 | return $(this).val();
49 | }
50 | }).get();
51 | let values = $(elem).children(":nth-child(even)").map(function () {
52 | if ($(this).val()) {
53 | return $(this).val();
54 | }
55 | }).get();
56 | if (keys.length == values.length) {
57 | let finalValue = [];
58 | keys.forEach(function (key, i) {
59 | let json = new Object;
60 | json[key] = values[i];
61 | finalValue.push(JSON.stringify(json))
62 | });
63 | hiddenInput.val(finalValue.toString());
64 | } else if (keys.length - values.length == 1) {
65 | let finalValue = [];
66 | keys.forEach(function (key, i) {
67 | let json = new Object;
68 | if (i < keys.length - 1) {
69 | json[key] = values[i];
70 | } else {
71 | json[key] = ''
72 | }
73 | finalValue.push(JSON.stringify(json))
74 | });
75 | hiddenInput.val(finalValue.toString());
76 | }
77 | }
78 |
79 |
80 | }
81 |
82 |
--------------------------------------------------------------------------------
/common/static/js/rating-star-readonly.js:
--------------------------------------------------------------------------------
1 | $(document).ready( function() {
2 | let render = function() {
3 | let ratingLabels = $(".rating-star");
4 | $(ratingLabels).each( function(index, value) {
5 | let ratingScore = $(this).data("rating-score") / 2;
6 | $(this).starRating({
7 | initialRating: ratingScore,
8 | readOnly: true
9 | });
10 | });
11 | };
12 | document.body.addEventListener('htmx:load', function(evt) {
13 | render();
14 | });
15 | render();
16 | });
--------------------------------------------------------------------------------
/common/static/js/scrape.js:
--------------------------------------------------------------------------------
1 | $(document).ready( function() {
2 |
3 | $(".submit").on('click', function(e) {
4 | e.preventDefault();
5 | let form = $("#scrapeForm form");
6 | if (form.data('submitted') === true) {
7 | // Previously submitted - don't submit again
8 | } else {
9 | // Mark it so that the next submit can be ignored
10 | form.data('submitted', true);
11 | $("#scrapeForm form").submit();
12 | }
13 | });
14 |
15 | // assume there is only one input[file] on page
16 | // $("input[type='file']").each(function() {
17 | // $(this).after('');
18 | // });
19 |
20 | // preview uploaded pic
21 | $("input[type='file']").change(function() {
22 | if (this.files && this.files[0]) {
23 | var reader = new FileReader();
24 |
25 | reader.onload = function (e) {
26 | $('#previewImage').attr('src', e.target.result);
27 | }
28 |
29 | reader.readAsDataURL(this.files[0]);
30 | }
31 | });
32 |
33 | $("#parser textarea").on('paste', function(e) {
34 |
35 | // access the clipboard using the api
36 | let pastedData = e.originalEvent.clipboardData.getData('text');
37 | let lines = pastedData.split('\n')
38 | lines.forEach(line => {
39 | words = line.split(': ');
40 | if (words.length > 1) {
41 | switch (words[0]) {
42 | case '作者':
43 | authors = words[1].replace(' / ', ',');
44 | $("input[name='author']").val(authors);
45 | break;
46 | case '译者':
47 | translators = words[1].replace(' / ', ',');
48 | $("input[name='translator']").val(translators);
49 | break;
50 | case '出版社':
51 | $("input[name='pub_house']").val(words[1]);
52 | break;
53 | case '页数':
54 | let tmp = Number(words[1]);
55 | $("input[name='pages']").val(tmp);
56 | break;
57 | case '出版年':
58 | let regex = /\d+\d*/g;
59 | let figures = words[1].match(regex)
60 | figures.forEach(figure => {
61 | if (figure > 1000) $("input[name='pub_year']").val(figure);
62 | else if (figure < 13) $("input[name='pub_month']").val(figure);
63 | });
64 | break;
65 | case '定价':
66 | $("input[name='price']").val(words[1]);
67 | break;
68 | case '装帧':
69 | $("input[name='binding']").val(words[1]);
70 | break;
71 | case 'ISBN':
72 | $("input[name='isbn']").val(words[1]);
73 | break;
74 | case '副标题':
75 | $("input[name='subtitle']").val(words[1]);
76 | break;
77 | case '原作名':
78 | $("input[name='orig_title']").val(words[1]);
79 | break;
80 | case '语言':
81 | $("input[name='language']").val(words[1]);
82 | break;
83 | default:
84 | $(".widget-value-key-input :nth-last-child(2)").val(words[0]);
85 | $(".widget-value-key-input :nth-last-child(1)").val(words[1]);
86 | $(".widget-value-key-input :nth-last-child(1)").trigger("input");
87 | break;
88 | }
89 | }
90 | });
91 | });
92 |
93 | });
--------------------------------------------------------------------------------
/common/static/opensearch.xml:
--------------------------------------------------------------------------------
1 |
2 |
{{ ann.get_plain_content | truncate:200 }}
40 |48 | } 49 |
75 | } 76 |
欢迎来到{{ site_name }}!
22 |23 | {{ site_name }}还在不断完善中,丰富的内容需要大家共同创造。 24 | 试图添加垃圾数据,将会受到严肃处理。 25 | {{ site_name }}继承了联邦宇宙的用户关系,比如您在联邦宇宙屏蔽了某人,那您将不会在公共区域看到TA的痕迹。 26 |
27 |28 | 此外,{{ site_name }}现处于测试阶段,疏漏在所难免,请妥善备份您的数据。 29 | 使用过程中遇到的问题或者错误欢迎向维护者提出。感谢理解和支持! 30 |
31 | 34 | 35 |