Compare commits
55 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
e847acfdd0 | 3 months ago |
|
|
ed27107eba | 3 months ago |
|
|
1e3223469b | 3 months ago |
|
|
c88329e9dd | 3 months ago |
|
|
ee3007fb77 | 3 months ago |
|
|
a754ff755f | 3 months ago |
|
|
9e7d7460bb | 3 months ago |
|
|
d3e8603e89 | 3 months ago |
|
|
31985976dc | 3 months ago |
|
|
3bb4711731 | 3 months ago |
|
|
0477027a9c | 3 months ago |
|
|
2b451b4006 | 3 months ago |
|
|
8561ef5e05 | 3 months ago |
|
|
3f778ab254 | 3 months ago |
|
|
f641a52a2d | 3 months ago |
|
|
7f4f413d9a | 3 months ago |
|
|
0b382f5e0d | 3 months ago |
|
|
e281e56f21 | 4 months ago |
|
|
632ac9139e | 4 months ago |
|
|
0317e952e1 | 4 months ago |
|
|
97580a9a0e | 4 months ago |
|
|
de7ab44806 | 4 months ago |
|
|
6cf665e85d | 4 months ago |
|
|
8351c49d0b | 4 months ago |
|
|
ad16f451fc | 4 months ago |
|
|
e1d0e5ee1a | 4 months ago |
|
|
8dd82320ff | 4 months ago |
|
|
cd4de206f5 | 4 months ago |
|
|
ee14e71a28 | 4 months ago |
|
|
13d565b068 | 4 months ago |
|
|
78b34b217b | 4 months ago |
|
|
b0936c086a | 4 months ago |
|
|
a8948bc29d | 4 months ago |
|
|
5854b4eab4 | 4 months ago |
|
|
d961cd1bdf | 4 months ago |
|
|
4522284714 | 4 months ago |
|
|
b330bf832f | 4 months ago |
|
|
e10fa45caf | 4 months ago |
|
|
94c963720d | 4 months ago |
|
|
0c87743507 | 4 months ago |
|
|
22600a02ae | 4 months ago |
|
|
c07d263049 | 4 months ago |
|
|
fbe60ca933 | 4 months ago |
|
|
5a2f7c5bf5 | 4 months ago |
|
|
6dac1cc00e | 5 months ago |
|
|
8623ec3caf | 5 months ago |
|
|
a2a323ba69 | 5 months ago |
|
|
076fef024c | 5 months ago |
|
|
fd754d9f87 | 5 months ago |
|
|
7bda1cf077 | 5 months ago |
|
|
792ea01c5f | 5 months ago |
|
|
e5cd1d760b | 5 months ago |
|
|
a70efd4ae7 | 5 months ago |
|
|
1c2f60f367 | 5 months ago |
|
|
6188b19134 | 5 months ago |
@ -0,0 +1,10 @@
|
|||||||
|
[run]
|
||||||
|
source = .
|
||||||
|
include = *.py
|
||||||
|
omit =
|
||||||
|
*migrations*
|
||||||
|
*tests*
|
||||||
|
*.html
|
||||||
|
*whoosh_cn_backend*
|
||||||
|
*settings.py*
|
||||||
|
*venv*
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
bin/data/
|
||||||
|
# virtualenv
|
||||||
|
venv/
|
||||||
|
collectedstatic/
|
||||||
|
djangoblog/whoosh_index/
|
||||||
|
uploads/
|
||||||
|
settings_production.py
|
||||||
|
*.md
|
||||||
|
docs/
|
||||||
|
logs/
|
||||||
|
static/
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
blog/static/* linguist-vendored
|
||||||
|
*.js linguist-vendored
|
||||||
|
*.css linguist-vendored
|
||||||
|
* text=auto
|
||||||
|
*.sh text eol=lf
|
||||||
|
*.conf text eol=lf
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
<!--
|
||||||
|
如果你不认真勾选下面的内容,我可能会直接关闭你的 Issue。
|
||||||
|
提问之前,建议先阅读 https://github.com/ruby-china/How-To-Ask-Questions-The-Smart-Way
|
||||||
|
-->
|
||||||
|
|
||||||
|
**我确定我已经查看了** (标注`[ ]`为`[x]`)
|
||||||
|
|
||||||
|
- [ ] [DjangoBlog的readme](https://github.com/liangliangyy/DjangoBlog/blob/master/README.md)
|
||||||
|
- [ ] [配置说明](https://github.com/liangliangyy/DjangoBlog/blob/master/bin/config.md)
|
||||||
|
- [ ] [其他 Issues](https://github.com/liangliangyy/DjangoBlog/issues)
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**我要申请** (标注`[ ]`为`[x]`)
|
||||||
|
|
||||||
|
- [ ] BUG 反馈
|
||||||
|
- [ ] 添加新的特性或者功能
|
||||||
|
- [ ] 请求技术支持
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- dev
|
||||||
|
paths-ignore:
|
||||||
|
- '**/*.md'
|
||||||
|
- '**/*.css'
|
||||||
|
- '**/*.js'
|
||||||
|
- '**/*.yml'
|
||||||
|
- '**/*.txt'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- dev
|
||||||
|
paths-ignore:
|
||||||
|
- '**/*.md'
|
||||||
|
- '**/*.css'
|
||||||
|
- '**/*.js'
|
||||||
|
- '**/*.yml'
|
||||||
|
- '**/*.txt'
|
||||||
|
schedule:
|
||||||
|
- cron: '30 1 * * 0'
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
CodeQL-Build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
security-events: write
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v2
|
||||||
@ -0,0 +1,136 @@
|
|||||||
|
name: Django CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- dev
|
||||||
|
paths-ignore:
|
||||||
|
- '**/*.md'
|
||||||
|
- '**/*.css'
|
||||||
|
- '**/*.js'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- dev
|
||||||
|
paths-ignore:
|
||||||
|
- '**/*.md'
|
||||||
|
- '**/*.css'
|
||||||
|
- '**/*.js'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-normal:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
max-parallel: 4
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.10","3.11" ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Start MySQL
|
||||||
|
uses: samin/mysql-action@v1.3
|
||||||
|
with:
|
||||||
|
host port: 3306
|
||||||
|
container port: 3306
|
||||||
|
character set server: utf8mb4
|
||||||
|
collation server: utf8mb4_general_ci
|
||||||
|
mysql version: latest
|
||||||
|
mysql root password: root
|
||||||
|
mysql database: djangoblog
|
||||||
|
mysql user: root
|
||||||
|
mysql password: root
|
||||||
|
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
cache: 'pip'
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
- name: Run Tests
|
||||||
|
env:
|
||||||
|
DJANGO_MYSQL_PASSWORD: root
|
||||||
|
DJANGO_MYSQL_HOST: 127.0.0.1
|
||||||
|
run: |
|
||||||
|
python manage.py makemigrations
|
||||||
|
python manage.py migrate
|
||||||
|
python manage.py test
|
||||||
|
|
||||||
|
build-with-es:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
max-parallel: 4
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.10","3.11" ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Start MySQL
|
||||||
|
uses: samin/mysql-action@v1.3
|
||||||
|
with:
|
||||||
|
host port: 3306
|
||||||
|
container port: 3306
|
||||||
|
character set server: utf8mb4
|
||||||
|
collation server: utf8mb4_general_ci
|
||||||
|
mysql version: latest
|
||||||
|
mysql root password: root
|
||||||
|
mysql database: djangoblog
|
||||||
|
mysql user: root
|
||||||
|
mysql password: root
|
||||||
|
|
||||||
|
- name: Configure sysctl limits
|
||||||
|
run: |
|
||||||
|
sudo swapoff -a
|
||||||
|
sudo sysctl -w vm.swappiness=1
|
||||||
|
sudo sysctl -w fs.file-max=262144
|
||||||
|
sudo sysctl -w vm.max_map_count=262144
|
||||||
|
|
||||||
|
- uses: miyataka/elasticsearch-github-actions@1
|
||||||
|
|
||||||
|
with:
|
||||||
|
stack-version: '7.12.1'
|
||||||
|
plugins: 'https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-7.12.1.zip'
|
||||||
|
|
||||||
|
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
cache: 'pip'
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
- name: Run Tests
|
||||||
|
env:
|
||||||
|
DJANGO_MYSQL_PASSWORD: root
|
||||||
|
DJANGO_MYSQL_HOST: 127.0.0.1
|
||||||
|
DJANGO_ELASTICSEARCH_HOST: 127.0.0.1:9200
|
||||||
|
run: |
|
||||||
|
python manage.py makemigrations
|
||||||
|
python manage.py migrate
|
||||||
|
coverage run manage.py test
|
||||||
|
coverage xml
|
||||||
|
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v1
|
||||||
|
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: false
|
||||||
|
tags: djangoblog/djangoblog:dev
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
name: docker
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- '**/*.md'
|
||||||
|
- '**/*.yml'
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'dev'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set env to docker dev tag
|
||||||
|
if: endsWith(github.ref, '/dev')
|
||||||
|
run: |
|
||||||
|
echo "DOCKER_TAG=test" >> $GITHUB_ENV
|
||||||
|
- name: Set env to docker latest tag
|
||||||
|
if: endsWith(github.ref, '/master')
|
||||||
|
run: |
|
||||||
|
echo "DOCKER_TAG=latest" >> $GITHUB_ENV
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ secrets.DOCKERHUB_USERNAME }}/djangoblog:${{env.DOCKER_TAG}}
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
name: publish release
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [ published ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v3
|
||||||
|
with:
|
||||||
|
images: name/app
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
platforms: |
|
||||||
|
linux/amd64
|
||||||
|
linux/arm64
|
||||||
|
linux/arm/v7
|
||||||
|
linux/arm/v6
|
||||||
|
linux/386
|
||||||
|
tags: ${{ secrets.DOCKERHUB_USERNAME }}/djangoblog:${{ github.event.release.tag_name }}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*,cover
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# http://www.jetbrains.com/pycharm/webhelp/project.html
|
||||||
|
.idea
|
||||||
|
.iml
|
||||||
|
static/
|
||||||
|
# virtualenv
|
||||||
|
venv/
|
||||||
|
|
||||||
|
collectedstatic/
|
||||||
|
djangoblog/whoosh_index/
|
||||||
|
google93fd32dbd906620a.html
|
||||||
|
baidu_verify_FlHL7cUyC9.html
|
||||||
|
BingSiteAuth.xml
|
||||||
|
cb9339dbe2ff86a5aa169d28dba5f615.txt
|
||||||
|
werobot_session.*
|
||||||
|
django.jpg
|
||||||
|
uploads/
|
||||||
|
settings_production.py
|
||||||
|
werobot_session.db
|
||||||
|
bin/datas/
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
FROM python:3.11
|
||||||
|
ENV PYTHONUNBUFFERED 1
|
||||||
|
WORKDIR /code/djangoblog/
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install default-libmysqlclient-dev gettext -y && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
ADD requirements.txt requirements.txt
|
||||||
|
RUN pip install --upgrade pip && \
|
||||||
|
pip install --no-cache-dir -r requirements.txt && \
|
||||||
|
pip install --no-cache-dir gunicorn[gevent] && \
|
||||||
|
pip cache purge
|
||||||
|
|
||||||
|
ADD . .
|
||||||
|
RUN chmod +x /code/djangoblog/deploy/entrypoint.sh
|
||||||
|
ENTRYPOINT ["/code/djangoblog/deploy/entrypoint.sh"]
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2025 车亮亮
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AccountsConfig(AppConfig):
|
||||||
|
name = 'accounts'
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
# Generated by Django 4.1.7 on 2023-03-02 07:14
|
||||||
|
|
||||||
|
import django.contrib.auth.models
|
||||||
|
import django.contrib.auth.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BlogUser',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||||
|
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||||
|
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||||
|
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||||
|
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||||
|
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
|
||||||
|
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||||
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
|
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||||
|
('nickname', models.CharField(blank=True, max_length=100, verbose_name='昵称')),
|
||||||
|
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||||
|
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||||
|
('source', models.CharField(blank=True, max_length=100, verbose_name='创建来源')),
|
||||||
|
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||||
|
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '用户',
|
||||||
|
'verbose_name_plural': '用户',
|
||||||
|
'ordering': ['-id'],
|
||||||
|
'get_latest_by': 'id',
|
||||||
|
},
|
||||||
|
managers=[
|
||||||
|
('objects', django.contrib.auth.models.UserManager()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
# Generated by Django 4.2.5 on 2023-09-06 13:13
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='bloguser',
|
||||||
|
options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'user', 'verbose_name_plural': 'user'},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='bloguser',
|
||||||
|
name='created_time',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='bloguser',
|
||||||
|
name='last_mod_time',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='bloguser',
|
||||||
|
name='creation_time',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='bloguser',
|
||||||
|
name='last_modify_time',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='bloguser',
|
||||||
|
name='nickname',
|
||||||
|
field=models.CharField(blank=True, max_length=100, verbose_name='nick name'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='bloguser',
|
||||||
|
name='source',
|
||||||
|
field=models.CharField(blank=True, max_length=100, verbose_name='create source'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,259 @@
|
|||||||
|
from django.test import Client, RequestFactory, TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from accounts.models import BlogUser
|
||||||
|
from blog.models import Article, Category
|
||||||
|
from djangoblog.utils import *
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
|
|
||||||
|
class AccountTest(TestCase):
|
||||||
|
"""
|
||||||
|
针对账户注册、登录、密码找回、邮箱验证等功能的测试类。
|
||||||
|
使用 Django 的 TestCase 提供的测试客户端和工厂方法进行测试。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
初始化测试所需的对象。
|
||||||
|
每个测试方法运行前都会执行。
|
||||||
|
"""
|
||||||
|
self.client = Client() # Django 测试客户端,用于模拟请求
|
||||||
|
self.factory = RequestFactory() # 用于构造请求对象
|
||||||
|
# 创建一个普通测试用户
|
||||||
|
self.blog_user = BlogUser.objects.create_user(
|
||||||
|
username="test",
|
||||||
|
email="admin@admin.com",
|
||||||
|
password="12345678"
|
||||||
|
)
|
||||||
|
self.new_test = "xxx123--=" # 用于密码重置测试的新密码
|
||||||
|
|
||||||
|
def test_validate_account(self):
|
||||||
|
"""
|
||||||
|
测试账户验证、登录以及文章管理功能
|
||||||
|
"""
|
||||||
|
site = get_current_site().domain # 获取当前站点域名
|
||||||
|
|
||||||
|
# 创建超级管理员账户
|
||||||
|
user = BlogUser.objects.create_superuser(
|
||||||
|
email="liangliangyy1@gmail.com",
|
||||||
|
username="liangliangyy1",
|
||||||
|
password="qwer!@#$ggg"
|
||||||
|
)
|
||||||
|
testuser = BlogUser.objects.get(username='liangliangyy1')
|
||||||
|
|
||||||
|
# 测试登录功能
|
||||||
|
loginresult = self.client.login(
|
||||||
|
username='liangliangyy1',
|
||||||
|
password='qwer!@#$ggg'
|
||||||
|
)
|
||||||
|
self.assertEqual(loginresult, True) # 登录应成功
|
||||||
|
|
||||||
|
# 测试访问 Django admin
|
||||||
|
response = self.client.get('/admin/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# 创建一个文章分类
|
||||||
|
category = Category()
|
||||||
|
category.name = "categoryaaa"
|
||||||
|
category.creation_time = timezone.now()
|
||||||
|
category.last_modify_time = timezone.now()
|
||||||
|
category.save()
|
||||||
|
|
||||||
|
# 创建一篇文章
|
||||||
|
article = Article()
|
||||||
|
article.title = "nicetitleaaa"
|
||||||
|
article.body = "nicecontentaaa"
|
||||||
|
article.author = user
|
||||||
|
article.category = category
|
||||||
|
article.type = 'a'
|
||||||
|
article.status = 'p'
|
||||||
|
article.save()
|
||||||
|
|
||||||
|
# 测试访问文章的后台管理页面
|
||||||
|
response = self.client.get(article.get_admin_url())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_validate_register(self):
|
||||||
|
"""
|
||||||
|
测试用户注册、激活、登录及文章管理流程
|
||||||
|
"""
|
||||||
|
# 验证注册前用户不存在
|
||||||
|
self.assertEquals(
|
||||||
|
0, len(BlogUser.objects.filter(email='user123@user.com'))
|
||||||
|
)
|
||||||
|
|
||||||
|
# 通过客户端 POST 请求模拟用户注册
|
||||||
|
response = self.client.post(reverse('account:register'), {
|
||||||
|
'username': 'user1233',
|
||||||
|
'email': 'user123@user.com',
|
||||||
|
'password1': 'password123!q@wE#R$T',
|
||||||
|
'password2': 'password123!q@wE#R$T',
|
||||||
|
})
|
||||||
|
|
||||||
|
# 验证注册后用户已创建
|
||||||
|
self.assertEquals(
|
||||||
|
1, len(BlogUser.objects.filter(email='user123@user.com'))
|
||||||
|
)
|
||||||
|
|
||||||
|
user = BlogUser.objects.filter(email='user123@user.com')[0]
|
||||||
|
# 生成邮箱验证签名
|
||||||
|
sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
|
||||||
|
path = reverse('accounts:result')
|
||||||
|
url = '{path}?type=validation&id={id}&sign={sign}'.format(
|
||||||
|
path=path, id=user.id, sign=sign
|
||||||
|
)
|
||||||
|
|
||||||
|
# 测试访问邮箱验证链接
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# 登录用户
|
||||||
|
self.client.login(username='user1233', password='password123!q@wE#R$T')
|
||||||
|
user.is_superuser = True
|
||||||
|
user.is_staff = True
|
||||||
|
user.save()
|
||||||
|
delete_sidebar_cache() # 清理缓存,避免后台界面异常
|
||||||
|
|
||||||
|
# 创建分类与文章
|
||||||
|
category = Category()
|
||||||
|
category.name = "categoryaaa"
|
||||||
|
category.creation_time = timezone.now()
|
||||||
|
category.last_modify_time = timezone.now()
|
||||||
|
category.save()
|
||||||
|
|
||||||
|
article = Article()
|
||||||
|
article.category = category
|
||||||
|
article.title = "nicetitle333"
|
||||||
|
article.body = "nicecontentttt"
|
||||||
|
article.author = user
|
||||||
|
article.type = 'a'
|
||||||
|
article.status = 'p'
|
||||||
|
article.save()
|
||||||
|
|
||||||
|
# 测试访问文章后台管理页面
|
||||||
|
response = self.client.get(article.get_admin_url())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# 测试注销
|
||||||
|
response = self.client.get(reverse('account:logout'))
|
||||||
|
self.assertIn(response.status_code, [301, 302, 200])
|
||||||
|
|
||||||
|
# 注销后访问后台页面可能重定向
|
||||||
|
response = self.client.get(article.get_admin_url())
|
||||||
|
self.assertIn(response.status_code, [301, 302, 200])
|
||||||
|
|
||||||
|
# 测试错误密码登录
|
||||||
|
response = self.client.post(reverse('account:login'), {
|
||||||
|
'username': 'user1233',
|
||||||
|
'password': 'password123'
|
||||||
|
})
|
||||||
|
self.assertIn(response.status_code, [301, 302, 200])
|
||||||
|
|
||||||
|
response = self.client.get(article.get_admin_url())
|
||||||
|
self.assertIn(response.status_code, [301, 302, 200])
|
||||||
|
|
||||||
|
def test_verify_email_code(self):
|
||||||
|
"""
|
||||||
|
测试邮箱验证码生成、发送与验证
|
||||||
|
"""
|
||||||
|
to_email = "admin@admin.com"
|
||||||
|
code = generate_code() # 生成验证码
|
||||||
|
utils.set_code(to_email, code) # 设置验证码缓存
|
||||||
|
utils.send_verify_email(to_email, code) # 发送验证码邮件
|
||||||
|
|
||||||
|
# 验证正确邮箱与验证码
|
||||||
|
err = utils.verify("admin@admin.com", code)
|
||||||
|
self.assertEqual(err, None)
|
||||||
|
|
||||||
|
# 验证错误邮箱
|
||||||
|
err = utils.verify("admin@123.com", code)
|
||||||
|
self.assertEqual(type(err), str)
|
||||||
|
|
||||||
|
def test_forget_password_email_code_success(self):
|
||||||
|
"""
|
||||||
|
测试发送忘记密码验证码成功
|
||||||
|
"""
|
||||||
|
resp = self.client.post(
|
||||||
|
path=reverse("account:forget_password_code"),
|
||||||
|
data=dict(email="admin@admin.com")
|
||||||
|
)
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertEqual(resp.content.decode("utf-8"), "ok")
|
||||||
|
|
||||||
|
def test_forget_password_email_code_fail(self):
|
||||||
|
"""
|
||||||
|
测试发送忘记密码验证码失败
|
||||||
|
"""
|
||||||
|
resp = self.client.post(
|
||||||
|
path=reverse("account:forget_password_code"),
|
||||||
|
data=dict()
|
||||||
|
)
|
||||||
|
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
|
||||||
|
|
||||||
|
resp = self.client.post(
|
||||||
|
path=reverse("account:forget_password_code"),
|
||||||
|
data=dict(email="admin@com")
|
||||||
|
)
|
||||||
|
self.assertEqual(resp.content.decode("utf-8"), "错误的邮箱")
|
||||||
|
|
||||||
|
def test_forget_password_email_success(self):
|
||||||
|
"""
|
||||||
|
测试通过邮箱验证码重置密码成功
|
||||||
|
"""
|
||||||
|
code = generate_code()
|
||||||
|
utils.set_code(self.blog_user.email, code)
|
||||||
|
data = dict(
|
||||||
|
new_password1=self.new_test,
|
||||||
|
new_password2=self.new_test,
|
||||||
|
email=self.blog_user.email,
|
||||||
|
code=code,
|
||||||
|
)
|
||||||
|
resp = self.client.post(
|
||||||
|
path=reverse("account:forget_password"),
|
||||||
|
data=data
|
||||||
|
)
|
||||||
|
self.assertEqual(resp.status_code, 302)
|
||||||
|
|
||||||
|
# 验证用户密码是否修改成功
|
||||||
|
blog_user = BlogUser.objects.filter(email=self.blog_user.email).first()
|
||||||
|
self.assertNotEqual(blog_user, None)
|
||||||
|
self.assertEqual(blog_user.check_password(data["new_password1"]), True)
|
||||||
|
|
||||||
|
def test_forget_password_email_not_user(self):
|
||||||
|
"""
|
||||||
|
测试不存在的用户尝试重置密码
|
||||||
|
"""
|
||||||
|
data = dict(
|
||||||
|
new_password1=self.new_test,
|
||||||
|
new_password2=self.new_test,
|
||||||
|
email="123@123.com",
|
||||||
|
code="123456",
|
||||||
|
)
|
||||||
|
resp = self.client.post(
|
||||||
|
path=reverse("account:forget_password"),
|
||||||
|
data=data
|
||||||
|
)
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
|
def test_forget_password_email_code_error(self):
|
||||||
|
"""
|
||||||
|
测试密码重置时使用错误验证码
|
||||||
|
"""
|
||||||
|
code = generate_code()
|
||||||
|
utils.set_code(self.blog_user.email, code)
|
||||||
|
data = dict(
|
||||||
|
new_password1=self.new_test,
|
||||||
|
new_password2=self.new_test,
|
||||||
|
email=self.blog_user.email,
|
||||||
|
code="111111", # 错误验证码
|
||||||
|
)
|
||||||
|
resp = self.client.post(
|
||||||
|
path=reverse("account:forget_password"),
|
||||||
|
data=data
|
||||||
|
)
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from django.urls import re_path
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
from .forms import LoginForm
|
||||||
|
|
||||||
|
app_name = "accounts"
|
||||||
|
|
||||||
|
urlpatterns = [re_path(r'^login/$',
|
||||||
|
views.LoginView.as_view(success_url='/'),
|
||||||
|
name='login',
|
||||||
|
kwargs={'authentication_form': LoginForm}),
|
||||||
|
re_path(r'^register/$',
|
||||||
|
views.RegisterView.as_view(success_url="/"),
|
||||||
|
name='register'),
|
||||||
|
re_path(r'^logout/$',
|
||||||
|
views.LogoutView.as_view(),
|
||||||
|
name='logout'),
|
||||||
|
path(r'account/result.html',
|
||||||
|
views.account_result,
|
||||||
|
name='result'),
|
||||||
|
re_path(r'^forget_password/$',
|
||||||
|
views.ForgetPasswordView.as_view(),
|
||||||
|
name='forget_password'),
|
||||||
|
re_path(r'^forget_password_code/$',
|
||||||
|
views.ForgetPasswordEmailCode.as_view(),
|
||||||
|
name='forget_password_code'),
|
||||||
|
]
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.backends import ModelBackend
|
||||||
|
|
||||||
|
|
||||||
|
class EmailOrUsernameModelBackend(ModelBackend):
|
||||||
|
"""
|
||||||
|
允许使用用户名或邮箱登录
|
||||||
|
"""
|
||||||
|
|
||||||
|
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||||
|
if '@' in username:
|
||||||
|
kwargs = {'email': username}
|
||||||
|
else:
|
||||||
|
kwargs = {'username': username}
|
||||||
|
try:
|
||||||
|
user = get_user_model().objects.get(**kwargs)
|
||||||
|
if user.check_password(password):
|
||||||
|
return user
|
||||||
|
except get_user_model().DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_user(self, username):
|
||||||
|
try:
|
||||||
|
return get_user_model().objects.get(pk=username)
|
||||||
|
except get_user_model().DoesNotExist:
|
||||||
|
return None
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
from django import forms
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.html import format_html
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
from .models import Article
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleForm(forms.ModelForm):
|
||||||
|
# body = forms.CharField(widget=AdminPagedownWidget())
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Article
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
def makr_article_publish(modeladmin, request, queryset):
|
||||||
|
queryset.update(status='p')
|
||||||
|
|
||||||
|
|
||||||
|
def draft_article(modeladmin, request, queryset):
|
||||||
|
queryset.update(status='d')
|
||||||
|
|
||||||
|
|
||||||
|
def close_article_commentstatus(modeladmin, request, queryset):
|
||||||
|
queryset.update(comment_status='c')
|
||||||
|
|
||||||
|
|
||||||
|
def open_article_commentstatus(modeladmin, request, queryset):
|
||||||
|
queryset.update(comment_status='o')
|
||||||
|
|
||||||
|
|
||||||
|
makr_article_publish.short_description = _('Publish selected articles')
|
||||||
|
draft_article.short_description = _('Draft selected articles')
|
||||||
|
close_article_commentstatus.short_description = _('Close article comments')
|
||||||
|
open_article_commentstatus.short_description = _('Open article comments')
|
||||||
|
|
||||||
|
|
||||||
|
class ArticlelAdmin(admin.ModelAdmin):
|
||||||
|
list_per_page = 20
|
||||||
|
search_fields = ('body', 'title')
|
||||||
|
form = ArticleForm
|
||||||
|
list_display = (
|
||||||
|
'id',
|
||||||
|
'title',
|
||||||
|
'author',
|
||||||
|
'link_to_category',
|
||||||
|
'creation_time',
|
||||||
|
'views',
|
||||||
|
'status',
|
||||||
|
'type',
|
||||||
|
'article_order')
|
||||||
|
list_display_links = ('id', 'title')
|
||||||
|
list_filter = ('status', 'type', 'category')
|
||||||
|
filter_horizontal = ('tags',)
|
||||||
|
exclude = ('creation_time', 'last_modify_time')
|
||||||
|
view_on_site = True
|
||||||
|
actions = [
|
||||||
|
makr_article_publish,
|
||||||
|
draft_article,
|
||||||
|
close_article_commentstatus,
|
||||||
|
open_article_commentstatus]
|
||||||
|
|
||||||
|
def link_to_category(self, obj):
|
||||||
|
info = (obj.category._meta.app_label, obj.category._meta.model_name)
|
||||||
|
link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,))
|
||||||
|
return format_html(u'<a href="%s">%s</a>' % (link, obj.category.name))
|
||||||
|
|
||||||
|
link_to_category.short_description = _('category')
|
||||||
|
|
||||||
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
|
form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs)
|
||||||
|
form.base_fields['author'].queryset = get_user_model(
|
||||||
|
).objects.filter(is_superuser=True)
|
||||||
|
return form
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
super(ArticlelAdmin, self).save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
def get_view_on_site_url(self, obj=None):
|
||||||
|
if obj:
|
||||||
|
url = obj.get_full_url()
|
||||||
|
return url
|
||||||
|
else:
|
||||||
|
from djangoblog.utils import get_current_site
|
||||||
|
site = get_current_site().domain
|
||||||
|
return site
|
||||||
|
|
||||||
|
|
||||||
|
class TagAdmin(admin.ModelAdmin):
|
||||||
|
exclude = ('slug', 'last_mod_time', 'creation_time')
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'parent_category', 'index')
|
||||||
|
exclude = ('slug', 'last_mod_time', 'creation_time')
|
||||||
|
|
||||||
|
|
||||||
|
class LinksAdmin(admin.ModelAdmin):
|
||||||
|
exclude = ('last_mod_time', 'creation_time')
|
||||||
|
|
||||||
|
|
||||||
|
class SideBarAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'content', 'is_enable', 'sequence')
|
||||||
|
exclude = ('last_mod_time', 'creation_time')
|
||||||
|
|
||||||
|
|
||||||
|
class BlogSettingsAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class BlogConfig(AppConfig):
|
||||||
|
name = 'blog'
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from djangoblog.utils import cache, get_blog_setting
|
||||||
|
from .models import Category, Article
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def seo_processor(requests):
|
||||||
|
key = 'seo_processor'
|
||||||
|
value = cache.get(key)
|
||||||
|
if value:
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
logger.info('set processor cache.')
|
||||||
|
setting = get_blog_setting()
|
||||||
|
value = {
|
||||||
|
'SITE_NAME': setting.site_name,
|
||||||
|
'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense,
|
||||||
|
'GOOGLE_ADSENSE_CODES': setting.google_adsense_codes,
|
||||||
|
'SITE_SEO_DESCRIPTION': setting.site_seo_description,
|
||||||
|
'SITE_DESCRIPTION': setting.site_description,
|
||||||
|
'SITE_KEYWORDS': setting.site_keywords,
|
||||||
|
'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/',
|
||||||
|
'ARTICLE_SUB_LENGTH': setting.article_sub_length,
|
||||||
|
'nav_category_list': Category.objects.all(),
|
||||||
|
'nav_pages': Article.objects.filter(
|
||||||
|
type='p',
|
||||||
|
status='p'),
|
||||||
|
'OPEN_SITE_COMMENT': setting.open_site_comment,
|
||||||
|
'BEIAN_CODE': setting.beian_code,
|
||||||
|
'ANALYTICS_CODE': setting.analytics_code,
|
||||||
|
"BEIAN_CODE_GONGAN": setting.gongan_beiancode,
|
||||||
|
"SHOW_GONGAN_CODE": setting.show_gongan_code,
|
||||||
|
"CURRENT_YEAR": timezone.now().year,
|
||||||
|
"GLOBAL_HEADER": setting.global_header,
|
||||||
|
"GLOBAL_FOOTER": setting.global_footer,
|
||||||
|
"COMMENT_NEED_REVIEW": setting.comment_need_review,
|
||||||
|
}
|
||||||
|
cache.set(key, value, 60 * 60 * 10)
|
||||||
|
return value
|
||||||
@ -0,0 +1,213 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
import elasticsearch.client
|
||||||
|
from django.conf import settings
|
||||||
|
from elasticsearch_dsl import Document, InnerDoc, Date, Integer, Long, Text, Object, GeoPoint, Keyword, Boolean
|
||||||
|
from elasticsearch_dsl.connections import connections
|
||||||
|
|
||||||
|
from blog.models import Article
|
||||||
|
|
||||||
|
ELASTICSEARCH_ENABLED = hasattr(settings, 'ELASTICSEARCH_DSL')
|
||||||
|
|
||||||
|
if ELASTICSEARCH_ENABLED:
|
||||||
|
connections.create_connection(
|
||||||
|
hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']])
|
||||||
|
from elasticsearch import Elasticsearch
|
||||||
|
|
||||||
|
es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
|
||||||
|
from elasticsearch.client import IngestClient
|
||||||
|
|
||||||
|
c = IngestClient(es)
|
||||||
|
try:
|
||||||
|
c.get_pipeline('geoip')
|
||||||
|
except elasticsearch.exceptions.NotFoundError:
|
||||||
|
c.put_pipeline('geoip', body='''{
|
||||||
|
"description" : "Add geoip info",
|
||||||
|
"processors" : [
|
||||||
|
{
|
||||||
|
"geoip" : {
|
||||||
|
"field" : "ip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}''')
|
||||||
|
|
||||||
|
|
||||||
|
class GeoIp(InnerDoc):
|
||||||
|
continent_name = Keyword()
|
||||||
|
country_iso_code = Keyword()
|
||||||
|
country_name = Keyword()
|
||||||
|
location = GeoPoint()
|
||||||
|
|
||||||
|
|
||||||
|
class UserAgentBrowser(InnerDoc):
|
||||||
|
Family = Keyword()
|
||||||
|
Version = Keyword()
|
||||||
|
|
||||||
|
|
||||||
|
class UserAgentOS(UserAgentBrowser):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UserAgentDevice(InnerDoc):
|
||||||
|
Family = Keyword()
|
||||||
|
Brand = Keyword()
|
||||||
|
Model = Keyword()
|
||||||
|
|
||||||
|
|
||||||
|
class UserAgent(InnerDoc):
|
||||||
|
browser = Object(UserAgentBrowser, required=False)
|
||||||
|
os = Object(UserAgentOS, required=False)
|
||||||
|
device = Object(UserAgentDevice, required=False)
|
||||||
|
string = Text()
|
||||||
|
is_bot = Boolean()
|
||||||
|
|
||||||
|
|
||||||
|
class ElapsedTimeDocument(Document):
|
||||||
|
url = Keyword()
|
||||||
|
time_taken = Long()
|
||||||
|
log_datetime = Date()
|
||||||
|
ip = Keyword()
|
||||||
|
geoip = Object(GeoIp, required=False)
|
||||||
|
useragent = Object(UserAgent, required=False)
|
||||||
|
|
||||||
|
class Index:
|
||||||
|
name = 'performance'
|
||||||
|
settings = {
|
||||||
|
"number_of_shards": 1,
|
||||||
|
"number_of_replicas": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
doc_type = 'ElapsedTime'
|
||||||
|
|
||||||
|
|
||||||
|
class ElaspedTimeDocumentManager:
|
||||||
|
@staticmethod
|
||||||
|
def build_index():
|
||||||
|
from elasticsearch import Elasticsearch
|
||||||
|
client = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
|
||||||
|
res = client.indices.exists(index="performance")
|
||||||
|
if not res:
|
||||||
|
ElapsedTimeDocument.init()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def delete_index():
|
||||||
|
from elasticsearch import Elasticsearch
|
||||||
|
es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
|
||||||
|
es.indices.delete(index='performance', ignore=[400, 404])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create(url, time_taken, log_datetime, useragent, ip):
|
||||||
|
ElaspedTimeDocumentManager.build_index()
|
||||||
|
ua = UserAgent()
|
||||||
|
ua.browser = UserAgentBrowser()
|
||||||
|
ua.browser.Family = useragent.browser.family
|
||||||
|
ua.browser.Version = useragent.browser.version_string
|
||||||
|
|
||||||
|
ua.os = UserAgentOS()
|
||||||
|
ua.os.Family = useragent.os.family
|
||||||
|
ua.os.Version = useragent.os.version_string
|
||||||
|
|
||||||
|
ua.device = UserAgentDevice()
|
||||||
|
ua.device.Family = useragent.device.family
|
||||||
|
ua.device.Brand = useragent.device.brand
|
||||||
|
ua.device.Model = useragent.device.model
|
||||||
|
ua.string = useragent.ua_string
|
||||||
|
ua.is_bot = useragent.is_bot
|
||||||
|
|
||||||
|
doc = ElapsedTimeDocument(
|
||||||
|
meta={
|
||||||
|
'id': int(
|
||||||
|
round(
|
||||||
|
time.time() *
|
||||||
|
1000))
|
||||||
|
},
|
||||||
|
url=url,
|
||||||
|
time_taken=time_taken,
|
||||||
|
log_datetime=log_datetime,
|
||||||
|
useragent=ua, ip=ip)
|
||||||
|
doc.save(pipeline="geoip")
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleDocument(Document):
|
||||||
|
body = Text(analyzer='ik_max_word', search_analyzer='ik_smart')
|
||||||
|
title = Text(analyzer='ik_max_word', search_analyzer='ik_smart')
|
||||||
|
author = Object(properties={
|
||||||
|
'nickname': Text(analyzer='ik_max_word', search_analyzer='ik_smart'),
|
||||||
|
'id': Integer()
|
||||||
|
})
|
||||||
|
category = Object(properties={
|
||||||
|
'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'),
|
||||||
|
'id': Integer()
|
||||||
|
})
|
||||||
|
tags = Object(properties={
|
||||||
|
'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'),
|
||||||
|
'id': Integer()
|
||||||
|
})
|
||||||
|
|
||||||
|
pub_time = Date()
|
||||||
|
status = Text()
|
||||||
|
comment_status = Text()
|
||||||
|
type = Text()
|
||||||
|
views = Integer()
|
||||||
|
article_order = Integer()
|
||||||
|
|
||||||
|
class Index:
|
||||||
|
name = 'blog'
|
||||||
|
settings = {
|
||||||
|
"number_of_shards": 1,
|
||||||
|
"number_of_replicas": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
doc_type = 'Article'
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleDocumentManager():
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.create_index()
|
||||||
|
|
||||||
|
def create_index(self):
|
||||||
|
ArticleDocument.init()
|
||||||
|
|
||||||
|
def delete_index(self):
|
||||||
|
from elasticsearch import Elasticsearch
|
||||||
|
es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
|
||||||
|
es.indices.delete(index='blog', ignore=[400, 404])
|
||||||
|
|
||||||
|
def convert_to_doc(self, articles):
|
||||||
|
return [
|
||||||
|
ArticleDocument(
|
||||||
|
meta={
|
||||||
|
'id': article.id},
|
||||||
|
body=article.body,
|
||||||
|
title=article.title,
|
||||||
|
author={
|
||||||
|
'nickname': article.author.username,
|
||||||
|
'id': article.author.id},
|
||||||
|
category={
|
||||||
|
'name': article.category.name,
|
||||||
|
'id': article.category.id},
|
||||||
|
tags=[
|
||||||
|
{
|
||||||
|
'name': t.name,
|
||||||
|
'id': t.id} for t in article.tags.all()],
|
||||||
|
pub_time=article.pub_time,
|
||||||
|
status=article.status,
|
||||||
|
comment_status=article.comment_status,
|
||||||
|
type=article.type,
|
||||||
|
views=article.views,
|
||||||
|
article_order=article.article_order) for article in articles]
|
||||||
|
|
||||||
|
def rebuild(self, articles=None):
|
||||||
|
ArticleDocument.init()
|
||||||
|
articles = articles if articles else Article.objects.all()
|
||||||
|
docs = self.convert_to_doc(articles)
|
||||||
|
for doc in docs:
|
||||||
|
doc.save()
|
||||||
|
|
||||||
|
def update_docs(self, docs):
|
||||||
|
for doc in docs:
|
||||||
|
doc.save()
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from haystack.forms import SearchForm
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class BlogSearchForm(SearchForm):
|
||||||
|
querydata = forms.CharField(required=True)
|
||||||
|
|
||||||
|
def search(self):
|
||||||
|
datas = super(BlogSearchForm, self).search()
|
||||||
|
if not self.is_valid():
|
||||||
|
return self.no_query_found()
|
||||||
|
|
||||||
|
if self.cleaned_data['querydata']:
|
||||||
|
logger.info(self.cleaned_data['querydata'])
|
||||||
|
return datas
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from blog.documents import ElapsedTimeDocument, ArticleDocumentManager, ElaspedTimeDocumentManager, \
|
||||||
|
ELASTICSEARCH_ENABLED
|
||||||
|
|
||||||
|
|
||||||
|
# TODO 参数化
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'build search index'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
if ELASTICSEARCH_ENABLED:
|
||||||
|
ElaspedTimeDocumentManager.build_index()
|
||||||
|
manager = ElapsedTimeDocument()
|
||||||
|
manager.init()
|
||||||
|
manager = ArticleDocumentManager()
|
||||||
|
manager.delete_index()
|
||||||
|
manager.rebuild()
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from blog.models import Tag, Category
|
||||||
|
|
||||||
|
|
||||||
|
# TODO 参数化
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'build search words'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
datas = set([t.name for t in Tag.objects.all()] +
|
||||||
|
[t.name for t in Category.objects.all()])
|
||||||
|
print('\n'.join(datas))
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from djangoblog.utils import cache
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'clear the whole cache'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
cache.clear()
|
||||||
|
self.stdout.write(self.style.SUCCESS('Cleared cache\n'))
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.hashers import make_password
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from blog.models import Article, Tag, Category
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'create test datas'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
user = get_user_model().objects.get_or_create(
|
||||||
|
email='test@test.com', username='测试用户', password=make_password('test!q@w#eTYU'))[0]
|
||||||
|
|
||||||
|
pcategory = Category.objects.get_or_create(
|
||||||
|
name='我是父类目', parent_category=None)[0]
|
||||||
|
|
||||||
|
category = Category.objects.get_or_create(
|
||||||
|
name='子类目', parent_category=pcategory)[0]
|
||||||
|
|
||||||
|
category.save()
|
||||||
|
basetag = Tag()
|
||||||
|
basetag.name = "标签"
|
||||||
|
basetag.save()
|
||||||
|
for i in range(1, 20):
|
||||||
|
article = Article.objects.get_or_create(
|
||||||
|
category=category,
|
||||||
|
title='nice title ' + str(i),
|
||||||
|
body='nice content ' + str(i),
|
||||||
|
author=user)[0]
|
||||||
|
tag = Tag()
|
||||||
|
tag.name = "标签" + str(i)
|
||||||
|
tag.save()
|
||||||
|
article.tags.add(tag)
|
||||||
|
article.tags.add(basetag)
|
||||||
|
article.save()
|
||||||
|
|
||||||
|
from djangoblog.utils import cache
|
||||||
|
cache.clear()
|
||||||
|
self.stdout.write(self.style.SUCCESS('created test datas \n'))
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from djangoblog.spider_notify import SpiderNotify
|
||||||
|
from djangoblog.utils import get_current_site
|
||||||
|
from blog.models import Article, Tag, Category
|
||||||
|
|
||||||
|
site = get_current_site().domain
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'notify baidu url'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'data_type',
|
||||||
|
type=str,
|
||||||
|
choices=[
|
||||||
|
'all',
|
||||||
|
'article',
|
||||||
|
'tag',
|
||||||
|
'category'],
|
||||||
|
help='article : all article,tag : all tag,category: all category,all: All of these')
|
||||||
|
|
||||||
|
def get_full_url(self, path):
|
||||||
|
url = "https://{site}{path}".format(site=site, path=path)
|
||||||
|
return url
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
type = options['data_type']
|
||||||
|
self.stdout.write('start get %s' % type)
|
||||||
|
|
||||||
|
urls = []
|
||||||
|
if type == 'article' or type == 'all':
|
||||||
|
for article in Article.objects.filter(status='p'):
|
||||||
|
urls.append(article.get_full_url())
|
||||||
|
if type == 'tag' or type == 'all':
|
||||||
|
for tag in Tag.objects.all():
|
||||||
|
url = tag.get_absolute_url()
|
||||||
|
urls.append(self.get_full_url(url))
|
||||||
|
if type == 'category' or type == 'all':
|
||||||
|
for category in Category.objects.all():
|
||||||
|
url = category.get_absolute_url()
|
||||||
|
urls.append(self.get_full_url(url))
|
||||||
|
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(
|
||||||
|
'start notify %d urls' %
|
||||||
|
len(urls)))
|
||||||
|
SpiderNotify.baidu_notify(urls)
|
||||||
|
self.stdout.write(self.style.SUCCESS('finish notify'))
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import requests
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.templatetags.static import static
|
||||||
|
|
||||||
|
from djangoblog.utils import save_user_avatar
|
||||||
|
from oauth.models import OAuthUser
|
||||||
|
from oauth.oauthmanager import get_manager_by_type
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'sync user avatar'
|
||||||
|
|
||||||
|
def test_picture(self, url):
|
||||||
|
try:
|
||||||
|
if requests.get(url, timeout=2).status_code == 200:
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
static_url = static("../")
|
||||||
|
users = OAuthUser.objects.all()
|
||||||
|
self.stdout.write(f'开始同步{len(users)}个用户头像')
|
||||||
|
for u in users:
|
||||||
|
self.stdout.write(f'开始同步:{u.nickname}')
|
||||||
|
url = u.picture
|
||||||
|
if url:
|
||||||
|
if url.startswith(static_url):
|
||||||
|
if self.test_picture(url):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if u.metadata:
|
||||||
|
manage = get_manager_by_type(u.type)
|
||||||
|
url = manage.get_picture(u.metadata)
|
||||||
|
url = save_user_avatar(url)
|
||||||
|
else:
|
||||||
|
url = static('blog/img/avatar.png')
|
||||||
|
else:
|
||||||
|
url = save_user_avatar(url)
|
||||||
|
else:
|
||||||
|
url = static('blog/img/avatar.png')
|
||||||
|
if url:
|
||||||
|
self.stdout.write(
|
||||||
|
f'结束同步:{u.nickname}.url:{url}')
|
||||||
|
u.picture = url
|
||||||
|
u.save()
|
||||||
|
self.stdout.write('结束同步')
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
from ipware import get_client_ip
|
||||||
|
from user_agents import parse
|
||||||
|
|
||||||
|
from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class OnlineMiddleware(object):
|
||||||
|
def __init__(self, get_response=None):
|
||||||
|
self.get_response = get_response
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
''' page render time '''
|
||||||
|
start_time = time.time()
|
||||||
|
response = self.get_response(request)
|
||||||
|
http_user_agent = request.META.get('HTTP_USER_AGENT', '')
|
||||||
|
ip, _ = get_client_ip(request)
|
||||||
|
user_agent = parse(http_user_agent)
|
||||||
|
if not response.streaming:
|
||||||
|
try:
|
||||||
|
cast_time = time.time() - start_time
|
||||||
|
if ELASTICSEARCH_ENABLED:
|
||||||
|
time_taken = round((cast_time) * 1000, 2)
|
||||||
|
url = request.path
|
||||||
|
from django.utils import timezone
|
||||||
|
ElaspedTimeDocumentManager.create(
|
||||||
|
url=url,
|
||||||
|
time_taken=time_taken,
|
||||||
|
log_datetime=timezone.now(),
|
||||||
|
useragent=user_agent,
|
||||||
|
ip=ip)
|
||||||
|
response.content = response.content.replace(
|
||||||
|
b'<!!LOAD_TIMES!!>', str.encode(str(cast_time)[:5]))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error OnlineMiddleware: %s" % e)
|
||||||
|
|
||||||
|
return response
|
||||||
@ -0,0 +1,137 @@
|
|||||||
|
# Generated by Django 4.1.7 on 2023-03-02 07:14
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import mdeditor.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BlogSettings',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('sitename', models.CharField(default='', max_length=200, verbose_name='网站名称')),
|
||||||
|
('site_description', models.TextField(default='', max_length=1000, verbose_name='网站描述')),
|
||||||
|
('site_seo_description', models.TextField(default='', max_length=1000, verbose_name='网站SEO描述')),
|
||||||
|
('site_keywords', models.TextField(default='', max_length=1000, verbose_name='网站关键字')),
|
||||||
|
('article_sub_length', models.IntegerField(default=300, verbose_name='文章摘要长度')),
|
||||||
|
('sidebar_article_count', models.IntegerField(default=10, verbose_name='侧边栏文章数目')),
|
||||||
|
('sidebar_comment_count', models.IntegerField(default=5, verbose_name='侧边栏评论数目')),
|
||||||
|
('article_comment_count', models.IntegerField(default=5, verbose_name='文章页面默认显示评论数目')),
|
||||||
|
('show_google_adsense', models.BooleanField(default=False, verbose_name='是否显示谷歌广告')),
|
||||||
|
('google_adsense_codes', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='广告内容')),
|
||||||
|
('open_site_comment', models.BooleanField(default=True, verbose_name='是否打开网站评论功能')),
|
||||||
|
('beiancode', models.CharField(blank=True, default='', max_length=2000, null=True, verbose_name='备案号')),
|
||||||
|
('analyticscode', models.TextField(default='', max_length=1000, verbose_name='网站统计代码')),
|
||||||
|
('show_gongan_code', models.BooleanField(default=False, verbose_name='是否显示公安备案号')),
|
||||||
|
('gongan_beiancode', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='公安备案号')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '网站配置',
|
||||||
|
'verbose_name_plural': '网站配置',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Links',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=30, unique=True, verbose_name='链接名称')),
|
||||||
|
('link', models.URLField(verbose_name='链接地址')),
|
||||||
|
('sequence', models.IntegerField(unique=True, verbose_name='排序')),
|
||||||
|
('is_enable', models.BooleanField(default=True, verbose_name='是否显示')),
|
||||||
|
('show_type', models.CharField(choices=[('i', '首页'), ('l', '列表页'), ('p', '文章页面'), ('a', '全站'), ('s', '友情链接页面')], default='i', max_length=1, verbose_name='显示类型')),
|
||||||
|
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||||
|
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '友情链接',
|
||||||
|
'verbose_name_plural': '友情链接',
|
||||||
|
'ordering': ['sequence'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SideBar',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=100, verbose_name='标题')),
|
||||||
|
('content', models.TextField(verbose_name='内容')),
|
||||||
|
('sequence', models.IntegerField(unique=True, verbose_name='排序')),
|
||||||
|
('is_enable', models.BooleanField(default=True, verbose_name='是否启用')),
|
||||||
|
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||||
|
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '侧边栏',
|
||||||
|
'verbose_name_plural': '侧边栏',
|
||||||
|
'ordering': ['sequence'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Tag',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||||
|
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||||
|
('name', models.CharField(max_length=30, unique=True, verbose_name='标签名')),
|
||||||
|
('slug', models.SlugField(blank=True, default='no-slug', max_length=60)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '标签',
|
||||||
|
'verbose_name_plural': '标签',
|
||||||
|
'ordering': ['name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Category',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||||
|
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||||
|
('name', models.CharField(max_length=30, unique=True, verbose_name='分类名')),
|
||||||
|
('slug', models.SlugField(blank=True, default='no-slug', max_length=60)),
|
||||||
|
('index', models.IntegerField(default=0, verbose_name='权重排序-越大越靠前')),
|
||||||
|
('parent_category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='父级分类')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '分类',
|
||||||
|
'verbose_name_plural': '分类',
|
||||||
|
'ordering': ['-index'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Article',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
|
||||||
|
('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
|
||||||
|
('title', models.CharField(max_length=200, unique=True, verbose_name='标题')),
|
||||||
|
('body', mdeditor.fields.MDTextField(verbose_name='正文')),
|
||||||
|
('pub_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='发布时间')),
|
||||||
|
('status', models.CharField(choices=[('d', '草稿'), ('p', '发表')], default='p', max_length=1, verbose_name='文章状态')),
|
||||||
|
('comment_status', models.CharField(choices=[('o', '打开'), ('c', '关闭')], default='o', max_length=1, verbose_name='评论状态')),
|
||||||
|
('type', models.CharField(choices=[('a', '文章'), ('p', '页面')], default='a', max_length=1, verbose_name='类型')),
|
||||||
|
('views', models.PositiveIntegerField(default=0, verbose_name='浏览量')),
|
||||||
|
('article_order', models.IntegerField(default=0, verbose_name='排序,数字越大越靠前')),
|
||||||
|
('show_toc', models.BooleanField(default=False, verbose_name='是否显示toc目录')),
|
||||||
|
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')),
|
||||||
|
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='分类')),
|
||||||
|
('tags', models.ManyToManyField(blank=True, to='blog.tag', verbose_name='标签集合')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '文章',
|
||||||
|
'verbose_name_plural': '文章',
|
||||||
|
'ordering': ['-article_order', '-pub_time'],
|
||||||
|
'get_latest_by': 'id',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 4.1.7 on 2023-03-29 06:08
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('blog', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
name='global_footer',
|
||||||
|
field=models.TextField(blank=True, default='', null=True, verbose_name='公共尾部'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
name='global_header',
|
||||||
|
field=models.TextField(blank=True, default='', null=True, verbose_name='公共头部'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 4.2.1 on 2023-05-09 07:45
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('blog', '0002_blogsettings_global_footer_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
name='comment_need_review',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='评论是否需要审核'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 4.2.1 on 2023-05-09 07:51
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('blog', '0003_blogsettings_comment_need_review'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
old_name='analyticscode',
|
||||||
|
new_name='analytics_code',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
old_name='beiancode',
|
||||||
|
new_name='beian_code',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
old_name='sitename',
|
||||||
|
new_name='site_name',
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,300 @@
|
|||||||
|
# Generated by Django 4.2.5 on 2023-09-06 13:13
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import mdeditor.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('blog', '0004_rename_analyticscode_blogsettings_analytics_code_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='article',
|
||||||
|
options={'get_latest_by': 'id', 'ordering': ['-article_order', '-pub_time'], 'verbose_name': 'article', 'verbose_name_plural': 'article'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='category',
|
||||||
|
options={'ordering': ['-index'], 'verbose_name': 'category', 'verbose_name_plural': 'category'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='links',
|
||||||
|
options={'ordering': ['sequence'], 'verbose_name': 'link', 'verbose_name_plural': 'link'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='sidebar',
|
||||||
|
options={'ordering': ['sequence'], 'verbose_name': 'sidebar', 'verbose_name_plural': 'sidebar'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='tag',
|
||||||
|
options={'ordering': ['name'], 'verbose_name': 'tag', 'verbose_name_plural': 'tag'},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='article',
|
||||||
|
name='created_time',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='article',
|
||||||
|
name='last_mod_time',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='category',
|
||||||
|
name='created_time',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='category',
|
||||||
|
name='last_mod_time',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='links',
|
||||||
|
name='created_time',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='sidebar',
|
||||||
|
name='created_time',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='tag',
|
||||||
|
name='created_time',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='tag',
|
||||||
|
name='last_mod_time',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='article',
|
||||||
|
name='creation_time',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='article',
|
||||||
|
name='last_modify_time',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='category',
|
||||||
|
name='creation_time',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='category',
|
||||||
|
name='last_modify_time',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='links',
|
||||||
|
name='creation_time',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sidebar',
|
||||||
|
name='creation_time',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tag',
|
||||||
|
name='creation_time',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tag',
|
||||||
|
name='last_modify_time',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='article_order',
|
||||||
|
field=models.IntegerField(default=0, verbose_name='order'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='author',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='body',
|
||||||
|
field=mdeditor.fields.MDTextField(verbose_name='body'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='category',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='category'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='comment_status',
|
||||||
|
field=models.CharField(choices=[('o', 'Open'), ('c', 'Close')], default='o', max_length=1, verbose_name='comment status'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='pub_time',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='publish time'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='show_toc',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='show toc'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(choices=[('d', 'Draft'), ('p', 'Published')], default='p', max_length=1, verbose_name='status'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='tags',
|
||||||
|
field=models.ManyToManyField(blank=True, to='blog.tag', verbose_name='tag'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='title',
|
||||||
|
field=models.CharField(max_length=200, unique=True, verbose_name='title'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(choices=[('a', 'Article'), ('p', 'Page')], default='a', max_length=1, verbose_name='type'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='views',
|
||||||
|
field=models.PositiveIntegerField(default=0, verbose_name='views'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
name='article_comment_count',
|
||||||
|
field=models.IntegerField(default=5, verbose_name='article comment count'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
name='article_sub_length',
|
||||||
|
field=models.IntegerField(default=300, verbose_name='article sub length'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
name='google_adsense_codes',
|
||||||
|
field=models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='adsense code'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
name='open_site_comment',
|
||||||
|
field=models.BooleanField(default=True, verbose_name='open site comment'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
name='show_google_adsense',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='show adsense'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
name='sidebar_article_count',
|
||||||
|
field=models.IntegerField(default=10, verbose_name='sidebar article count'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
name='sidebar_comment_count',
|
||||||
|
field=models.IntegerField(default=5, verbose_name='sidebar comment count'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
name='site_description',
|
||||||
|
field=models.TextField(default='', max_length=1000, verbose_name='site description'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
name='site_keywords',
|
||||||
|
field=models.TextField(default='', max_length=1000, verbose_name='site keywords'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
name='site_name',
|
||||||
|
field=models.CharField(default='', max_length=200, verbose_name='site name'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='blogsettings',
|
||||||
|
name='site_seo_description',
|
||||||
|
field=models.TextField(default='', max_length=1000, verbose_name='site seo description'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='category',
|
||||||
|
name='index',
|
||||||
|
field=models.IntegerField(default=0, verbose_name='index'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='category',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=30, unique=True, verbose_name='category name'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='category',
|
||||||
|
name='parent_category',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='parent category'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='links',
|
||||||
|
name='is_enable',
|
||||||
|
field=models.BooleanField(default=True, verbose_name='is show'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='links',
|
||||||
|
name='last_mod_time',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='links',
|
||||||
|
name='link',
|
||||||
|
field=models.URLField(verbose_name='link'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='links',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=30, unique=True, verbose_name='link name'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='links',
|
||||||
|
name='sequence',
|
||||||
|
field=models.IntegerField(unique=True, verbose_name='order'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='links',
|
||||||
|
name='show_type',
|
||||||
|
field=models.CharField(choices=[('i', 'index'), ('l', 'list'), ('p', 'post'), ('a', 'all'), ('s', 'slide')], default='i', max_length=1, verbose_name='show type'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='sidebar',
|
||||||
|
name='content',
|
||||||
|
field=models.TextField(verbose_name='content'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='sidebar',
|
||||||
|
name='is_enable',
|
||||||
|
field=models.BooleanField(default=True, verbose_name='is enable'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='sidebar',
|
||||||
|
name='last_mod_time',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='sidebar',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=100, verbose_name='title'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='sidebar',
|
||||||
|
name='sequence',
|
||||||
|
field=models.IntegerField(unique=True, verbose_name='order'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tag',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=30, unique=True, verbose_name='tag name'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 4.2.7 on 2024-01-26 02:41
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('blog', '0005_alter_article_options_alter_category_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='blogsettings',
|
||||||
|
options={'verbose_name': 'Website configuration', 'verbose_name_plural': 'Website configuration'},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,376 @@
|
|||||||
|
import logging
|
||||||
|
import re
|
||||||
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.timezone import now
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from mdeditor.fields import MDTextField
|
||||||
|
from uuslug import slugify
|
||||||
|
|
||||||
|
from djangoblog.utils import cache_decorator, cache
|
||||||
|
from djangoblog.utils import get_current_site
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class LinkShowType(models.TextChoices):
|
||||||
|
I = ('i', _('index'))
|
||||||
|
L = ('l', _('list'))
|
||||||
|
P = ('p', _('post'))
|
||||||
|
A = ('a', _('all'))
|
||||||
|
S = ('s', _('slide'))
|
||||||
|
|
||||||
|
|
||||||
|
class BaseModel(models.Model):
|
||||||
|
id = models.AutoField(primary_key=True)
|
||||||
|
creation_time = models.DateTimeField(_('creation time'), default=now)
|
||||||
|
last_modify_time = models.DateTimeField(_('modify time'), default=now)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
is_update_views = isinstance(
|
||||||
|
self,
|
||||||
|
Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views']
|
||||||
|
if is_update_views:
|
||||||
|
Article.objects.filter(pk=self.pk).update(views=self.views)
|
||||||
|
else:
|
||||||
|
if 'slug' in self.__dict__:
|
||||||
|
slug = getattr(
|
||||||
|
self, 'title') if 'title' in self.__dict__ else getattr(
|
||||||
|
self, 'name')
|
||||||
|
setattr(self, 'slug', slugify(slug))
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_full_url(self):
|
||||||
|
site = get_current_site().domain
|
||||||
|
url = "https://{site}{path}".format(site=site,
|
||||||
|
path=self.get_absolute_url())
|
||||||
|
return url
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_absolute_url(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Article(BaseModel):
|
||||||
|
"""文章"""
|
||||||
|
STATUS_CHOICES = (
|
||||||
|
('d', _('Draft')),
|
||||||
|
('p', _('Published')),
|
||||||
|
)
|
||||||
|
COMMENT_STATUS = (
|
||||||
|
('o', _('Open')),
|
||||||
|
('c', _('Close')),
|
||||||
|
)
|
||||||
|
TYPE = (
|
||||||
|
('a', _('Article')),
|
||||||
|
('p', _('Page')),
|
||||||
|
)
|
||||||
|
title = models.CharField(_('title'), max_length=200, unique=True)
|
||||||
|
body = MDTextField(_('body'))
|
||||||
|
pub_time = models.DateTimeField(
|
||||||
|
_('publish time'), blank=False, null=False, default=now)
|
||||||
|
status = models.CharField(
|
||||||
|
_('status'),
|
||||||
|
max_length=1,
|
||||||
|
choices=STATUS_CHOICES,
|
||||||
|
default='p')
|
||||||
|
comment_status = models.CharField(
|
||||||
|
_('comment status'),
|
||||||
|
max_length=1,
|
||||||
|
choices=COMMENT_STATUS,
|
||||||
|
default='o')
|
||||||
|
type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a')
|
||||||
|
views = models.PositiveIntegerField(_('views'), default=0)
|
||||||
|
author = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name=_('author'),
|
||||||
|
blank=False,
|
||||||
|
null=False,
|
||||||
|
on_delete=models.CASCADE)
|
||||||
|
article_order = models.IntegerField(
|
||||||
|
_('order'), blank=False, null=False, default=0)
|
||||||
|
show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False)
|
||||||
|
category = models.ForeignKey(
|
||||||
|
'Category',
|
||||||
|
verbose_name=_('category'),
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
blank=False,
|
||||||
|
null=False)
|
||||||
|
tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True)
|
||||||
|
|
||||||
|
def body_to_string(self):
|
||||||
|
return self.body
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['-article_order', '-pub_time']
|
||||||
|
verbose_name = _('article')
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
get_latest_by = 'id'
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('blog:detailbyid', kwargs={
|
||||||
|
'article_id': self.id,
|
||||||
|
'year': self.creation_time.year,
|
||||||
|
'month': self.creation_time.month,
|
||||||
|
'day': self.creation_time.day
|
||||||
|
})
|
||||||
|
|
||||||
|
@cache_decorator(60 * 60 * 10)
|
||||||
|
def get_category_tree(self):
|
||||||
|
tree = self.category.get_category_tree()
|
||||||
|
names = list(map(lambda c: (c.name, c.get_absolute_url()), tree))
|
||||||
|
|
||||||
|
return names
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def viewed(self):
|
||||||
|
self.views += 1
|
||||||
|
self.save(update_fields=['views'])
|
||||||
|
|
||||||
|
def comment_list(self):
|
||||||
|
cache_key = 'article_comments_{id}'.format(id=self.id)
|
||||||
|
value = cache.get(cache_key)
|
||||||
|
if value:
|
||||||
|
logger.info('get article comments:{id}'.format(id=self.id))
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
comments = self.comment_set.filter(is_enable=True).order_by('-id')
|
||||||
|
cache.set(cache_key, comments, 60 * 100)
|
||||||
|
logger.info('set article comments:{id}'.format(id=self.id))
|
||||||
|
return comments
|
||||||
|
|
||||||
|
def get_admin_url(self):
|
||||||
|
info = (self._meta.app_label, self._meta.model_name)
|
||||||
|
return reverse('admin:%s_%s_change' % info, args=(self.pk,))
|
||||||
|
|
||||||
|
@cache_decorator(expiration=60 * 100)
|
||||||
|
def next_article(self):
|
||||||
|
# 下一篇
|
||||||
|
return Article.objects.filter(
|
||||||
|
id__gt=self.id, status='p').order_by('id').first()
|
||||||
|
|
||||||
|
@cache_decorator(expiration=60 * 100)
|
||||||
|
def prev_article(self):
|
||||||
|
# 前一篇
|
||||||
|
return Article.objects.filter(id__lt=self.id, status='p').first()
|
||||||
|
|
||||||
|
def get_first_image_url(self):
|
||||||
|
"""
|
||||||
|
Get the first image url from article.body.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
match = re.search(r'!\[.*?\]\((.+?)\)', self.body)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
class Category(BaseModel):
|
||||||
|
"""文章分类"""
|
||||||
|
name = models.CharField(_('category name'), max_length=30, unique=True)
|
||||||
|
parent_category = models.ForeignKey(
|
||||||
|
'self',
|
||||||
|
verbose_name=_('parent category'),
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=models.CASCADE)
|
||||||
|
slug = models.SlugField(default='no-slug', max_length=60, blank=True)
|
||||||
|
index = models.IntegerField(default=0, verbose_name=_('index'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['-index']
|
||||||
|
verbose_name = _('category')
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse(
|
||||||
|
'blog:category_detail', kwargs={
|
||||||
|
'category_name': self.slug})
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
@cache_decorator(60 * 60 * 10)
|
||||||
|
def get_category_tree(self):
|
||||||
|
"""
|
||||||
|
递归获得分类目录的父级
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
categorys = []
|
||||||
|
|
||||||
|
def parse(category):
|
||||||
|
categorys.append(category)
|
||||||
|
if category.parent_category:
|
||||||
|
parse(category.parent_category)
|
||||||
|
|
||||||
|
parse(self)
|
||||||
|
return categorys
|
||||||
|
|
||||||
|
@cache_decorator(60 * 60 * 10)
|
||||||
|
def get_sub_categorys(self):
|
||||||
|
"""
|
||||||
|
获得当前分类目录所有子集
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
categorys = []
|
||||||
|
all_categorys = Category.objects.all()
|
||||||
|
|
||||||
|
def parse(category):
|
||||||
|
if category not in categorys:
|
||||||
|
categorys.append(category)
|
||||||
|
childs = all_categorys.filter(parent_category=category)
|
||||||
|
for child in childs:
|
||||||
|
if category not in categorys:
|
||||||
|
categorys.append(child)
|
||||||
|
parse(child)
|
||||||
|
|
||||||
|
parse(self)
|
||||||
|
return categorys
|
||||||
|
|
||||||
|
|
||||||
|
class Tag(BaseModel):
|
||||||
|
"""文章标签"""
|
||||||
|
name = models.CharField(_('tag name'), max_length=30, unique=True)
|
||||||
|
slug = models.SlugField(default='no-slug', max_length=60, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('blog:tag_detail', kwargs={'tag_name': self.slug})
|
||||||
|
|
||||||
|
@cache_decorator(60 * 60 * 10)
|
||||||
|
def get_article_count(self):
|
||||||
|
return Article.objects.filter(tags__name=self.name).distinct().count()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['name']
|
||||||
|
verbose_name = _('tag')
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
|
||||||
|
class Links(models.Model):
|
||||||
|
"""友情链接"""
|
||||||
|
|
||||||
|
name = models.CharField(_('link name'), max_length=30, unique=True)
|
||||||
|
link = models.URLField(_('link'))
|
||||||
|
sequence = models.IntegerField(_('order'), unique=True)
|
||||||
|
is_enable = models.BooleanField(
|
||||||
|
_('is show'), default=True, blank=False, null=False)
|
||||||
|
show_type = models.CharField(
|
||||||
|
_('show type'),
|
||||||
|
max_length=1,
|
||||||
|
choices=LinkShowType.choices,
|
||||||
|
default=LinkShowType.I)
|
||||||
|
creation_time = models.DateTimeField(_('creation time'), default=now)
|
||||||
|
last_mod_time = models.DateTimeField(_('modify time'), default=now)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['sequence']
|
||||||
|
verbose_name = _('link')
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class SideBar(models.Model):
|
||||||
|
"""侧边栏,可以展示一些html内容"""
|
||||||
|
name = models.CharField(_('title'), max_length=100)
|
||||||
|
content = models.TextField(_('content'))
|
||||||
|
sequence = models.IntegerField(_('order'), unique=True)
|
||||||
|
is_enable = models.BooleanField(_('is enable'), default=True)
|
||||||
|
creation_time = models.DateTimeField(_('creation time'), default=now)
|
||||||
|
last_mod_time = models.DateTimeField(_('modify time'), default=now)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['sequence']
|
||||||
|
verbose_name = _('sidebar')
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class BlogSettings(models.Model):
|
||||||
|
"""blog的配置"""
|
||||||
|
site_name = models.CharField(
|
||||||
|
_('site name'),
|
||||||
|
max_length=200,
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
default='')
|
||||||
|
site_description = models.TextField(
|
||||||
|
_('site description'),
|
||||||
|
max_length=1000,
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
default='')
|
||||||
|
site_seo_description = models.TextField(
|
||||||
|
_('site seo description'), max_length=1000, null=False, blank=False, default='')
|
||||||
|
site_keywords = models.TextField(
|
||||||
|
_('site keywords'),
|
||||||
|
max_length=1000,
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
default='')
|
||||||
|
article_sub_length = models.IntegerField(_('article sub length'), default=300)
|
||||||
|
sidebar_article_count = models.IntegerField(_('sidebar article count'), default=10)
|
||||||
|
sidebar_comment_count = models.IntegerField(_('sidebar comment count'), default=5)
|
||||||
|
article_comment_count = models.IntegerField(_('article comment count'), default=5)
|
||||||
|
show_google_adsense = models.BooleanField(_('show adsense'), default=False)
|
||||||
|
google_adsense_codes = models.TextField(
|
||||||
|
_('adsense code'), max_length=2000, null=True, blank=True, default='')
|
||||||
|
open_site_comment = models.BooleanField(_('open site comment'), default=True)
|
||||||
|
global_header = models.TextField("公共头部", null=True, blank=True, default='')
|
||||||
|
global_footer = models.TextField("公共尾部", null=True, blank=True, default='')
|
||||||
|
beian_code = models.CharField(
|
||||||
|
'备案号',
|
||||||
|
max_length=2000,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
default='')
|
||||||
|
analytics_code = models.TextField(
|
||||||
|
"网站统计代码",
|
||||||
|
max_length=1000,
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
default='')
|
||||||
|
show_gongan_code = models.BooleanField(
|
||||||
|
'是否显示公安备案号', default=False, null=False)
|
||||||
|
gongan_beiancode = models.TextField(
|
||||||
|
'公安备案号',
|
||||||
|
max_length=2000,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
default='')
|
||||||
|
comment_need_review = models.BooleanField(
|
||||||
|
'评论是否需要审核', default=False, null=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Website configuration')
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.site_name
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if BlogSettings.objects.exclude(id=self.id).count():
|
||||||
|
raise ValidationError(_('There can only be one configuration'))
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
from djangoblog.utils import cache
|
||||||
|
cache.clear()
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
from haystack import indexes
|
||||||
|
|
||||||
|
from blog.models import Article
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleIndex(indexes.SearchIndex, indexes.Indexable):
|
||||||
|
text = indexes.CharField(document=True, use_template=True)
|
||||||
|
|
||||||
|
def get_model(self):
|
||||||
|
return Article
|
||||||
|
|
||||||
|
def index_queryset(self, using=None):
|
||||||
|
return self.get_model().objects.filter(status='p')
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
.button {
|
||||||
|
border: none;
|
||||||
|
padding: 4px 80px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 4px 2px;
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
let wait = 60;
|
||||||
|
|
||||||
|
function time(o) {
|
||||||
|
if (wait == 0) {
|
||||||
|
o.removeAttribute("disabled");
|
||||||
|
o.value = "获取验证码";
|
||||||
|
wait = 60
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
o.setAttribute("disabled", true);
|
||||||
|
o.value = "重新发送(" + wait + ")";
|
||||||
|
wait--;
|
||||||
|
setTimeout(function () {
|
||||||
|
time(o)
|
||||||
|
},
|
||||||
|
1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("btn").onclick = function () {
|
||||||
|
let id_email = $("#id_email")
|
||||||
|
let token = $("*[name='csrfmiddlewaretoken']").val()
|
||||||
|
let ts = this
|
||||||
|
let myErr = $("#myErr")
|
||||||
|
$.ajax(
|
||||||
|
{
|
||||||
|
url: "/forget_password_code/",
|
||||||
|
type: "POST",
|
||||||
|
data: {
|
||||||
|
"email": id_email.val(),
|
||||||
|
"csrfmiddlewaretoken": token
|
||||||
|
},
|
||||||
|
success: function (result) {
|
||||||
|
if (result != "ok") {
|
||||||
|
myErr.remove()
|
||||||
|
id_email.after("<ul className='errorlist' id='myErr'><li>" + result + "</li></ul>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
myErr.remove()
|
||||||
|
time(ts)
|
||||||
|
},
|
||||||
|
error: function (e) {
|
||||||
|
alert("发送失败,请重试")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,13 @@
|
|||||||
|
/*!
|
||||||
|
* IE10 viewport hack for Surface/desktop Windows 8 bug
|
||||||
|
* Copyright 2014-2015 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* See the Getting Started docs for more information:
|
||||||
|
* http://getbootstrap.com/getting-started/#support-ie10-width
|
||||||
|
*/
|
||||||
|
@-ms-viewport { width: device-width; }
|
||||||
|
@-o-viewport { width: device-width; }
|
||||||
|
@viewport { width: device-width; }
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
body {
|
||||||
|
padding-top: 40px;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin {
|
||||||
|
max-width: 330px;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.form-signin-heading {
|
||||||
|
margin: 0 0 15px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
.form-signin .checkbox {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
.form-signin .form-control {
|
||||||
|
position: relative;
|
||||||
|
height: auto;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.form-signin .form-control:focus {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.form-signin input[type="email"] {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.form-signin input[type="password"] {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
width: 304px;
|
||||||
|
padding: 20px 25px 30px;
|
||||||
|
margin: 0 auto 25px;
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
border-radius: 2px;
|
||||||
|
-webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
|
||||||
|
box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
|
||||||
|
}
|
||||||
|
.card-signin {
|
||||||
|
width: 354px;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
.card-signin .profile-img {
|
||||||
|
display: block;
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
margin: 0 auto 10px;
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 221 B |
@ -0,0 +1,51 @@
|
|||||||
|
// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT
|
||||||
|
// IT'S JUST JUNK FOR OUR DOCS!
|
||||||
|
// ++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
/*!
|
||||||
|
* Copyright 2014-2015 Twitter, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Creative Commons Attribution 3.0 Unported License. For
|
||||||
|
* details, see https://creativecommons.org/licenses/by/3.0/.
|
||||||
|
*/
|
||||||
|
// Intended to prevent false-positive bug reports about Bootstrap not working properly in old versions of IE due to folks testing using IE's unreliable emulation modes.
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function emulatedIEMajorVersion() {
|
||||||
|
var groups = /MSIE ([0-9.]+)/.exec(window.navigator.userAgent)
|
||||||
|
if (groups === null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
var ieVersionNum = parseInt(groups[1], 10)
|
||||||
|
var ieMajorVersion = Math.floor(ieVersionNum)
|
||||||
|
return ieMajorVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
function actualNonEmulatedIEMajorVersion() {
|
||||||
|
// Detects the actual version of IE in use, even if it's in an older-IE emulation mode.
|
||||||
|
// IE JavaScript conditional compilation docs: https://msdn.microsoft.com/library/121hztk3%28v=vs.94%29.aspx
|
||||||
|
// @cc_on docs: https://msdn.microsoft.com/library/8ka90k2e%28v=vs.94%29.aspx
|
||||||
|
var jscriptVersion = new Function('/*@cc_on return @_jscript_version; @*/')() // jshint ignore:line
|
||||||
|
if (jscriptVersion === undefined) {
|
||||||
|
return 11 // IE11+ not in emulation mode
|
||||||
|
}
|
||||||
|
if (jscriptVersion < 9) {
|
||||||
|
return 8 // IE8 (or lower; haven't tested on IE<8)
|
||||||
|
}
|
||||||
|
return jscriptVersion // IE9 or IE10 in any mode, or IE11 in non-IE11 mode
|
||||||
|
}
|
||||||
|
|
||||||
|
var ua = window.navigator.userAgent
|
||||||
|
if (ua.indexOf('Opera') > -1 || ua.indexOf('Presto') > -1) {
|
||||||
|
return // Opera, which might pretend to be IE
|
||||||
|
}
|
||||||
|
var emulated = emulatedIEMajorVersion()
|
||||||
|
if (emulated === null) {
|
||||||
|
return // Not IE
|
||||||
|
}
|
||||||
|
var nonEmulated = actualNonEmulatedIEMajorVersion()
|
||||||
|
|
||||||
|
if (emulated !== nonEmulated) {
|
||||||
|
window.alert('WARNING: You appear to be using IE' + nonEmulated + ' in IE' + emulated + ' emulation mode.\nIE emulation modes can behave significantly differently from ACTUAL older versions of IE.\nPLEASE DON\'T FILE BOOTSTRAP BUGS based on testing in IE emulation modes!')
|
||||||
|
}
|
||||||
|
})();
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
/*!
|
||||||
|
* IE10 viewport hack for Surface/desktop Windows 8 bug
|
||||||
|
* Copyright 2014-2015 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// See the Getting Started docs for more information:
|
||||||
|
// http://getbootstrap.com/getting-started/#support-ie10-width
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
if (navigator.userAgent.match(/IEMobile\/10\.0/)) {
|
||||||
|
var msViewportStyle = document.createElement('style')
|
||||||
|
msViewportStyle.appendChild(
|
||||||
|
document.createTextNode(
|
||||||
|
'@-ms-viewport{width:auto!important}'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
document.querySelector('head').appendChild(msViewportStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
@ -0,0 +1,273 @@
|
|||||||
|
/*
|
||||||
|
Styles for older IE versions (previous to IE9).
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #e6e6e6;
|
||||||
|
}
|
||||||
|
body.custom-background-empty {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
body.custom-background-empty .site,
|
||||||
|
body.custom-background-white .site {
|
||||||
|
box-shadow: none;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.assistive-text,
|
||||||
|
.site .screen-reader-text {
|
||||||
|
clip: rect(1px 1px 1px 1px);
|
||||||
|
}
|
||||||
|
.full-width .site-content {
|
||||||
|
float: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
img.size-full,
|
||||||
|
img.size-large,
|
||||||
|
img.header-image,
|
||||||
|
img.wp-post-image,
|
||||||
|
img[class*="align"],
|
||||||
|
img[class*="wp-image-"],
|
||||||
|
img[class*="attachment-"] {
|
||||||
|
width: auto; /* Prevent stretching of full-size and large-size images with height and width attributes in IE8 */
|
||||||
|
}
|
||||||
|
.author-avatar {
|
||||||
|
float: left;
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-top: 0.571428571rem;
|
||||||
|
}
|
||||||
|
.author-description {
|
||||||
|
float: right;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
.site {
|
||||||
|
box-shadow: 0 2px 6px rgba(100, 100, 100, 0.3);
|
||||||
|
margin: 48px auto;
|
||||||
|
max-width: 960px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0 40px;
|
||||||
|
}
|
||||||
|
.site-content {
|
||||||
|
float: left;
|
||||||
|
width: 65.104166667%;
|
||||||
|
}
|
||||||
|
body.template-front-page .site-content,
|
||||||
|
body.attachment .site-content,
|
||||||
|
body.full-width .site-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.widget-area {
|
||||||
|
float: right;
|
||||||
|
width: 26.041666667%;
|
||||||
|
}
|
||||||
|
.site-header h1,
|
||||||
|
.site-header h2 {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.site-header h1 {
|
||||||
|
font-size: 26px;
|
||||||
|
line-height: 1.846153846;
|
||||||
|
}
|
||||||
|
.main-navigation ul.nav-menu,
|
||||||
|
.main-navigation div.nav-menu > ul {
|
||||||
|
border-bottom: 1px solid #ededed;
|
||||||
|
border-top: 1px solid #ededed;
|
||||||
|
display: inline-block !important;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.main-navigation ul {
|
||||||
|
margin: 0;
|
||||||
|
text-indent: 0;
|
||||||
|
}
|
||||||
|
.main-navigation li a,
|
||||||
|
.main-navigation li {
|
||||||
|
display: inline-block;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.ie7 .main-navigation li a,
|
||||||
|
.ie7 .main-navigation li {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.main-navigation li a {
|
||||||
|
border-bottom: 0;
|
||||||
|
color: #6a6a6a;
|
||||||
|
line-height: 3.692307692;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.main-navigation li a:hover {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.main-navigation li {
|
||||||
|
margin: 0 40px 0 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.main-navigation li ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
height: 1px;
|
||||||
|
width: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(1px, 1px, 1px, 1px);
|
||||||
|
}
|
||||||
|
.ie7 .main-navigation li ul {
|
||||||
|
clip: inherit;
|
||||||
|
display: none;
|
||||||
|
left: 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
.main-navigation li ul ul,
|
||||||
|
.ie7 .main-navigation li ul ul {
|
||||||
|
top: 0;
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
.main-navigation ul li:hover > ul,
|
||||||
|
.main-navigation ul li:focus > ul,
|
||||||
|
.main-navigation .focus > ul {
|
||||||
|
border-left: 0;
|
||||||
|
clip: inherit;
|
||||||
|
overflow: inherit;
|
||||||
|
height: inherit;
|
||||||
|
width: inherit;
|
||||||
|
}
|
||||||
|
.ie7 .main-navigation ul li:hover > ul,
|
||||||
|
.ie7 .main-navigation ul li:focus > ul {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.main-navigation li ul li a {
|
||||||
|
background: #efefef;
|
||||||
|
border-bottom: 1px solid #ededed;
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 2.181818182;
|
||||||
|
padding: 8px 10px;
|
||||||
|
width: 180px;
|
||||||
|
}
|
||||||
|
.main-navigation li ul li a:hover {
|
||||||
|
background: #e3e3e3;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
.main-navigation .current-menu-item > a,
|
||||||
|
.main-navigation .current-menu-ancestor > a,
|
||||||
|
.main-navigation .current_page_item > a,
|
||||||
|
.main-navigation .current_page_ancestor > a {
|
||||||
|
color: #636363;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.main-navigation .menu-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.entry-header .entry-title {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
#respond form input[type="text"] {
|
||||||
|
width: 46.333333333%;
|
||||||
|
}
|
||||||
|
#respond form textarea.blog-textarea {
|
||||||
|
width: 79.666666667%;
|
||||||
|
}
|
||||||
|
.template-front-page .site-content,
|
||||||
|
.template-front-page article {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.template-front-page.has-post-thumbnail article {
|
||||||
|
float: left;
|
||||||
|
width: 47.916666667%;
|
||||||
|
}
|
||||||
|
.entry-page-image {
|
||||||
|
float: right;
|
||||||
|
margin-bottom: 0;
|
||||||
|
width: 47.916666667%;
|
||||||
|
}
|
||||||
|
/* IE Front Page Template Widget fix */
|
||||||
|
.template-front-page .widget-area {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.template-front-page .widget {
|
||||||
|
width: 100% !important;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.template-front-page .widget-area .widget,
|
||||||
|
.template-front-page .first.front-widgets,
|
||||||
|
.template-front-page.two-sidebars .widget-area .front-widgets {
|
||||||
|
float: left;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
width: 51.875%;
|
||||||
|
}
|
||||||
|
.template-front-page .second.front-widgets,
|
||||||
|
.template-front-page .widget-area .widget:nth-child(odd) {
|
||||||
|
clear: right;
|
||||||
|
}
|
||||||
|
.template-front-page .first.front-widgets,
|
||||||
|
.template-front-page .second.front-widgets,
|
||||||
|
.template-front-page.two-sidebars .widget-area .front-widgets + .front-widgets {
|
||||||
|
float: right;
|
||||||
|
margin: 0 0 24px;
|
||||||
|
width: 39.0625%;
|
||||||
|
}
|
||||||
|
.template-front-page.two-sidebars .widget,
|
||||||
|
.template-front-page.two-sidebars .widget:nth-child(even) {
|
||||||
|
float: none;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
/* add input font for <IE9 Password Box to make the bullets show up */
|
||||||
|
input[type="password"] {
|
||||||
|
font-family: Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RTL overrides for IE7 and IE8
|
||||||
|
-------------------------------------------------------------- */
|
||||||
|
.rtl .site-header h1,
|
||||||
|
.rtl .site-header h2 {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.rtl .widget-area,
|
||||||
|
.rtl .author-description {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.rtl .author-avatar,
|
||||||
|
.rtl .site-content {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.rtl .main-navigation ul.nav-menu,
|
||||||
|
.rtl .main-navigation div.nav-menu > ul {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.rtl .main-navigation ul li ul li,
|
||||||
|
.rtl .main-navigation ul li ul li ul li {
|
||||||
|
margin-left: 40px;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
.rtl .main-navigation li ul ul {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.ie7 .rtl .main-navigation li ul ul {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.ie7 .rtl .main-navigation ul li {
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
.ie7 .rtl .main-navigation li ul {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.ie7 .rtl .main-navigation li {
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: 40px;
|
||||||
|
}
|
||||||
|
.ie7 .rtl .main-navigation li ul ul ul {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
/* Make clicks pass-through */
|
||||||
|
#nprogress {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nprogress .bar {
|
||||||
|
background: red;
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1031;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fancy blur effect */
|
||||||
|
#nprogress .peg {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
width: 100px;
|
||||||
|
height: 100%;
|
||||||
|
box-shadow: 0 0 10px #29d, 0 0 5px #29d;
|
||||||
|
opacity: 1.0;
|
||||||
|
|
||||||
|
-webkit-transform: rotate(3deg) translate(0px, -4px);
|
||||||
|
-ms-transform: rotate(3deg) translate(0px, -4px);
|
||||||
|
transform: rotate(3deg) translate(0px, -4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove these to get rid of the spinner */
|
||||||
|
#nprogress .spinner {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1031;
|
||||||
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nprogress .spinner-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
border: solid 2px transparent;
|
||||||
|
border-top-color: red;
|
||||||
|
border-left-color: red;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
-webkit-animation: nprogress-spinner 400ms linear infinite;
|
||||||
|
animation: nprogress-spinner 400ms linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nprogress-custom-parent {
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nprogress-custom-parent #nprogress .spinner,
|
||||||
|
.nprogress-custom-parent #nprogress .bar {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes nprogress-spinner {
|
||||||
|
0% { -webkit-transform: rotate(0deg); }
|
||||||
|
100% { -webkit-transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
@keyframes nprogress-spinner {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,305 @@
|
|||||||
|
|
||||||
|
.icon-sn-google {
|
||||||
|
background-position: 0 -28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-bg-google {
|
||||||
|
background-color: #4285f4;
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-sn-google {
|
||||||
|
color: #4285f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-github {
|
||||||
|
background-position: -28px -28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-bg-github {
|
||||||
|
background-color: #333;
|
||||||
|
background-position: -28px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-sn-github {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-weibo {
|
||||||
|
background-position: -56px -28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-bg-weibo {
|
||||||
|
background-color: #e90d24;
|
||||||
|
background-position: -56px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-sn-weibo {
|
||||||
|
color: #e90d24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-qq {
|
||||||
|
background-position: -84px -28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-bg-qq {
|
||||||
|
background-color: #0098e6;
|
||||||
|
background-position: -84px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-sn-qq {
|
||||||
|
color: #0098e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-twitter {
|
||||||
|
background-position: -112px -28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-bg-twitter {
|
||||||
|
background-color: #50abf1;
|
||||||
|
background-position: -112px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-sn-twitter {
|
||||||
|
color: #50abf1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-facebook {
|
||||||
|
background-position: -140px -28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-bg-facebook {
|
||||||
|
background-color: #4862a3;
|
||||||
|
background-position: -140px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-sn-facebook {
|
||||||
|
color: #4862a3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-renren {
|
||||||
|
background-position: -168px -28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-bg-renren {
|
||||||
|
background-color: #197bc8;
|
||||||
|
background-position: -168px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-sn-renren {
|
||||||
|
color: #197bc8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-tqq {
|
||||||
|
background-position: -196px -28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-bg-tqq {
|
||||||
|
background-color: #1f9ed2;
|
||||||
|
background-position: -196px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-sn-tqq {
|
||||||
|
color: #1f9ed2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-douban {
|
||||||
|
background-position: -224px -28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-bg-douban {
|
||||||
|
background-color: #279738;
|
||||||
|
background-position: -224px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-sn-douban {
|
||||||
|
color: #279738;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-weixin {
|
||||||
|
background-position: -252px -28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-bg-weixin {
|
||||||
|
background-color: #00b500;
|
||||||
|
background-position: -252px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-sn-weixin {
|
||||||
|
color: #00b500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-dotted {
|
||||||
|
background-position: -280px -28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-bg-dotted {
|
||||||
|
background-color: #eee;
|
||||||
|
background-position: -280px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-sn-dotted {
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-site {
|
||||||
|
background-position: -308px -28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-bg-site {
|
||||||
|
background-color: #00b500;
|
||||||
|
background-position: -308px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-sn-site {
|
||||||
|
color: #00b500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-linkedin {
|
||||||
|
background-position: -336px -28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sn-bg-linkedin {
|
||||||
|
background-color: #0077b9;
|
||||||
|
background-position: -336px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-sn-linkedin {
|
||||||
|
color: #0077b9;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*=icon-sn-] {
|
||||||
|
display: inline-block;
|
||||||
|
background-image: url('../img/icon-sn.svg');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
vertical-align: middle;
|
||||||
|
background-size: auto 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*=icon-sn-]:hover {
|
||||||
|
opacity: .8;
|
||||||
|
filter: alpha(opacity=80);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-google {
|
||||||
|
background: #4285f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-google:active, .btn-sn-google:focus, .btn-sn-google:hover {
|
||||||
|
background: #2a75f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-github {
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-github:active, .btn-sn-github:focus, .btn-sn-github:hover {
|
||||||
|
background: #262626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-weibo {
|
||||||
|
background: #e90d24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-weibo:active, .btn-sn-weibo:focus, .btn-sn-weibo:hover {
|
||||||
|
background: #d10c20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-qq {
|
||||||
|
background: #0098e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-qq:active, .btn-sn-qq:focus, .btn-sn-qq:hover {
|
||||||
|
background: #0087cd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-twitter {
|
||||||
|
background: #50abf1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-twitter:active, .btn-sn-twitter:focus, .btn-sn-twitter:hover {
|
||||||
|
background: #38a0ef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-facebook {
|
||||||
|
background: #4862a3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-facebook:active, .btn-sn-facebook:focus, .btn-sn-facebook:hover {
|
||||||
|
background: #405791;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-renren {
|
||||||
|
background: #197bc8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-renren:active, .btn-sn-renren:focus, .btn-sn-renren:hover {
|
||||||
|
background: #166db1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-tqq {
|
||||||
|
background: #1f9ed2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-tqq:active, .btn-sn-tqq:focus, .btn-sn-tqq:hover {
|
||||||
|
background: #1c8dbc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-douban {
|
||||||
|
background: #279738;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-douban:active, .btn-sn-douban:focus, .btn-sn-douban:hover {
|
||||||
|
background: #228330;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-weixin {
|
||||||
|
background: #00b500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-weixin:active, .btn-sn-weixin:focus, .btn-sn-weixin:hover {
|
||||||
|
background: #009c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-dotted {
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-dotted:active, .btn-sn-dotted:focus, .btn-sn-dotted:hover {
|
||||||
|
background: #e1e1e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-site {
|
||||||
|
background: #00b500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-site:active, .btn-sn-site:focus, .btn-sn-site:hover {
|
||||||
|
background: #009c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-linkedin {
|
||||||
|
background: #0077b9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-linkedin:active, .btn-sn-linkedin:focus, .btn-sn-linkedin:hover {
|
||||||
|
background: #0067a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*=btn-sn-], [class*=btn-sn-]:active, [class*=btn-sn-]:focus, [class*=btn-sn-]:hover {
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-more {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sn-more, .btn-sn-more:active, .btn-sn-more:hover {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*=btn-sn-] [class*=icon-sn-] {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,600 @@
|
|||||||
|
/* cyrillic-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* greek-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
/* greek */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||||
|
}
|
||||||
|
/* hebrew */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
|
||||||
|
}
|
||||||
|
/* math */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
|
||||||
|
}
|
||||||
|
/* symbols */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
/* cyrillic-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* greek-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
/* greek */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||||
|
}
|
||||||
|
/* hebrew */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
|
||||||
|
}
|
||||||
|
/* math */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
|
||||||
|
}
|
||||||
|
/* symbols */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
/* cyrillic-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* greek-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
/* greek */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||||
|
}
|
||||||
|
/* hebrew */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
|
||||||
|
}
|
||||||
|
/* math */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
|
||||||
|
}
|
||||||
|
/* symbols */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
/* cyrillic-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* greek-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
/* greek */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||||
|
}
|
||||||
|
/* hebrew */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
|
||||||
|
}
|
||||||
|
/* math */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
|
||||||
|
}
|
||||||
|
/* symbols */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
/* cyrillic-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* greek-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
/* greek */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||||
|
}
|
||||||
|
/* hebrew */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
|
||||||
|
}
|
||||||
|
/* math */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
|
||||||
|
}
|
||||||
|
/* symbols */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
/* cyrillic-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* greek-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
/* greek */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||||
|
}
|
||||||
|
/* hebrew */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
|
||||||
|
}
|
||||||
|
/* math */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
|
||||||
|
}
|
||||||
|
/* symbols */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-stretch: 100%;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 25 KiB |
@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* Created by liangliang on 2016/11/20.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
function do_reply(parentid) {
|
||||||
|
console.log(parentid);
|
||||||
|
$("#id_parent_comment_id").val(parentid)
|
||||||
|
$("#commentform").appendTo($("#div-comment-" + parentid));
|
||||||
|
$("#reply-title").hide();
|
||||||
|
$("#cancel_comment").show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel_reply() {
|
||||||
|
$("#reply-title").show();
|
||||||
|
$("#cancel_comment").hide();
|
||||||
|
$("#id_parent_comment_id").val('')
|
||||||
|
$("#commentform").appendTo($("#respond"));
|
||||||
|
}
|
||||||
|
|
||||||
|
NProgress.start();
|
||||||
|
NProgress.set(0.4);
|
||||||
|
//Increment
|
||||||
|
var interval = setInterval(function () {
|
||||||
|
NProgress.inc();
|
||||||
|
}, 1000);
|
||||||
|
$(document).ready(function () {
|
||||||
|
NProgress.done();
|
||||||
|
clearInterval(interval);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/** 侧边栏回到顶部 */
|
||||||
|
var rocket = $('#rocket');
|
||||||
|
|
||||||
|
$(window).on('scroll', debounce(slideTopSet, 300));
|
||||||
|
|
||||||
|
function debounce(func, wait) {
|
||||||
|
var timeout;
|
||||||
|
return function () {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(func, wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function slideTopSet() {
|
||||||
|
var top = $(document).scrollTop();
|
||||||
|
|
||||||
|
if (top > 200) {
|
||||||
|
rocket.addClass('show');
|
||||||
|
} else {
|
||||||
|
rocket.removeClass('show');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('click', '#rocket', function (event) {
|
||||||
|
rocket.addClass('move');
|
||||||
|
$('body, html').animate({
|
||||||
|
scrollTop: 0
|
||||||
|
}, 800);
|
||||||
|
});
|
||||||
|
$(document).on('animationEnd', function () {
|
||||||
|
setTimeout(function () {
|
||||||
|
rocket.removeClass('move');
|
||||||
|
}, 400);
|
||||||
|
|
||||||
|
});
|
||||||
|
$(document).on('webkitAnimationEnd', function () {
|
||||||
|
setTimeout(function () {
|
||||||
|
rocket.removeClass('move');
|
||||||
|
}, 400);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function () {
|
||||||
|
var replyLinks = document.querySelectorAll(".comment-reply-link");
|
||||||
|
for (var i = 0; i < replyLinks.length; i++) {
|
||||||
|
replyLinks[i].onclick = function () {
|
||||||
|
var pk = this.getAttribute("data-pk");
|
||||||
|
do_reply(pk);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// $(document).ready(function () {
|
||||||
|
// var form = $('#i18n-form');
|
||||||
|
// var selector = $('.i18n-select');
|
||||||
|
// selector.on('change', function () {
|
||||||
|
// form.submit();
|
||||||
|
// });
|
||||||
|
// });
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
HTML5 Shiv v3.7.0 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
|
||||||
|
*/
|
||||||
|
(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
|
||||||
|
a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x<style>article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}</style>";
|
||||||
|
c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="<xyz></xyz>";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
|
||||||
|
"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:"3.7.0",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);
|
||||||
|
if(g)return a.createDocumentFragment();for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d<h;d++)c.createElement(e[d]);return c}};l.html5=e;q(f)})(this,document);
|
||||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* Handles toggling the navigation menu for small screens and
|
||||||
|
* accessibility for submenu items.
|
||||||
|
*/
|
||||||
|
( function() {
|
||||||
|
var nav = document.getElementById( 'site-navigation' ), button, menu;
|
||||||
|
if ( ! nav ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
button = nav.getElementsByTagName( 'button' )[0];
|
||||||
|
menu = nav.getElementsByTagName( 'ul' )[0];
|
||||||
|
if ( ! button ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide button if menu is missing or empty.
|
||||||
|
if ( ! menu || ! menu.childNodes.length ) {
|
||||||
|
button.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.onclick = function() {
|
||||||
|
if ( -1 === menu.className.indexOf( 'nav-menu' ) ) {
|
||||||
|
menu.className = 'nav-menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( -1 !== button.className.indexOf( 'toggled-on' ) ) {
|
||||||
|
button.className = button.className.replace( ' toggled-on', '' );
|
||||||
|
menu.className = menu.className.replace( ' toggled-on', '' );
|
||||||
|
} else {
|
||||||
|
button.className += ' toggled-on';
|
||||||
|
menu.className += ' toggled-on';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} )();
|
||||||
|
|
||||||
|
// Better focus for hidden submenu items for accessibility.
|
||||||
|
( function( $ ) {
|
||||||
|
$( '.main-navigation' ).find( 'a' ).on( 'focus.twentytwelve blur.twentytwelve', function() {
|
||||||
|
$( this ).parents( '.menu-item, .page_item' ).toggleClass( 'focus' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
if ( 'ontouchstart' in window ) {
|
||||||
|
$('body').on( 'touchstart.twentytwelve', '.menu-item-has-children > a, .page_item_has_children > a', function( e ) {
|
||||||
|
var el = $( this ).parent( 'li' );
|
||||||
|
|
||||||
|
if ( ! el.hasClass( 'focus' ) ) {
|
||||||
|
e.preventDefault();
|
||||||
|
el.toggleClass( 'focus' );
|
||||||
|
el.siblings( '.focus').removeClass( 'focus' );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
} )( jQuery );
|
||||||
@ -0,0 +1,480 @@
|
|||||||
|
/* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress
|
||||||
|
* @license MIT */
|
||||||
|
|
||||||
|
;(function(root, factory) {
|
||||||
|
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
define(factory);
|
||||||
|
} else if (typeof exports === 'object') {
|
||||||
|
module.exports = factory();
|
||||||
|
} else {
|
||||||
|
root.NProgress = factory();
|
||||||
|
}
|
||||||
|
|
||||||
|
})(this, function() {
|
||||||
|
var NProgress = {};
|
||||||
|
|
||||||
|
NProgress.version = '0.2.0';
|
||||||
|
|
||||||
|
var Settings = NProgress.settings = {
|
||||||
|
minimum: 0.08,
|
||||||
|
easing: 'linear',
|
||||||
|
positionUsing: '',
|
||||||
|
speed: 200,
|
||||||
|
trickle: true,
|
||||||
|
trickleSpeed: 200,
|
||||||
|
showSpinner: true,
|
||||||
|
barSelector: '[role="bar"]',
|
||||||
|
spinnerSelector: '[role="spinner"]',
|
||||||
|
parent: 'body',
|
||||||
|
template: '<div class="bar" role="bar"><div class="peg"></div></div><div class="spinner" role="spinner"><div class="spinner-icon"></div></div>'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates configuration.
|
||||||
|
*
|
||||||
|
* NProgress.configure({
|
||||||
|
* minimum: 0.1
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
NProgress.configure = function(options) {
|
||||||
|
var key, value;
|
||||||
|
for (key in options) {
|
||||||
|
value = options[key];
|
||||||
|
if (value !== undefined && options.hasOwnProperty(key)) Settings[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last number.
|
||||||
|
*/
|
||||||
|
|
||||||
|
NProgress.status = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the progress bar status, where `n` is a number from `0.0` to `1.0`.
|
||||||
|
*
|
||||||
|
* NProgress.set(0.4);
|
||||||
|
* NProgress.set(1.0);
|
||||||
|
*/
|
||||||
|
|
||||||
|
NProgress.set = function(n) {
|
||||||
|
var started = NProgress.isStarted();
|
||||||
|
|
||||||
|
n = clamp(n, Settings.minimum, 1);
|
||||||
|
NProgress.status = (n === 1 ? null : n);
|
||||||
|
|
||||||
|
var progress = NProgress.render(!started),
|
||||||
|
bar = progress.querySelector(Settings.barSelector),
|
||||||
|
speed = Settings.speed,
|
||||||
|
ease = Settings.easing;
|
||||||
|
|
||||||
|
progress.offsetWidth; /* Repaint */
|
||||||
|
|
||||||
|
queue(function(next) {
|
||||||
|
// Set positionUsing if it hasn't already been set
|
||||||
|
if (Settings.positionUsing === '') Settings.positionUsing = NProgress.getPositioningCSS();
|
||||||
|
|
||||||
|
// Add transition
|
||||||
|
css(bar, barPositionCSS(n, speed, ease));
|
||||||
|
|
||||||
|
if (n === 1) {
|
||||||
|
// Fade out
|
||||||
|
css(progress, {
|
||||||
|
transition: 'none',
|
||||||
|
opacity: 1
|
||||||
|
});
|
||||||
|
progress.offsetWidth; /* Repaint */
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
css(progress, {
|
||||||
|
transition: 'all ' + speed + 'ms linear',
|
||||||
|
opacity: 0
|
||||||
|
});
|
||||||
|
setTimeout(function() {
|
||||||
|
NProgress.remove();
|
||||||
|
next();
|
||||||
|
}, speed);
|
||||||
|
}, speed);
|
||||||
|
} else {
|
||||||
|
setTimeout(next, speed);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
NProgress.isStarted = function() {
|
||||||
|
return typeof NProgress.status === 'number';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the progress bar.
|
||||||
|
* This is the same as setting the status to 0%, except that it doesn't go backwards.
|
||||||
|
*
|
||||||
|
* NProgress.start();
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
NProgress.start = function() {
|
||||||
|
if (!NProgress.status) NProgress.set(0);
|
||||||
|
|
||||||
|
var work = function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
if (!NProgress.status) return;
|
||||||
|
NProgress.trickle();
|
||||||
|
work();
|
||||||
|
}, Settings.trickleSpeed);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Settings.trickle) work();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the progress bar.
|
||||||
|
* This is the *sort of* the same as setting the status to 100%, with the
|
||||||
|
* difference being `done()` makes some placebo effect of some realistic motion.
|
||||||
|
*
|
||||||
|
* NProgress.done();
|
||||||
|
*
|
||||||
|
* If `true` is passed, it will show the progress bar even if its hidden.
|
||||||
|
*
|
||||||
|
* NProgress.done(true);
|
||||||
|
*/
|
||||||
|
|
||||||
|
NProgress.done = function(force) {
|
||||||
|
if (!force && !NProgress.status) return this;
|
||||||
|
|
||||||
|
return NProgress.inc(0.3 + 0.5 * Math.random()).set(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments by a random amount.
|
||||||
|
*/
|
||||||
|
|
||||||
|
NProgress.inc = function(amount) {
|
||||||
|
var n = NProgress.status;
|
||||||
|
|
||||||
|
if (!n) {
|
||||||
|
return NProgress.start();
|
||||||
|
} else if(n > 1) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (typeof amount !== 'number') {
|
||||||
|
if (n >= 0 && n < 0.2) { amount = 0.1; }
|
||||||
|
else if (n >= 0.2 && n < 0.5) { amount = 0.04; }
|
||||||
|
else if (n >= 0.5 && n < 0.8) { amount = 0.02; }
|
||||||
|
else if (n >= 0.8 && n < 0.99) { amount = 0.005; }
|
||||||
|
else { amount = 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
n = clamp(n + amount, 0, 0.994);
|
||||||
|
return NProgress.set(n);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NProgress.trickle = function() {
|
||||||
|
return NProgress.inc();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for all supplied jQuery promises and
|
||||||
|
* increases the progress as the promises resolve.
|
||||||
|
*
|
||||||
|
* @param $promise jQUery Promise
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
var initial = 0, current = 0;
|
||||||
|
|
||||||
|
NProgress.promise = function($promise) {
|
||||||
|
if (!$promise || $promise.state() === "resolved") {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current === 0) {
|
||||||
|
NProgress.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
initial++;
|
||||||
|
current++;
|
||||||
|
|
||||||
|
$promise.always(function() {
|
||||||
|
current--;
|
||||||
|
if (current === 0) {
|
||||||
|
initial = 0;
|
||||||
|
NProgress.done();
|
||||||
|
} else {
|
||||||
|
NProgress.set((initial - current) / initial);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Internal) renders the progress bar markup based on the `template`
|
||||||
|
* setting.
|
||||||
|
*/
|
||||||
|
|
||||||
|
NProgress.render = function(fromStart) {
|
||||||
|
if (NProgress.isRendered()) return document.getElementById('nprogress');
|
||||||
|
|
||||||
|
addClass(document.documentElement, 'nprogress-busy');
|
||||||
|
|
||||||
|
var progress = document.createElement('div');
|
||||||
|
progress.id = 'nprogress';
|
||||||
|
progress.innerHTML = Settings.template;
|
||||||
|
|
||||||
|
var bar = progress.querySelector(Settings.barSelector),
|
||||||
|
perc = fromStart ? '-100' : toBarPerc(NProgress.status || 0),
|
||||||
|
parent = document.querySelector(Settings.parent),
|
||||||
|
spinner;
|
||||||
|
|
||||||
|
css(bar, {
|
||||||
|
transition: 'all 0 linear',
|
||||||
|
transform: 'translate3d(' + perc + '%,0,0)'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!Settings.showSpinner) {
|
||||||
|
spinner = progress.querySelector(Settings.spinnerSelector);
|
||||||
|
spinner && removeElement(spinner);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent != document.body) {
|
||||||
|
addClass(parent, 'nprogress-custom-parent');
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.appendChild(progress);
|
||||||
|
return progress;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the element. Opposite of render().
|
||||||
|
*/
|
||||||
|
|
||||||
|
NProgress.remove = function() {
|
||||||
|
removeClass(document.documentElement, 'nprogress-busy');
|
||||||
|
removeClass(document.querySelector(Settings.parent), 'nprogress-custom-parent');
|
||||||
|
var progress = document.getElementById('nprogress');
|
||||||
|
progress && removeElement(progress);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the progress bar is rendered.
|
||||||
|
*/
|
||||||
|
|
||||||
|
NProgress.isRendered = function() {
|
||||||
|
return !!document.getElementById('nprogress');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine which positioning CSS rule to use.
|
||||||
|
*/
|
||||||
|
|
||||||
|
NProgress.getPositioningCSS = function() {
|
||||||
|
// Sniff on document.body.style
|
||||||
|
var bodyStyle = document.body.style;
|
||||||
|
|
||||||
|
// Sniff prefixes
|
||||||
|
var vendorPrefix = ('WebkitTransform' in bodyStyle) ? 'Webkit' :
|
||||||
|
('MozTransform' in bodyStyle) ? 'Moz' :
|
||||||
|
('msTransform' in bodyStyle) ? 'ms' :
|
||||||
|
('OTransform' in bodyStyle) ? 'O' : '';
|
||||||
|
|
||||||
|
if (vendorPrefix + 'Perspective' in bodyStyle) {
|
||||||
|
// Modern browsers with 3D support, e.g. Webkit, IE10
|
||||||
|
return 'translate3d';
|
||||||
|
} else if (vendorPrefix + 'Transform' in bodyStyle) {
|
||||||
|
// Browsers without 3D support, e.g. IE9
|
||||||
|
return 'translate';
|
||||||
|
} else {
|
||||||
|
// Browsers without translate() support, e.g. IE7-8
|
||||||
|
return 'margin';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helpers
|
||||||
|
*/
|
||||||
|
|
||||||
|
function clamp(n, min, max) {
|
||||||
|
if (n < min) return min;
|
||||||
|
if (n > max) return max;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Internal) converts a percentage (`0..1`) to a bar translateX
|
||||||
|
* percentage (`-100%..0%`).
|
||||||
|
*/
|
||||||
|
|
||||||
|
function toBarPerc(n) {
|
||||||
|
return (-1 + n) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Internal) returns the correct CSS for changing the bar's
|
||||||
|
* position given an n percentage, and speed and ease from Settings
|
||||||
|
*/
|
||||||
|
|
||||||
|
function barPositionCSS(n, speed, ease) {
|
||||||
|
var barCSS;
|
||||||
|
|
||||||
|
if (Settings.positionUsing === 'translate3d') {
|
||||||
|
barCSS = { transform: 'translate3d('+toBarPerc(n)+'%,0,0)' };
|
||||||
|
} else if (Settings.positionUsing === 'translate') {
|
||||||
|
barCSS = { transform: 'translate('+toBarPerc(n)+'%,0)' };
|
||||||
|
} else {
|
||||||
|
barCSS = { 'margin-left': toBarPerc(n)+'%' };
|
||||||
|
}
|
||||||
|
|
||||||
|
barCSS.transition = 'all '+speed+'ms '+ease;
|
||||||
|
|
||||||
|
return barCSS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Internal) Queues a function to be executed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var queue = (function() {
|
||||||
|
var pending = [];
|
||||||
|
|
||||||
|
function next() {
|
||||||
|
var fn = pending.shift();
|
||||||
|
if (fn) {
|
||||||
|
fn(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(fn) {
|
||||||
|
pending.push(fn);
|
||||||
|
if (pending.length == 1) next();
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Internal) Applies css properties to an element, similar to the jQuery
|
||||||
|
* css method.
|
||||||
|
*
|
||||||
|
* While this helper does assist with vendor prefixed property names, it
|
||||||
|
* does not perform any manipulation of values prior to setting styles.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var css = (function() {
|
||||||
|
var cssPrefixes = [ 'Webkit', 'O', 'Moz', 'ms' ],
|
||||||
|
cssProps = {};
|
||||||
|
|
||||||
|
function camelCase(string) {
|
||||||
|
return string.replace(/^-ms-/, 'ms-').replace(/-([\da-z])/gi, function(match, letter) {
|
||||||
|
return letter.toUpperCase();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVendorProp(name) {
|
||||||
|
var style = document.body.style;
|
||||||
|
if (name in style) return name;
|
||||||
|
|
||||||
|
var i = cssPrefixes.length,
|
||||||
|
capName = name.charAt(0).toUpperCase() + name.slice(1),
|
||||||
|
vendorName;
|
||||||
|
while (i--) {
|
||||||
|
vendorName = cssPrefixes[i] + capName;
|
||||||
|
if (vendorName in style) return vendorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStyleProp(name) {
|
||||||
|
name = camelCase(name);
|
||||||
|
return cssProps[name] || (cssProps[name] = getVendorProp(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCss(element, prop, value) {
|
||||||
|
prop = getStyleProp(prop);
|
||||||
|
element.style[prop] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(element, properties) {
|
||||||
|
var args = arguments,
|
||||||
|
prop,
|
||||||
|
value;
|
||||||
|
|
||||||
|
if (args.length == 2) {
|
||||||
|
for (prop in properties) {
|
||||||
|
value = properties[prop];
|
||||||
|
if (value !== undefined && properties.hasOwnProperty(prop)) applyCss(element, prop, value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
applyCss(element, args[1], args[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Internal) Determines if an element or space separated list of class names contains a class name.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function hasClass(element, name) {
|
||||||
|
var list = typeof element == 'string' ? element : classList(element);
|
||||||
|
return list.indexOf(' ' + name + ' ') >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Internal) Adds a class to an element.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function addClass(element, name) {
|
||||||
|
var oldList = classList(element),
|
||||||
|
newList = oldList + name;
|
||||||
|
|
||||||
|
if (hasClass(oldList, name)) return;
|
||||||
|
|
||||||
|
// Trim the opening space.
|
||||||
|
element.className = newList.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Internal) Removes a class from an element.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function removeClass(element, name) {
|
||||||
|
var oldList = classList(element),
|
||||||
|
newList;
|
||||||
|
|
||||||
|
if (!hasClass(element, name)) return;
|
||||||
|
|
||||||
|
// Replace the class name.
|
||||||
|
newList = oldList.replace(' ' + name + ' ', ' ');
|
||||||
|
|
||||||
|
// Trim the opening and closing spaces.
|
||||||
|
element.className = newList.substring(1, newList.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Internal) Gets a space separated list of the class names on the element.
|
||||||
|
* The list is wrapped with a single space on each end to facilitate finding
|
||||||
|
* matches within the list.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function classList(element) {
|
||||||
|
return (' ' + (element && element.className || '') + ' ').replace(/\s+/gi, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Internal) Removes an element from the DOM.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function removeElement(element) {
|
||||||
|
element && element.parentNode && element.parentNode.removeChild(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NProgress;
|
||||||
|
});
|
||||||
@ -0,0 +1,293 @@
|
|||||||
|
.codehilite .hll {
|
||||||
|
background-color: #ffffcc
|
||||||
|
}
|
||||||
|
|
||||||
|
.codehilite {
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codehilite .c {
|
||||||
|
color: #177500
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Comment */
|
||||||
|
.codehilite .err {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error */
|
||||||
|
.codehilite .k {
|
||||||
|
color: #A90D91
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyword */
|
||||||
|
.codehilite .l {
|
||||||
|
color: #1C01CE
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal */
|
||||||
|
.codehilite .n {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name */
|
||||||
|
.codehilite .o {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Operator */
|
||||||
|
.codehilite .ch {
|
||||||
|
color: #177500
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Comment.Hashbang */
|
||||||
|
.codehilite .cm {
|
||||||
|
color: #177500
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Comment.Multiline */
|
||||||
|
.codehilite .cp {
|
||||||
|
color: #633820
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Comment.Preproc */
|
||||||
|
.codehilite .cpf {
|
||||||
|
color: #177500
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Comment.PreprocFile */
|
||||||
|
.codehilite .c1 {
|
||||||
|
color: #177500
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Comment.Single */
|
||||||
|
.codehilite .cs {
|
||||||
|
color: #177500
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Comment.Special */
|
||||||
|
.codehilite .kc {
|
||||||
|
color: #A90D91
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyword.Constant */
|
||||||
|
.codehilite .kd {
|
||||||
|
color: #A90D91
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyword.Declaration */
|
||||||
|
.codehilite .kn {
|
||||||
|
color: #A90D91
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyword.Namespace */
|
||||||
|
.codehilite .kp {
|
||||||
|
color: #A90D91
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyword.Pseudo */
|
||||||
|
.codehilite .kr {
|
||||||
|
color: #A90D91
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyword.Reserved */
|
||||||
|
.codehilite .kt {
|
||||||
|
color: #A90D91
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyword.Type */
|
||||||
|
.codehilite .ld {
|
||||||
|
color: #1C01CE
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.Date */
|
||||||
|
.codehilite .m {
|
||||||
|
color: #1C01CE
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.Number */
|
||||||
|
.codehilite .s {
|
||||||
|
color: #C41A16
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.String */
|
||||||
|
.codehilite .na {
|
||||||
|
color: #836C28
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Attribute */
|
||||||
|
.codehilite .nb {
|
||||||
|
color: #A90D91
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Builtin */
|
||||||
|
.codehilite .nc {
|
||||||
|
color: #3F6E75
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Class */
|
||||||
|
.codehilite .no {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Constant */
|
||||||
|
.codehilite .nd {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Decorator */
|
||||||
|
.codehilite .ni {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Entity */
|
||||||
|
.codehilite .ne {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Exception */
|
||||||
|
.codehilite .nf {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Function */
|
||||||
|
.codehilite .nl {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Label */
|
||||||
|
.codehilite .nn {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Namespace */
|
||||||
|
.codehilite .nx {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Other */
|
||||||
|
.codehilite .py {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Property */
|
||||||
|
.codehilite .nt {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Tag */
|
||||||
|
.codehilite .nv {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Variable */
|
||||||
|
.codehilite .ow {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Operator.Word */
|
||||||
|
.codehilite .mb {
|
||||||
|
color: #1C01CE
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.Number.Bin */
|
||||||
|
.codehilite .mf {
|
||||||
|
color: #1C01CE
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.Number.Float */
|
||||||
|
.codehilite .mh {
|
||||||
|
color: #1C01CE
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.Number.Hex */
|
||||||
|
.codehilite .mi {
|
||||||
|
color: #1C01CE
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.Number.Integer */
|
||||||
|
.codehilite .mo {
|
||||||
|
color: #1C01CE
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.Number.Oct */
|
||||||
|
.codehilite .sb {
|
||||||
|
color: #C41A16
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.String.Backtick */
|
||||||
|
.codehilite .sc {
|
||||||
|
color: #2300CE
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.String.Char */
|
||||||
|
.codehilite .sd {
|
||||||
|
color: #C41A16
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.String.Doc */
|
||||||
|
.codehilite .s2 {
|
||||||
|
color: #C41A16
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.String.Double */
|
||||||
|
.codehilite .se {
|
||||||
|
color: #C41A16
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.String.Escape */
|
||||||
|
.codehilite .sh {
|
||||||
|
color: #C41A16
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.String.Heredoc */
|
||||||
|
.codehilite .si {
|
||||||
|
color: #C41A16
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.String.Interpol */
|
||||||
|
.codehilite .sx {
|
||||||
|
color: #C41A16
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.String.Other */
|
||||||
|
.codehilite .sr {
|
||||||
|
color: #C41A16
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.String.Regex */
|
||||||
|
.codehilite .s1 {
|
||||||
|
color: #C41A16
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.String.Single */
|
||||||
|
.codehilite .ss {
|
||||||
|
color: #C41A16
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.String.Symbol */
|
||||||
|
.codehilite .bp {
|
||||||
|
color: #5B269A
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Builtin.Pseudo */
|
||||||
|
.codehilite .vc {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Variable.Class */
|
||||||
|
.codehilite .vg {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Variable.Global */
|
||||||
|
.codehilite .vi {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name.Variable.Instance */
|
||||||
|
.codehilite .il {
|
||||||
|
color: #1C01CE
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal.Number.Integer.Long */
|
||||||
@ -0,0 +1,232 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
|
from django.core.management import call_command
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
from django.templatetags.static import static
|
||||||
|
from django.test import Client, RequestFactory, TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from accounts.models import BlogUser
|
||||||
|
from blog.forms import BlogSearchForm
|
||||||
|
from blog.models import Article, Category, Tag, SideBar, Links
|
||||||
|
from blog.templatetags.blog_tags import load_pagination_info, load_articletags
|
||||||
|
from djangoblog.utils import get_current_site, get_sha256
|
||||||
|
from oauth.models import OAuthUser, OAuthConfig
|
||||||
|
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
|
|
||||||
|
class ArticleTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
|
||||||
|
def test_validate_article(self):
|
||||||
|
site = get_current_site().domain
|
||||||
|
user = BlogUser.objects.get_or_create(
|
||||||
|
email="liangliangyy@gmail.com",
|
||||||
|
username="liangliangyy")[0]
|
||||||
|
user.set_password("liangliangyy")
|
||||||
|
user.is_staff = True
|
||||||
|
user.is_superuser = True
|
||||||
|
user.save()
|
||||||
|
response = self.client.get(user.get_absolute_url())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
response = self.client.get('/admin/servermanager/emailsendlog/')
|
||||||
|
response = self.client.get('admin/admin/logentry/')
|
||||||
|
s = SideBar()
|
||||||
|
s.sequence = 1
|
||||||
|
s.name = 'test'
|
||||||
|
s.content = 'test content'
|
||||||
|
s.is_enable = True
|
||||||
|
s.save()
|
||||||
|
|
||||||
|
category = Category()
|
||||||
|
category.name = "category"
|
||||||
|
category.creation_time = timezone.now()
|
||||||
|
category.last_mod_time = timezone.now()
|
||||||
|
category.save()
|
||||||
|
|
||||||
|
tag = Tag()
|
||||||
|
tag.name = "nicetag"
|
||||||
|
tag.save()
|
||||||
|
|
||||||
|
article = Article()
|
||||||
|
article.title = "nicetitle"
|
||||||
|
article.body = "nicecontent"
|
||||||
|
article.author = user
|
||||||
|
article.category = category
|
||||||
|
article.type = 'a'
|
||||||
|
article.status = 'p'
|
||||||
|
|
||||||
|
article.save()
|
||||||
|
self.assertEqual(0, article.tags.count())
|
||||||
|
article.tags.add(tag)
|
||||||
|
article.save()
|
||||||
|
self.assertEqual(1, article.tags.count())
|
||||||
|
|
||||||
|
for i in range(20):
|
||||||
|
article = Article()
|
||||||
|
article.title = "nicetitle" + str(i)
|
||||||
|
article.body = "nicetitle" + str(i)
|
||||||
|
article.author = user
|
||||||
|
article.category = category
|
||||||
|
article.type = 'a'
|
||||||
|
article.status = 'p'
|
||||||
|
article.save()
|
||||||
|
article.tags.add(tag)
|
||||||
|
article.save()
|
||||||
|
from blog.documents import ELASTICSEARCH_ENABLED
|
||||||
|
if ELASTICSEARCH_ENABLED:
|
||||||
|
call_command("build_index")
|
||||||
|
response = self.client.get('/search', {'q': 'nicetitle'})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
response = self.client.get(article.get_absolute_url())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
from djangoblog.spider_notify import SpiderNotify
|
||||||
|
SpiderNotify.notify(article.get_absolute_url())
|
||||||
|
response = self.client.get(tag.get_absolute_url())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
response = self.client.get(category.get_absolute_url())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
response = self.client.get('/search', {'q': 'django'})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
s = load_articletags(article)
|
||||||
|
self.assertIsNotNone(s)
|
||||||
|
|
||||||
|
self.client.login(username='liangliangyy', password='liangliangyy')
|
||||||
|
|
||||||
|
response = self.client.get(reverse('blog:archives'))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
p = Paginator(Article.objects.all(), settings.PAGINATE_BY)
|
||||||
|
self.check_pagination(p, '', '')
|
||||||
|
|
||||||
|
p = Paginator(Article.objects.filter(tags=tag), settings.PAGINATE_BY)
|
||||||
|
self.check_pagination(p, '分类标签归档', tag.slug)
|
||||||
|
|
||||||
|
p = Paginator(
|
||||||
|
Article.objects.filter(
|
||||||
|
author__username='liangliangyy'), settings.PAGINATE_BY)
|
||||||
|
self.check_pagination(p, '作者文章归档', 'liangliangyy')
|
||||||
|
|
||||||
|
p = Paginator(Article.objects.filter(category=category), settings.PAGINATE_BY)
|
||||||
|
self.check_pagination(p, '分类目录归档', category.slug)
|
||||||
|
|
||||||
|
f = BlogSearchForm()
|
||||||
|
f.search()
|
||||||
|
# self.client.login(username='liangliangyy', password='liangliangyy')
|
||||||
|
from djangoblog.spider_notify import SpiderNotify
|
||||||
|
SpiderNotify.baidu_notify([article.get_full_url()])
|
||||||
|
|
||||||
|
from blog.templatetags.blog_tags import gravatar_url, gravatar
|
||||||
|
u = gravatar_url('liangliangyy@gmail.com')
|
||||||
|
u = gravatar('liangliangyy@gmail.com')
|
||||||
|
|
||||||
|
link = Links(
|
||||||
|
sequence=1,
|
||||||
|
name="lylinux",
|
||||||
|
link='https://wwww.lylinux.net')
|
||||||
|
link.save()
|
||||||
|
response = self.client.get('/links.html')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
response = self.client.get('/feed/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
response = self.client.get('/sitemap.xml')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
self.client.get("/admin/blog/article/1/delete/")
|
||||||
|
self.client.get('/admin/servermanager/emailsendlog/')
|
||||||
|
self.client.get('/admin/admin/logentry/')
|
||||||
|
self.client.get('/admin/admin/logentry/1/change/')
|
||||||
|
|
||||||
|
def check_pagination(self, p, type, value):
|
||||||
|
for page in range(1, p.num_pages + 1):
|
||||||
|
s = load_pagination_info(p.page(page), type, value)
|
||||||
|
self.assertIsNotNone(s)
|
||||||
|
if s['previous_url']:
|
||||||
|
response = self.client.get(s['previous_url'])
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
if s['next_url']:
|
||||||
|
response = self.client.get(s['next_url'])
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_image(self):
|
||||||
|
import requests
|
||||||
|
rsp = requests.get(
|
||||||
|
'https://www.python.org/static/img/python-logo.png')
|
||||||
|
imagepath = os.path.join(settings.BASE_DIR, 'python.png')
|
||||||
|
with open(imagepath, 'wb') as file:
|
||||||
|
file.write(rsp.content)
|
||||||
|
rsp = self.client.post('/upload')
|
||||||
|
self.assertEqual(rsp.status_code, 403)
|
||||||
|
sign = get_sha256(get_sha256(settings.SECRET_KEY))
|
||||||
|
with open(imagepath, 'rb') as file:
|
||||||
|
imgfile = SimpleUploadedFile(
|
||||||
|
'python.png', file.read(), content_type='image/jpg')
|
||||||
|
form_data = {'python.png': imgfile}
|
||||||
|
rsp = self.client.post(
|
||||||
|
'/upload?sign=' + sign, form_data, follow=True)
|
||||||
|
self.assertEqual(rsp.status_code, 200)
|
||||||
|
os.remove(imagepath)
|
||||||
|
from djangoblog.utils import save_user_avatar, send_email
|
||||||
|
send_email(['qq@qq.com'], 'testTitle', 'testContent')
|
||||||
|
save_user_avatar(
|
||||||
|
'https://www.python.org/static/img/python-logo.png')
|
||||||
|
|
||||||
|
def test_errorpage(self):
|
||||||
|
rsp = self.client.get('/eee')
|
||||||
|
self.assertEqual(rsp.status_code, 404)
|
||||||
|
|
||||||
|
def test_commands(self):
|
||||||
|
user = BlogUser.objects.get_or_create(
|
||||||
|
email="liangliangyy@gmail.com",
|
||||||
|
username="liangliangyy")[0]
|
||||||
|
user.set_password("liangliangyy")
|
||||||
|
user.is_staff = True
|
||||||
|
user.is_superuser = True
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
c = OAuthConfig()
|
||||||
|
c.type = 'qq'
|
||||||
|
c.appkey = 'appkey'
|
||||||
|
c.appsecret = 'appsecret'
|
||||||
|
c.save()
|
||||||
|
|
||||||
|
u = OAuthUser()
|
||||||
|
u.type = 'qq'
|
||||||
|
u.openid = 'openid'
|
||||||
|
u.user = user
|
||||||
|
u.picture = static("/blog/img/avatar.png")
|
||||||
|
u.metadata = '''
|
||||||
|
{
|
||||||
|
"figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30"
|
||||||
|
}'''
|
||||||
|
u.save()
|
||||||
|
|
||||||
|
u = OAuthUser()
|
||||||
|
u.type = 'qq'
|
||||||
|
u.openid = 'openid1'
|
||||||
|
u.picture = 'https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30'
|
||||||
|
u.metadata = '''
|
||||||
|
{
|
||||||
|
"figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30"
|
||||||
|
}'''
|
||||||
|
u.save()
|
||||||
|
|
||||||
|
from blog.documents import ELASTICSEARCH_ENABLED
|
||||||
|
if ELASTICSEARCH_ENABLED:
|
||||||
|
call_command("build_index")
|
||||||
|
call_command("ping_baidu", "all")
|
||||||
|
call_command("create_testdata")
|
||||||
|
call_command("clear_cache")
|
||||||
|
call_command("sync_user_avatar")
|
||||||
|
call_command("build_search_words")
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from django.views.decorators.cache import cache_page
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = "blog"
|
||||||
|
urlpatterns = [
|
||||||
|
path(
|
||||||
|
r'',
|
||||||
|
views.IndexView.as_view(),
|
||||||
|
name='index'),
|
||||||
|
path(
|
||||||
|
r'page/<int:page>/',
|
||||||
|
views.IndexView.as_view(),
|
||||||
|
name='index_page'),
|
||||||
|
path(
|
||||||
|
r'article/<int:year>/<int:month>/<int:day>/<int:article_id>.html',
|
||||||
|
views.ArticleDetailView.as_view(),
|
||||||
|
name='detailbyid'),
|
||||||
|
path(
|
||||||
|
r'category/<slug:category_name>.html',
|
||||||
|
views.CategoryDetailView.as_view(),
|
||||||
|
name='category_detail'),
|
||||||
|
path(
|
||||||
|
r'category/<slug:category_name>/<int:page>.html',
|
||||||
|
views.CategoryDetailView.as_view(),
|
||||||
|
name='category_detail_page'),
|
||||||
|
path(
|
||||||
|
r'author/<author_name>.html',
|
||||||
|
views.AuthorDetailView.as_view(),
|
||||||
|
name='author_detail'),
|
||||||
|
path(
|
||||||
|
r'author/<author_name>/<int:page>.html',
|
||||||
|
views.AuthorDetailView.as_view(),
|
||||||
|
name='author_detail_page'),
|
||||||
|
path(
|
||||||
|
r'tag/<slug:tag_name>.html',
|
||||||
|
views.TagDetailView.as_view(),
|
||||||
|
name='tag_detail'),
|
||||||
|
path(
|
||||||
|
r'tag/<slug:tag_name>/<int:page>.html',
|
||||||
|
views.TagDetailView.as_view(),
|
||||||
|
name='tag_detail_page'),
|
||||||
|
path(
|
||||||
|
'archives.html',
|
||||||
|
cache_page(
|
||||||
|
60 * 60)(
|
||||||
|
views.ArchivesView.as_view()),
|
||||||
|
name='archives'),
|
||||||
|
path(
|
||||||
|
'links.html',
|
||||||
|
views.LinkListView.as_view(),
|
||||||
|
name='links'),
|
||||||
|
path(
|
||||||
|
r'upload',
|
||||||
|
views.fileupload,
|
||||||
|
name='upload'),
|
||||||
|
path(
|
||||||
|
r'clean',
|
||||||
|
views.clean_cache_view,
|
||||||
|
name='clean'),
|
||||||
|
]
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue