Compare commits
121 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0368630c92 | ||
|
|
02c1ba0c71 | ||
|
|
83cc8832cb | ||
|
|
14a5d43dc8 | ||
|
|
bea079dd05 | ||
|
|
df8170fa55 | ||
|
|
2904d5938d | ||
|
|
18bfecb026 | ||
|
|
4ee5a4fd9f | ||
|
|
bbaedfad33 | ||
|
|
de413f1473 | ||
|
|
d012385088 | ||
|
|
d18a330135 | ||
|
|
a8f7ef8ef7 | ||
|
|
d6972cacfb | ||
|
|
3b21e44422 | ||
|
|
1a78ca68bb | ||
|
|
fac7b8cd5b | ||
|
|
8f780545a4 | ||
|
|
218f7d92d7 | ||
|
|
621bacff1c | ||
|
|
9a849a979c | ||
|
|
e8366e5280 | ||
|
|
0a8270e7cf | ||
|
|
aad8b220d1 | ||
|
|
d5e0a0a623 | ||
|
|
8cd94d49e8 | ||
|
|
08b805a547 | ||
|
|
ecac30136b | ||
|
|
d694408af6 | ||
|
|
6e284f6ae8 | ||
|
|
62c049a6de | ||
|
|
dee7249347 | ||
|
|
17946c8dac | ||
|
|
fa2326949e | ||
|
|
8177d9ba0f | ||
|
|
8781a6572d | ||
|
|
c7d518071c | ||
|
|
ea96c63289 | ||
|
|
8485a64726 | ||
|
|
e89bd44412 | ||
|
|
2e0e48bb38 | ||
|
|
040fa7c192 | ||
|
|
7000097602 | ||
|
|
3cbc6b5609 | ||
|
|
8ff52f542e | ||
|
|
8cc0fcaed2 | ||
|
|
d4197773bf | ||
|
|
f530b3dc7a | ||
|
|
d1bf4d4bbb | ||
|
|
d584a3db25 | ||
|
|
aaa3737ae0 | ||
|
|
5072859e57 | ||
|
|
ead3c6ef76 | ||
|
|
d734cb813e | ||
|
|
8aa24d4771 | ||
|
|
c714ff4dbe | ||
|
|
a32545c1dc | ||
|
|
dfe8e1fd42 | ||
|
|
729d573460 | ||
|
|
8472b541aa | ||
|
|
7e95e985ec | ||
|
|
f7e2aa9b83 | ||
|
|
99cf428470 | ||
|
|
60a533f9c8 | ||
|
|
723416575f | ||
|
|
e34889953a | ||
|
|
ec8a879efa | ||
|
|
f44ebe0d05 | ||
|
|
13c82cdbf9 | ||
|
|
e9a60ece81 | ||
|
|
9575a86480 | ||
|
|
12036b9972 | ||
|
|
bffb260dfa | ||
|
|
9d460a3623 | ||
|
|
56c5f28348 | ||
|
|
29d4dcb73d | ||
|
|
e60441ec99 | ||
|
|
608e024caa | ||
|
|
f596b12f12 | ||
|
|
bdd41a5ba2 | ||
|
|
80e566917e | ||
|
|
4294c132c6 | ||
|
|
da12daaf03 | ||
|
|
150cf5ebac | ||
|
|
01b9022451 | ||
|
|
2bda5bbbf7 | ||
|
|
a743a4e202 | ||
|
|
ffa7513f9e | ||
|
|
8cb6ed2f60 | ||
|
|
1d6375bf84 | ||
|
|
cfab867e0d | ||
|
|
29dd7c9ee7 | ||
|
|
72cb046e37 | ||
|
|
a555906a32 | ||
|
|
2e255aba0d | ||
|
|
a136a18a8e | ||
|
|
cdf4c0d1bb | ||
|
|
3aedbfbdc3 | ||
|
|
dc7c688ed5 | ||
|
|
8aa8f15ad7 | ||
|
|
6b8a231eee | ||
|
|
b0e338f08a | ||
|
|
1a6e0c8706 | ||
|
|
c7ceae4350 | ||
|
|
caefa6099b | ||
|
|
2e4645bb0c | ||
|
|
6c966f8ef6 | ||
|
|
7140cb0f93 | ||
|
|
b95c3f6685 | ||
|
|
81cd551975 | ||
|
|
55777fd948 | ||
|
|
3b5b505116 | ||
|
|
aea3f62f9b | ||
|
|
201c493658 | ||
|
|
8ffc6a0236 | ||
|
|
84ad88b30b | ||
|
|
233f2a911f | ||
|
|
989d8765d7 | ||
|
|
2fcd207dc7 | ||
|
|
a3dc5f283a |
@@ -1,4 +1,5 @@
|
||||
# only set this to true when testing/debugging
|
||||
# when unset: 1 (true) - dont unset this, just for development
|
||||
DEBUG=0
|
||||
|
||||
# hosts the application can run under e.g. recipes.mydomain.com,cooking.mydomain.com,...
|
||||
@@ -18,4 +19,17 @@ POSTGRES_DB=djangodb
|
||||
# Serve mediafiles directly using gunicorn. Basically everyone recommends not doing this. Please use any of the examples
|
||||
# provided that include an additional nxginx container to handle media file serving.
|
||||
# If you know what you are doing turn this back on (1) to serve media files using djangos serve() method.
|
||||
# when unset: 1 (true) - this is temporary until an appropriate amount of time has passed for everyone to migrate
|
||||
GUNICORN_MEDIA=0
|
||||
|
||||
|
||||
# allow authentication via reverse proxy (e.g. authelia), leave of if you dont know what you are doing
|
||||
# docs: https://github.com/vabene1111/recipes/tree/develop/docs/docker/nginx-proxy%20with%20proxy%20authentication
|
||||
# when unset: 0 (false)
|
||||
REVERSE_PROXY_AUTH=0
|
||||
|
||||
|
||||
# the default value for the user preference 'comments' (enable/disable commenting system)
|
||||
# when unset: 1 (true)
|
||||
COMMENT_PREF_DEFAULT=1
|
||||
|
||||
|
||||
15
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Version
|
||||
Please provide your current version (can be found on the system page since v0.8.4)
|
||||
Version:
|
||||
|
||||
### Bug description
|
||||
A clear and concise description of what the bug is.
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
27
.github/ISSUE_TEMPLATE/help-request.md
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: Help request
|
||||
about: If there is anything wrong with your setup
|
||||
title: ''
|
||||
labels: setup issue
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Version
|
||||
Please provide your current version (can be found on the system page since v0.8.4)
|
||||
Version:
|
||||
|
||||
### Issue
|
||||
Please describe your problem here
|
||||
|
||||
### `.env`
|
||||
Please include your `.env` config file (**make sure to remove/replace all secrets**)
|
||||
```
|
||||
env content
|
||||
```
|
||||
|
||||
### `docker-compose.yml`
|
||||
When running with docker compose please provide your `docker-compose.yml`
|
||||
```
|
||||
docker-compose.yml content
|
||||
```
|
||||
51
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: "Code scanning - action"
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 13 * * 2'
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
# with:
|
||||
# languages: go, javascript, csharp, python, cpp, java
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
# - name: Autobuild
|
||||
# uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
8
.github/workflows/docker-publish-dev.yml
vendored
@@ -10,6 +10,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Update version file
|
||||
uses: DamianReeves/write-file-action@v1.0
|
||||
with:
|
||||
path: recipes/version.py
|
||||
contents: |
|
||||
VERSION_NUMBER = 'develop'
|
||||
BUILD_REF = '${{ github.sha }}'
|
||||
write-mode: overwrite
|
||||
- name: Publish to Registry
|
||||
uses: elgohr/Publish-Docker-Github-Action@2.13
|
||||
with:
|
||||
|
||||
15
.github/workflows/docker-publish-latest.yml
vendored
@@ -1,14 +1,25 @@
|
||||
name: publish latest image docker
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Get version number
|
||||
id: get_version
|
||||
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
|
||||
- name: Update version file
|
||||
uses: DamianReeves/write-file-action@v1.0
|
||||
with:
|
||||
path: recipes/version.py
|
||||
contents: |
|
||||
VERSION_NUMBER = '${{ steps.get_version.outputs.VERSION }}'
|
||||
BUILD_REF = '${{ github.sha }}'
|
||||
write-mode: overwrite
|
||||
- name: Build and publish image
|
||||
uses: ilteoood/docker_buildx@master
|
||||
with:
|
||||
|
||||
40
.github/workflows/docker-publish-release.yml
vendored
@@ -7,19 +7,27 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: Build image job
|
||||
steps:
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@master#
|
||||
- name: Get the version
|
||||
id: get_version
|
||||
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
|
||||
- name: Build and publish image
|
||||
uses: ilteoood/docker_buildx@master
|
||||
with:
|
||||
publish: true
|
||||
imageName: vabene1111/recipes
|
||||
tag: ${{ steps.get_version.outputs.VERSION }}
|
||||
dockerHubUser: ${{ secrets.DOCKER_USERNAME }}
|
||||
dockerHubPassword: ${{ secrets.DOCKER_PASSWORD }}
|
||||
runs-on: ubuntu-latest
|
||||
name: Build image job
|
||||
steps:
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@master#
|
||||
- name: Get version number
|
||||
id: get_version
|
||||
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
|
||||
- name: Update version file
|
||||
uses: DamianReeves/write-file-action@v1.0
|
||||
with:
|
||||
path: recipes/version.py
|
||||
contents: |
|
||||
VERSION_NUMBER = '${{ steps.get_version.outputs.VERSION }}'
|
||||
BUILD_REF = '${{ github.sha }}'
|
||||
write-mode: overwrite
|
||||
- name: Build and publish image
|
||||
uses: ilteoood/docker_buildx@master
|
||||
with:
|
||||
publish: true
|
||||
imageName: vabene1111/recipes
|
||||
tag: ${{ steps.get_version.outputs.VERSION }}
|
||||
dockerHubUser: ${{ secrets.DOCKER_USERNAME }}
|
||||
dockerHubPassword: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
2
.idea/dictionaries/vabene1111_PC.xml
generated
@@ -1,7 +1,9 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="vabene1111-PC">
|
||||
<words>
|
||||
<w>csrftoken</w>
|
||||
<w>gunicorn</w>
|
||||
<w>ical</w>
|
||||
<w>traefik</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
|
||||
2
.idea/recipes.iml
generated
@@ -27,8 +27,8 @@
|
||||
<option name="TEMPLATE_CONFIGURATION" value="Django" />
|
||||
<option name="TEMPLATE_FOLDERS">
|
||||
<list>
|
||||
<option value="$MODULE_DIR$/templates" />
|
||||
<option value="$MODULE_DIR$/cookbook/templates" />
|
||||
<option value="$MODULE_DIR$/recipes/templates" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
2
boot.sh
@@ -8,4 +8,4 @@ echo "Done"
|
||||
|
||||
chmod -R 755 /opt/recipes/mediafiles
|
||||
|
||||
exec gunicorn -b :8080 --access-logfile - --error-logfile - recipes.wsgi
|
||||
exec gunicorn -b :8080 --access-logfile - --error-logfile - --log-level INFO recipes.wsgi
|
||||
@@ -3,7 +3,7 @@ from .models import *
|
||||
|
||||
|
||||
class UserPreferenceAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'theme', 'nav_color', 'default_page', 'search_style')
|
||||
list_display = ('name', 'theme', 'nav_color', 'default_page', 'search_style', 'comments')
|
||||
|
||||
@staticmethod
|
||||
def name(obj):
|
||||
@@ -94,7 +94,7 @@ admin.site.register(RecipeBookEntry, RecipeBookEntryAdmin)
|
||||
|
||||
|
||||
class MealPlanAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'recipe', 'meal', 'date')
|
||||
list_display = ('user', 'recipe', 'meal_type', 'date')
|
||||
|
||||
@staticmethod
|
||||
def user(obj):
|
||||
@@ -102,3 +102,31 @@ class MealPlanAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
admin.site.register(MealPlan, MealPlanAdmin)
|
||||
|
||||
|
||||
class MealTypeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'created_by', 'order')
|
||||
|
||||
|
||||
admin.site.register(MealType, MealTypeAdmin)
|
||||
|
||||
|
||||
class ViewLogAdmin(admin.ModelAdmin):
|
||||
list_display = ('recipe', 'created_by', 'created_at')
|
||||
|
||||
|
||||
admin.site.register(ViewLog, ViewLogAdmin)
|
||||
|
||||
|
||||
class CookLogAdmin(admin.ModelAdmin):
|
||||
list_display = ('recipe', 'created_by', 'created_at', 'rating', 'servings')
|
||||
|
||||
|
||||
admin.site.register(CookLog, CookLogAdmin)
|
||||
|
||||
|
||||
class ShareLinkAdmin(admin.ModelAdmin):
|
||||
list_display = ('recipe', 'created_by', 'uuid', 'created_at',)
|
||||
|
||||
|
||||
admin.site.register(ShareLink, ShareLinkAdmin)
|
||||
|
||||
@@ -31,13 +31,15 @@ class UserPreferenceForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = UserPreference
|
||||
fields = ('default_unit', 'theme', 'nav_color', 'default_page', 'show_recent', 'search_style', 'plan_share')
|
||||
fields = ('default_unit', 'theme', 'nav_color', 'default_page', 'show_recent', 'search_style', 'plan_share', 'ingredient_decimals', 'comments')
|
||||
|
||||
help_texts = {
|
||||
'nav_color': _('Color of the top navigation bar. Not all colors work with all themes, just try them out!'),
|
||||
'default_unit': _('Default Unit to be used when inserting a new ingredient into a recipe.'),
|
||||
'plan_share': _('Default user to share newly created meal plan entries with.'),
|
||||
'show_recent': _('Show recently viewed recipes on search page.'),
|
||||
'ingredient_decimals': _('Number of decimals to round ingredients.'),
|
||||
'comments': _('If you want to be able to create and see comments underneath recipes.')
|
||||
}
|
||||
|
||||
widgets = {
|
||||
@@ -263,7 +265,7 @@ class MealPlanForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = MealPlan
|
||||
fields = ('recipe', 'title', 'meal', 'note', 'date', 'shared')
|
||||
fields = ('recipe', 'title', 'meal_type', 'note', 'date', 'shared')
|
||||
|
||||
help_texts = {
|
||||
'shared': _('You can list default users to share recipes with in the settings.'),
|
||||
|
||||
10
cookbook/helper/permission_config.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# Permission Config
|
||||
from cookbook.helper.permission_helper import CustomIsUser, CustomIsOwner, CustomIsAdmin, CustomIsGuest
|
||||
|
||||
|
||||
class PermissionConfig:
|
||||
BOOKS = {
|
||||
'owner': True,
|
||||
'groups': ['user'],
|
||||
'drf': [CustomIsUser],
|
||||
}
|
||||
@@ -3,12 +3,25 @@ Source: https://djangosnippets.org/snippets/1703/
|
||||
"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from django.utils.translation import gettext as _
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.urls import reverse_lazy, reverse
|
||||
from rest_framework import permissions
|
||||
|
||||
from cookbook.models import ShareLink
|
||||
|
||||
|
||||
# Helper Functions
|
||||
|
||||
def get_allowed_groups(groups_required):
|
||||
"""
|
||||
Builds a list of all groups equal or higher to the provided groups
|
||||
This means checking for guest will also allow admins to access
|
||||
:param groups_required: list or tuple of groups
|
||||
:return: tuple of groups
|
||||
"""
|
||||
groups_allowed = tuple(groups_required)
|
||||
if 'guest' in groups_required:
|
||||
groups_allowed = groups_allowed + ('user', 'admin')
|
||||
@@ -17,15 +30,70 @@ def get_allowed_groups(groups_required):
|
||||
return groups_allowed
|
||||
|
||||
|
||||
def has_group_permission(user, groups):
|
||||
"""
|
||||
Tests if a given user is member of a certain group (or any higher group)
|
||||
Superusers always bypass permission checks. Unauthenticated users cant be member of any
|
||||
group thus always return false.
|
||||
:param user: django auth user object
|
||||
:param groups: list or tuple of groups the user should be checked for
|
||||
:return: True if user is in allowed groups, false otherwise
|
||||
"""
|
||||
if not user.is_authenticated:
|
||||
return False
|
||||
groups_allowed = get_allowed_groups(groups)
|
||||
if user.is_authenticated:
|
||||
if user.is_superuser | bool(user.groups.filter(name__in=groups_allowed)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_object_owner(user, obj):
|
||||
"""
|
||||
Tests if a given user is the owner of a given object
|
||||
test performed by checking user against the objects user and create_by field (if exists)
|
||||
superusers bypass all checks, unauthenticated users cannot own anything
|
||||
:param user django auth user object
|
||||
:param obj any object that should be tested
|
||||
:return: true if user is owner of object, false otherwise
|
||||
"""
|
||||
# TODO this could be improved/cleaned up by adding get_owner methods to all models that allow owner checks
|
||||
if not user.is_authenticated:
|
||||
return False
|
||||
if user.is_superuser:
|
||||
return True
|
||||
if owner := getattr(obj, 'created_by', None):
|
||||
return owner == user
|
||||
if owner := getattr(obj, 'user', None):
|
||||
return owner == user
|
||||
return False
|
||||
|
||||
|
||||
def share_link_valid(recipe, share):
|
||||
"""
|
||||
Verifies the validity of a share uuid
|
||||
:param recipe: recipe object
|
||||
:param share: share uuid
|
||||
:return: true if a share link with the given recipe and uuid exists, false otherwise
|
||||
"""
|
||||
try:
|
||||
return True if ShareLink.objects.filter(recipe=recipe, uuid=share).exists() else False
|
||||
except ValidationError:
|
||||
return False
|
||||
|
||||
|
||||
# Django Views
|
||||
|
||||
def group_required(*groups_required):
|
||||
"""Requires user membership in at least one of the groups passed in."""
|
||||
"""
|
||||
Decorator that tests the requesting user to be member of at least one of the provided groups
|
||||
or higher level groups
|
||||
:param groups_required: list of required groups
|
||||
:return: true if member of group, false otherwise
|
||||
"""
|
||||
|
||||
def in_groups(u):
|
||||
groups_allowed = get_allowed_groups(groups_required)
|
||||
if u.is_authenticated:
|
||||
if u.is_superuser | bool(u.groups.filter(name__in=groups_allowed)):
|
||||
return True
|
||||
return False
|
||||
return has_group_permission(u, groups_required)
|
||||
|
||||
return user_passes_test(in_groups, login_url='index')
|
||||
|
||||
@@ -38,18 +106,10 @@ class GroupRequiredMixin(object):
|
||||
groups_required = None
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_authenticated:
|
||||
messages.add_message(request, messages.ERROR, _('You are not logged in and therefore cannot view this page!'))
|
||||
return HttpResponseRedirect(reverse_lazy('login'))
|
||||
else:
|
||||
if not request.user.is_superuser:
|
||||
group_allowed = get_allowed_groups(self.groups_required)
|
||||
user_groups = []
|
||||
for group in request.user.groups.values_list('name', flat=True):
|
||||
user_groups.append(group)
|
||||
if len(set(user_groups).intersection(group_allowed)) <= 0:
|
||||
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
|
||||
return HttpResponseRedirect(reverse_lazy('index'))
|
||||
if not has_group_permission(request.user, self.groups_required):
|
||||
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
|
||||
return HttpResponseRedirect(reverse_lazy('index'))
|
||||
|
||||
return super(GroupRequiredMixin, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
@@ -60,9 +120,59 @@ class OwnerRequiredMixin(object):
|
||||
messages.add_message(request, messages.ERROR, _('You are not logged in and therefore cannot view this page!'))
|
||||
return HttpResponseRedirect(reverse_lazy('login'))
|
||||
else:
|
||||
obj = self.get_object()
|
||||
if not (obj.created_by == request.user or request.user.is_superuser):
|
||||
if not is_object_owner(request.user, self.get_object()):
|
||||
messages.add_message(request, messages.ERROR, _('You cannot interact with this object as its not owned by you!'))
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
return super(OwnerRequiredMixin, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
# Django Rest Framework Permission classes
|
||||
|
||||
class CustomIsOwner(permissions.BasePermission):
|
||||
"""
|
||||
Custom permission class for django rest framework views
|
||||
verifies user has ownership over object
|
||||
(either user or created_by or user is request user)
|
||||
"""
|
||||
message = _('You cannot interact with this object as its not owned by you!')
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return request.user.is_authenticated
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
return is_object_owner(request.user, obj)
|
||||
|
||||
|
||||
class CustomIsGuest(permissions.BasePermission):
|
||||
"""
|
||||
Custom permission class for django rest framework views
|
||||
verifies the user is member of at least the group: guest
|
||||
"""
|
||||
message = _('You do not have the required permissions to view this page!')
|
||||
|
||||
def has_permission(self, request, view):
|
||||
has_group_permission(request.user, ['guest'])
|
||||
|
||||
|
||||
class CustomIsUser(permissions.BasePermission):
|
||||
"""
|
||||
Custom permission class for django rest framework views
|
||||
verifies the user is member of at least the group: user
|
||||
"""
|
||||
message = _('You do not have the required permissions to view this page!')
|
||||
|
||||
def has_permission(self, request, view):
|
||||
has_group_permission(request.user, ['user'])
|
||||
|
||||
|
||||
class CustomIsAdmin(permissions.BasePermission):
|
||||
"""
|
||||
Custom permission class for django rest framework views
|
||||
verifies the user is member of at least the group: admin
|
||||
"""
|
||||
message = _('You do not have the required permissions to view this page!')
|
||||
|
||||
def has_permission(self, request, view):
|
||||
has_group_permission(request.user, ['admin'])
|
||||
|
||||
|
||||
BIN
cookbook/locale/nl/LC_MESSAGES/django.mo
Normal file
1160
cookbook/locale/nl/LC_MESSAGES/django.po
Normal file
27
cookbook/migrations/0046_auto_20200602_1133.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 3.0.5 on 2020-06-02 09:33
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0045_userpreference_show_recent'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='MealType',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=128)),
|
||||
('order', models.IntegerField(default=0)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mealplan',
|
||||
name='meal_type',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.MealType'),
|
||||
),
|
||||
]
|
||||
51
cookbook/migrations/0047_auto_20200602_1133.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# Generated by Django 3.0.5 on 2020-06-02 09:33
|
||||
|
||||
from django.db import migrations
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
||||
def migrate_meal_types(apps, schema_editor):
|
||||
MealPlan = apps.get_model('cookbook', 'MealPlan')
|
||||
MealType = apps.get_model('cookbook', 'MealType')
|
||||
|
||||
breakfast = MealType.objects.create(
|
||||
name=_('Breakfast'),
|
||||
order=0,
|
||||
)
|
||||
|
||||
lunch = MealType.objects.create(
|
||||
name=_('Lunch'),
|
||||
order=0,
|
||||
)
|
||||
|
||||
dinner = MealType.objects.create(
|
||||
name=_('Dinner'),
|
||||
order=0,
|
||||
)
|
||||
|
||||
other = MealType.objects.create(
|
||||
name=_('Other'),
|
||||
order=0,
|
||||
)
|
||||
|
||||
for m in MealPlan.objects.all():
|
||||
if m.meal == 'BREAKFAST':
|
||||
m.meal_type = breakfast
|
||||
if m.meal == 'LUNCH':
|
||||
m.meal_type = lunch
|
||||
if m.meal == 'DINNER':
|
||||
m.meal_type = dinner
|
||||
if m.meal == 'OTHER':
|
||||
m.meal_type = other
|
||||
|
||||
m.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('cookbook', '0046_auto_20200602_1133'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_meal_types),
|
||||
]
|
||||
23
cookbook/migrations/0048_auto_20200602_1140.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.0.5 on 2020-06-02 09:40
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0047_auto_20200602_1133'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='mealplan',
|
||||
name='meal',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mealplan',
|
||||
name='meal_type',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.MealType'),
|
||||
),
|
||||
]
|
||||
21
cookbook/migrations/0049_mealtype_created_by.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 3.0.7 on 2020-06-11 13:08
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('cookbook', '0048_auto_20200602_1140'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='mealtype',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
30
cookbook/migrations/0050_auto_20200611_1509.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# Generated by Django 3.0.7 on 2020-06-11 13:09
|
||||
|
||||
from django.db import migrations
|
||||
from django.db.models import Q
|
||||
|
||||
|
||||
def migrate_meal_types(apps, schema_editor):
|
||||
MealPlan = apps.get_model('cookbook', 'MealPlan')
|
||||
MealType = apps.get_model('cookbook', 'MealType')
|
||||
User = apps.get_model('auth', 'User')
|
||||
|
||||
for u in User.objects.all():
|
||||
for t in MealType.objects.filter(created_by=None).all():
|
||||
user_type = MealType.objects.create(
|
||||
name=t.name,
|
||||
created_by=u,
|
||||
)
|
||||
MealPlan.objects.filter(Q(created_by=u) and Q(meal_type=t)).update(meal_type=user_type)
|
||||
|
||||
MealType.objects.filter(created_by=None).delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('cookbook', '0049_mealtype_created_by'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_meal_types),
|
||||
]
|
||||
21
cookbook/migrations/0051_auto_20200611_1518.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 3.0.7 on 2020-06-11 13:18
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('cookbook', '0050_auto_20200611_1509'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='mealtype',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.0.7 on 2020-06-11 20:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0051_auto_20200611_1518'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userpreference',
|
||||
name='ingredient_decimals',
|
||||
field=models.IntegerField(default=2),
|
||||
),
|
||||
]
|
||||
18
cookbook/migrations/0053_auto_20200611_2217.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.0.7 on 2020-06-11 20:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0052_userpreference_ingredient_decimals'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='recipeingredient',
|
||||
name='amount',
|
||||
field=models.DecimalField(decimal_places=16, default=0, max_digits=32),
|
||||
),
|
||||
]
|
||||
27
cookbook/migrations/0054_sharelink.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 3.0.7 on 2020-06-16 08:57
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('cookbook', '0053_auto_20200611_2217'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ShareLink',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('uuid', models.UUIDField(default=uuid.UUID('dbbf5150-0795-4305-b9bd-3952dfa2264b'))),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe')),
|
||||
],
|
||||
),
|
||||
]
|
||||
24
cookbook/migrations/0055_auto_20200616_1236.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 3.0.7 on 2020-06-16 10:36
|
||||
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0054_sharelink'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userpreference',
|
||||
name='comments',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sharelink',
|
||||
name='uuid',
|
||||
field=models.UUIDField(default=uuid.UUID('a6e8f192-cc03-4dd4-8a03-58d7ab6b7df7')),
|
||||
),
|
||||
]
|
||||
@@ -1,11 +1,13 @@
|
||||
import re
|
||||
|
||||
import uuid
|
||||
from annoying.fields import AutoOneToOneField
|
||||
from django.contrib import auth
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import gettext as _
|
||||
from django.db import models
|
||||
|
||||
from recipes.settings import COMMENT_PREF_DEFAULT
|
||||
|
||||
|
||||
def get_user_name(self):
|
||||
if not (name := f"{self.first_name} {self.last_name}") == " ":
|
||||
@@ -63,6 +65,8 @@ class UserPreference(models.Model):
|
||||
search_style = models.CharField(choices=SEARCH_STYLE, max_length=64, default=LARGE)
|
||||
show_recent = models.BooleanField(default=True)
|
||||
plan_share = models.ManyToManyField(User, blank=True, related_name='plan_share_default')
|
||||
ingredient_decimals = models.IntegerField(default=2)
|
||||
comments = models.BooleanField(default=COMMENT_PREF_DEFAULT)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.user)
|
||||
@@ -163,7 +167,7 @@ class RecipeIngredient(models.Model):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
ingredient = models.ForeignKey(Ingredient, on_delete=models.PROTECT)
|
||||
unit = models.ForeignKey(Unit, on_delete=models.PROTECT)
|
||||
amount = models.DecimalField(default=0, decimal_places=2, max_digits=16)
|
||||
amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
|
||||
note = models.CharField(max_length=64, null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
@@ -211,18 +215,21 @@ class RecipeBookEntry(models.Model):
|
||||
return self.recipe.name
|
||||
|
||||
|
||||
class MealPlan(models.Model):
|
||||
BREAKFAST = 'BREAKFAST'
|
||||
LUNCH = 'LUNCH'
|
||||
DINNER = 'DINNER'
|
||||
OTHER = 'OTHER'
|
||||
MEAL_TYPES = ((BREAKFAST, _('Breakfast')), (LUNCH, _('Lunch')), (DINNER, _('Dinner')), (OTHER, _('Other')),)
|
||||
class MealType(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
order = models.IntegerField(default=0)
|
||||
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class MealPlan(models.Model):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, blank=True, null=True)
|
||||
title = models.CharField(max_length=64, blank=True, default='')
|
||||
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
shared = models.ManyToManyField(User, blank=True, related_name='plan_share')
|
||||
meal = models.CharField(choices=MEAL_TYPES, max_length=128, default=BREAKFAST)
|
||||
meal_type = models.ForeignKey(MealType, on_delete=models.CASCADE)
|
||||
note = models.TextField(blank=True)
|
||||
date = models.DateField()
|
||||
|
||||
@@ -232,8 +239,17 @@ class MealPlan(models.Model):
|
||||
return str(self.recipe)
|
||||
|
||||
def get_meal_name(self):
|
||||
meals = dict(self.MEAL_TYPES)
|
||||
return meals.get(self.meal)
|
||||
return self.meal_type.name
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.get_label()} - {self.date} - {self.meal_type.name}'
|
||||
|
||||
|
||||
class ShareLink(models.Model):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
uuid = models.UUIDField(default=uuid.uuid4())
|
||||
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
|
||||
class CookLog(models.Model):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import base64
|
||||
import io
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
@@ -90,14 +91,14 @@ class Dropbox(Provider):
|
||||
return response['url']
|
||||
|
||||
@staticmethod
|
||||
def get_base64_file(recipe):
|
||||
def get_file(recipe):
|
||||
if not recipe.link:
|
||||
recipe.link = Dropbox.get_share_link(recipe)
|
||||
recipe.save()
|
||||
|
||||
response = requests.get(recipe.link.replace('www.dropbox.', 'dl.dropboxusercontent.'))
|
||||
|
||||
return base64.b64encode(response.content)
|
||||
return io.BytesIO(response.content)
|
||||
|
||||
@staticmethod
|
||||
def rename_file(recipe, new_name):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import base64
|
||||
import io
|
||||
import os
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
@@ -85,18 +86,17 @@ class Nextcloud(Provider):
|
||||
return Nextcloud.create_share_link(recipe)
|
||||
|
||||
@staticmethod
|
||||
def get_base64_file(recipe):
|
||||
def get_file(recipe):
|
||||
client = Nextcloud.get_client(recipe.storage)
|
||||
|
||||
tmp_file_path = tempfile.gettempdir() + '/' + recipe.name + '.pdf'
|
||||
|
||||
client.download_file(remote_path=recipe.file_path, local_path=tmp_file_path)
|
||||
|
||||
val = base64.b64encode(open(tmp_file_path, 'rb').read())
|
||||
|
||||
file = io.BytesIO(open(tmp_file_path, 'rb').read())
|
||||
os.remove(tmp_file_path)
|
||||
|
||||
return val
|
||||
return file
|
||||
|
||||
@staticmethod
|
||||
def rename_file(recipe, new_name):
|
||||
|
||||
@@ -12,7 +12,7 @@ class Provider:
|
||||
raise Exception('Method not implemented in storage provider')
|
||||
|
||||
@staticmethod
|
||||
def get_base64_file(recipe):
|
||||
def get_file(recipe):
|
||||
raise Exception('Method not implemented in storage provider')
|
||||
|
||||
@staticmethod
|
||||
|
||||
128
cookbook/serializer.py
Normal file
@@ -0,0 +1,128 @@
|
||||
from django.contrib.auth.models import User
|
||||
from rest_framework import serializers
|
||||
|
||||
from cookbook.models import MealPlan, MealType, Recipe, ViewLog, UserPreference, Storage, Sync, SyncLog, Keyword, Ingredient, Unit, RecipeIngredient, Comment, RecipeImport, RecipeBook, RecipeBookEntry, ShareLink, CookLog
|
||||
from cookbook.templatetags.custom_tags import markdown
|
||||
|
||||
|
||||
class UserNameSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('id', 'username', 'first_name', 'last_name')
|
||||
|
||||
|
||||
class UserPreferenceSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = UserPreference
|
||||
fields = '__all__'
|
||||
read_only_fields = ['user']
|
||||
|
||||
|
||||
class StorageSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Storage
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class SyncSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Sync
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class SyncLogSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SyncLog
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class KeywordSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Keyword
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class RecipeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Recipe
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class UnitSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Unit
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class IngredientSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Ingredient
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class RecipeIngredientSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = RecipeIngredient
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class CommentSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Comment
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class RecipeImportSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = RecipeImport
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class RecipeBookSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = RecipeBook
|
||||
fields = '__all__'
|
||||
read_only_fields = ['id', 'created_by']
|
||||
|
||||
|
||||
class RecipeBookEntrySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = RecipeBookEntry
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class MealTypeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = MealType
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class MealPlanSerializer(serializers.ModelSerializer):
|
||||
recipe_name = serializers.ReadOnlyField(source='recipe.name')
|
||||
meal_type_name = serializers.ReadOnlyField(source='meal_type.name')
|
||||
note_markdown = serializers.SerializerMethodField('get_note_markdown')
|
||||
|
||||
def get_note_markdown(self, obj):
|
||||
return markdown(obj.note)
|
||||
|
||||
class Meta:
|
||||
model = MealPlan
|
||||
fields = ('id', 'title', 'recipe', 'note', 'note_markdown', 'date', 'meal_type', 'created_by', 'shared', 'recipe_name', 'meal_type_name')
|
||||
|
||||
|
||||
class ShareLinkSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ShareLink
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class CookLogSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = CookLog
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class ViewLogSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ViewLog
|
||||
fields = '__all__'
|
||||
2
cookbook/static/js/Sortable.min.js
vendored
Normal file
1
cookbook/static/js/js.cookie.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(e){var n;if("function"==typeof define&&define.amd&&(define(e),n=!0),"object"==typeof exports&&(module.exports=e(),n=!0),!n){var t=window.Cookies,o=window.Cookies=e();o.noConflict=function(){return window.Cookies=t,o}}}(function(){function f(){for(var e=0,n={};e<arguments.length;e++){var t=arguments[e];for(var o in t)n[o]=t[o]}return n}function a(e){return e.replace(/(%[0-9A-Z]{2})+/g,decodeURIComponent)}return function e(u){function c(){}function t(e,n,t){if("undefined"!=typeof document){"number"==typeof(t=f({path:"/"},c.defaults,t)).expires&&(t.expires=new Date(1*new Date+864e5*t.expires)),t.expires=t.expires?t.expires.toUTCString():"";try{var o=JSON.stringify(n);/^[\{\[]/.test(o)&&(n=o)}catch(e){}n=u.write?u.write(n,e):encodeURIComponent(String(n)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),e=encodeURIComponent(String(e)).replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent).replace(/[\(\)]/g,escape);var r="";for(var i in t)t[i]&&(r+="; "+i,!0!==t[i]&&(r+="="+t[i].split(";")[0]));return document.cookie=e+"="+n+r}}function n(e,n){if("undefined"!=typeof document){for(var t={},o=document.cookie?document.cookie.split("; "):[],r=0;r<o.length;r++){var i=o[r].split("="),c=i.slice(1).join("=");n||'"'!==c.charAt(0)||(c=c.slice(1,-1));try{var f=a(i[0]);if(c=(u.read||u)(c,f)||a(c),n)try{c=JSON.parse(c)}catch(e){}if(t[f]=c,e===f)break}catch(e){}}return e?t[e]:t}}return c.set=t,c.get=function(e){return n(e,!1)},c.getJSON=function(e){return n(e,!0)},c.remove=function(e,n){t(e,"",f(n,{expires:-1}))},c.defaults={},c.withConverter=e,c}(function(){})});
|
||||
1
cookbook/static/js/js.cookie.min.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["js.cookie.js"],"names":["factory","registeredInModuleLoader","define","amd","exports","module","OldCookies","window","Cookies","api","noConflict","extend","i","result","arguments","length","attributes","key","decode","s","replace","decodeURIComponent","init","converter","set","value","document","path","defaults","expires","Date","toUTCString","JSON","stringify","test","e","write","encodeURIComponent","String","escape","stringifiedAttributes","attributeName","split","cookie","get","json","jar","cookies","parts","slice","join","charAt","name","read","parse","getJSON","remove","withConverter"],"mappings":"CAOE,SAAUA,GACX,IAAIC,EASJ,GARsB,mBAAXC,QAAyBA,OAAOC,MAC1CD,OAAOF,GACPC,GAA2B,GAEL,iBAAZG,UACVC,OAAOD,QAAUJ,IACjBC,GAA2B,IAEvBA,EAA0B,CAC9B,IAAIK,EAAaC,OAAOC,QACpBC,EAAMF,OAAOC,QAAUR,IAC3BS,EAAIC,WAAa,WAEhB,OADAH,OAAOC,QAAUF,EACVG,IAfT,CAkBC,WACD,SAASE,IAGR,IAFA,IAAIC,EAAI,EACJC,EAAS,GACND,EAAIE,UAAUC,OAAQH,IAAK,CACjC,IAAII,EAAaF,UAAWF,GAC5B,IAAK,IAAIK,KAAOD,EACfH,EAAOI,GAAOD,EAAWC,GAG3B,OAAOJ,EAGR,SAASK,EAAQC,GAChB,OAAOA,EAAEC,QAAQ,mBAAoBC,oBA0HtC,OAvHA,SAASC,EAAMC,GACd,SAASd,KAET,SAASe,EAAKP,EAAKQ,EAAOT,GACzB,GAAwB,oBAAbU,SAAX,CAQkC,iBAJlCV,EAAaL,EAAO,CACnBgB,KAAM,KACJlB,EAAImB,SAAUZ,IAEKa,UACrBb,EAAWa,QAAU,IAAIC,KAAkB,EAAb,IAAIA,KAAkC,MAArBd,EAAWa,UAI3Db,EAAWa,QAAUb,EAAWa,QAAUb,EAAWa,QAAQE,cAAgB,GAE7E,IACC,IAAIlB,EAASmB,KAAKC,UAAUR,GACxB,UAAUS,KAAKrB,KAClBY,EAAQZ,GAER,MAAOsB,IAETV,EAAQF,EAAUa,MACjBb,EAAUa,MAAMX,EAAOR,GACvBoB,mBAAmBC,OAAOb,IACxBL,QAAQ,4DAA6DC,oBAExEJ,EAAMoB,mBAAmBC,OAAOrB,IAC9BG,QAAQ,2BAA4BC,oBACpCD,QAAQ,UAAWmB,QAErB,IAAIC,EAAwB,GAC5B,IAAK,IAAIC,KAAiBzB,EACpBA,EAAWyB,KAGhBD,GAAyB,KAAOC,GACE,IAA9BzB,EAAWyB,KAWfD,GAAyB,IAAMxB,EAAWyB,GAAeC,MAAM,KAAK,KAGrE,OAAQhB,SAASiB,OAAS1B,EAAM,IAAMQ,EAAQe,GAG/C,SAASI,EAAK3B,EAAK4B,GAClB,GAAwB,oBAAbnB,SAAX,CAUA,IANA,IAAIoB,EAAM,GAGNC,EAAUrB,SAASiB,OAASjB,SAASiB,OAAOD,MAAM,MAAQ,GAC1D9B,EAAI,EAEDA,EAAImC,EAAQhC,OAAQH,IAAK,CAC/B,IAAIoC,EAAQD,EAAQnC,GAAG8B,MAAM,KACzBC,EAASK,EAAMC,MAAM,GAAGC,KAAK,KAE5BL,GAA6B,MAArBF,EAAOQ,OAAO,KAC1BR,EAASA,EAAOM,MAAM,GAAI,IAG3B,IACC,IAAIG,EAAOlC,EAAO8B,EAAM,IAIxB,GAHAL,GAAUpB,EAAU8B,MAAQ9B,GAAWoB,EAAQS,IAC9ClC,EAAOyB,GAEJE,EACH,IACCF,EAASX,KAAKsB,MAAMX,GACnB,MAAOR,IAKV,GAFAW,EAAIM,GAAQT,EAER1B,IAAQmC,EACX,MAEA,MAAOjB,KAGV,OAAOlB,EAAM6B,EAAI7B,GAAO6B,GAoBzB,OAjBArC,EAAIe,IAAMA,EACVf,EAAImC,IAAM,SAAU3B,GACnB,OAAO2B,EAAI3B,GAAK,IAEjBR,EAAI8C,QAAU,SAAUtC,GACvB,OAAO2B,EAAI3B,GAAK,IAEjBR,EAAI+C,OAAS,SAAUvC,EAAKD,GAC3BQ,EAAIP,EAAK,GAAIN,EAAOK,EAAY,CAC/Ba,SAAU,MAIZpB,EAAImB,SAAW,GAEfnB,EAAIgD,cAAgBnC,EAEbb,EAGDa,CAAK"}
|
||||
2
cookbook/static/js/moment-with-locales.min.js
vendored
Normal file
1
cookbook/static/js/pdf.min.js
vendored
137
cookbook/static/js/redoc.standalone.js
Normal file
1
cookbook/static/js/vue-draggable.min.js
vendored
Normal file
7
cookbook/static/js/vue-resource.js
Normal file
11965
cookbook/static/js/vue.js
Normal file
6
cookbook/static/js/vue.min.js
vendored
Normal file
2
cookbook/static/js/vuedraggable.umd.min.js
vendored
Normal file
1
cookbook/static/js/vuedraggable.umd.min.js.map
Normal file
11
cookbook/static/pdfjs/images/annotation-check.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40">
|
||||
<path
|
||||
d="M 1.5006714,23.536225 6.8925879,18.994244 14.585721,26.037937 34.019683,4.5410479 38.499329,9.2235032 14.585721,35.458952 z"
|
||||
id="path4"
|
||||
style="fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.25402856;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 415 B |
16
cookbook/static/pdfjs/images/annotation-comment.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="40"
|
||||
width="40"
|
||||
viewBox="0 0 40 40">
|
||||
<rect
|
||||
style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
width="33.76017"
|
||||
height="33.76017"
|
||||
x="3.119915"
|
||||
y="3.119915" />
|
||||
<path
|
||||
d="m 20.677967,8.54499 c -7.342801,0 -13.295293,4.954293 -13.295293,11.065751 0,2.088793 0.3647173,3.484376 1.575539,5.150563 L 6.0267418,31.45501 13.560595,29.011117 c 2.221262,1.387962 4.125932,1.665377 7.117372,1.665377 7.3428,0 13.295291,-4.954295 13.295291,-11.065753 0,-6.111458 -5.952491,-11.065751 -13.295291,-11.065751 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.93031836;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 883 B |
26
cookbook/static/pdfjs/images/annotation-help.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40">
|
||||
<g
|
||||
transform="translate(0,-60)"
|
||||
id="layer1">
|
||||
<rect
|
||||
width="36.460953"
|
||||
height="34.805603"
|
||||
x="1.7695236"
|
||||
y="62.597198"
|
||||
style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.30826771;stroke-opacity:1" />
|
||||
<g
|
||||
transform="matrix(0.88763677,0,0,0.88763677,2.2472646,8.9890584)">
|
||||
<path
|
||||
d="M 20,64.526342 C 11.454135,64.526342 4.5263421,71.454135 4.5263421,80 4.5263421,88.545865 11.454135,95.473658 20,95.473658 28.545865,95.473658 35.473658,88.545865 35.473658,80 35.473658,71.454135 28.545865,64.526342 20,64.526342 z m -0.408738,9.488564 c 3.527079,0 6.393832,2.84061 6.393832,6.335441 0,3.494831 -2.866753,6.335441 -6.393832,6.335441 -3.527079,0 -6.393832,-2.84061 -6.393832,-6.335441 0,-3.494831 2.866753,-6.335441 6.393832,-6.335441 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.02768445;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
d="m 7.2335209,71.819938 4.9702591,4.161823 c -1.679956,2.581606 -1.443939,6.069592 0.159325,8.677725 l -5.1263071,3.424463 c 0.67516,1.231452 3.0166401,3.547686 4.2331971,4.194757 l 3.907728,-4.567277 c 2.541952,1.45975 5.730694,1.392161 8.438683,-0.12614 l 3.469517,6.108336 c 1.129779,-0.44367 4.742234,-3.449633 5.416358,-5.003859 l -5.46204,-4.415541 c 1.44319,-2.424098 1.651175,-5.267515 0.557303,-7.748623 l 5.903195,-3.833951 C 33.14257,71.704996 30.616217,69.018606 29.02952,67.99296 l -4.118813,4.981678 C 22.411934,71.205099 18.900853,70.937534 16.041319,72.32916 l -3.595408,-5.322091 c -1.345962,0.579488 -4.1293881,2.921233 -5.2123901,4.812869 z m 8.1010311,3.426672 c 2.75284,-2.446266 6.769149,-2.144694 9.048998,0.420874 2.279848,2.56557 2.113919,6.596919 -0.638924,9.043185 -2.752841,2.446267 -6.775754,2.13726 -9.055604,-0.428308 -2.279851,-2.565568 -2.107313,-6.589485 0.64553,-9.035751 z"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
10
cookbook/static/pdfjs/images/annotation-insert.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64"
|
||||
viewBox="0 0 64 64">
|
||||
<path
|
||||
d="M 32.003143,1.4044602 57.432701,62.632577 6.5672991,62.627924 z"
|
||||
style="fill:#ffff00;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:1.00493038;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 408 B |
11
cookbook/static/pdfjs/images/annotation-key.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64"
|
||||
viewBox="0 0 64 64">
|
||||
<path
|
||||
d="M 25.470843,9.4933766 C 25.30219,12.141818 30.139101,14.445969 34.704831,13.529144 40.62635,12.541995 41.398833,7.3856498 35.97505,5.777863 31.400921,4.1549155 25.157674,6.5445892 25.470843,9.4933766 z M 4.5246282,17.652051 C 4.068249,11.832873 9.2742983,5.9270407 18.437379,3.0977088 29.751911,-0.87185184 45.495663,1.4008022 53.603953,7.1104009 c 9.275765,6.1889221 7.158128,16.2079421 -3.171076,21.5939521 -1.784316,1.635815 -6.380222,1.21421 -7.068351,3.186186 -1.04003,0.972427 -1.288046,2.050158 -1.232864,3.168203 1.015111,2.000108 -3.831548,1.633216 -3.270553,3.759574 0.589477,5.264544 -0.179276,10.53738 -0.362842,15.806257 -0.492006,2.184998 1.163456,4.574232 -0.734888,6.610642 -2.482919,2.325184 -7.30604,2.189143 -9.193497,-0.274767 -2.733688,-1.740626 -8.254447,-3.615254 -6.104247,-6.339626 3.468112,-1.708686 -2.116197,-3.449897 0.431242,-5.080274 5.058402,-1.39256 -2.393215,-2.304318 -0.146889,-4.334645 3.069198,-0.977415 2.056986,-2.518352 -0.219121,-3.540397 1.876567,-1.807151 1.484149,-4.868919 -2.565455,-5.942205 0.150866,-1.805474 2.905737,-4.136876 -1.679967,-5.20493 C 10.260902,27.882167 4.6872697,22.95045 4.5245945,17.652051 z"
|
||||
id="path604"
|
||||
style="fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.72665179;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
11
cookbook/static/pdfjs/images/annotation-newparagraph.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64"
|
||||
viewBox="0 0 64 64">
|
||||
<path
|
||||
d="M 32.003143,10.913072 57.432701,53.086929 6.567299,53.083723 z"
|
||||
id="path2985"
|
||||
style="fill:#ffff00;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:0.83403099;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 426 B |
7
cookbook/static/pdfjs/images/annotation-noicon.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40">
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 158 B |
42
cookbook/static/pdfjs/images/annotation-note.svg
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40">
|
||||
<rect
|
||||
width="36.075428"
|
||||
height="31.096582"
|
||||
x="1.962286"
|
||||
y="4.4517088"
|
||||
id="rect4"
|
||||
style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.23004246;stroke-opacity:1" />
|
||||
<rect
|
||||
width="27.96859"
|
||||
height="1.5012145"
|
||||
x="6.0157046"
|
||||
y="10.285"
|
||||
id="rect6"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
width="27.96859"
|
||||
height="0.85783684"
|
||||
x="6.0157056"
|
||||
y="23.21689"
|
||||
id="rect8"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
width="27.96859"
|
||||
height="0.85783684"
|
||||
x="5.8130345"
|
||||
y="28.964394"
|
||||
id="rect10"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
width="27.96859"
|
||||
height="0.85783684"
|
||||
x="6.0157046"
|
||||
y="17.426493"
|
||||
id="rect12"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
16
cookbook/static/pdfjs/images/annotation-paragraph.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40">
|
||||
<rect
|
||||
width="33.76017"
|
||||
height="33.76017"
|
||||
x="3.119915"
|
||||
y="3.119915"
|
||||
style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path
|
||||
d="m 17.692678,34.50206 0,-16.182224 c -1.930515,-0.103225 -3.455824,-0.730383 -4.57593,-1.881473 -1.12011,-1.151067 -1.680164,-2.619596 -1.680164,-4.405591 0,-1.992435 0.621995,-3.5796849 1.865988,-4.7617553 1.243989,-1.1820288 3.06352,-1.7730536 5.458598,-1.7730764 l 9.802246,0 0,2.6789711 -2.229895,0 0,26.3251486 -2.632515,0 0,-26.3251486 -3.45324,0 0,26.3251486 z"
|
||||
style="font-size:29.42051125px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.07795751;stroke-opacity:1;font-family:Arial;-inkscape-font-specification:Arial" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
cookbook/static/pdfjs/images/findbarButton-next.png
Normal file
|
After Width: | Height: | Size: 193 B |
BIN
cookbook/static/pdfjs/images/findbarButton-next@2x.png
Normal file
|
After Width: | Height: | Size: 296 B |
BIN
cookbook/static/pdfjs/images/findbarButton-previous.png
Normal file
|
After Width: | Height: | Size: 199 B |
BIN
cookbook/static/pdfjs/images/findbarButton-previous@2x.png
Normal file
|
After Width: | Height: | Size: 304 B |
BIN
cookbook/static/pdfjs/images/grab.cur
Normal file
|
After Width: | Height: | Size: 326 B |
BIN
cookbook/static/pdfjs/images/grabbing.cur
Normal file
|
After Width: | Height: | Size: 326 B |
BIN
cookbook/static/pdfjs/images/loading-icon.gif
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
cookbook/static/pdfjs/images/loading-small.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
cookbook/static/pdfjs/images/loading-small@2x.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 403 B |
|
After Width: | Height: | Size: 933 B |
|
After Width: | Height: | Size: 179 B |
|
After Width: | Height: | Size: 266 B |
BIN
cookbook/static/pdfjs/images/secondaryToolbarButton-handTool.png
Normal file
|
After Width: | Height: | Size: 301 B |
|
After Width: | Height: | Size: 583 B |
BIN
cookbook/static/pdfjs/images/secondaryToolbarButton-lastPage.png
Normal file
|
After Width: | Height: | Size: 175 B |
|
After Width: | Height: | Size: 276 B |
|
After Width: | Height: | Size: 360 B |
|
After Width: | Height: | Size: 731 B |
BIN
cookbook/static/pdfjs/images/secondaryToolbarButton-rotateCw.png
Normal file
|
After Width: | Height: | Size: 359 B |
|
After Width: | Height: | Size: 714 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 332 B |
|
After Width: | Height: | Size: 228 B |
|
After Width: | Height: | Size: 349 B |
|
After Width: | Height: | Size: 297 B |
|
After Width: | Height: | Size: 490 B |
|
After Width: | Height: | Size: 461 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 347 B |
|
After Width: | Height: | Size: 694 B |
|
After Width: | Height: | Size: 179 B |
|
After Width: | Height: | Size: 261 B |
|
After Width: | Height: | Size: 344 B |
|
After Width: | Height: | Size: 621 B |
BIN
cookbook/static/pdfjs/images/shadow.png
Normal file
|
After Width: | Height: | Size: 290 B |
BIN
cookbook/static/pdfjs/images/texture.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
cookbook/static/pdfjs/images/toolbarButton-bookmark.png
Normal file
|
After Width: | Height: | Size: 174 B |
BIN
cookbook/static/pdfjs/images/toolbarButton-bookmark@2x.png
Normal file
|
After Width: | Height: | Size: 260 B |
BIN
cookbook/static/pdfjs/images/toolbarButton-download.png
Normal file
|
After Width: | Height: | Size: 259 B |
BIN
cookbook/static/pdfjs/images/toolbarButton-download@2x.png
Normal file
|
After Width: | Height: | Size: 425 B |
BIN
cookbook/static/pdfjs/images/toolbarButton-menuArrows.png
Normal file
|
After Width: | Height: | Size: 107 B |
BIN
cookbook/static/pdfjs/images/toolbarButton-menuArrows@2x.png
Normal file
|
After Width: | Height: | Size: 152 B |
BIN
cookbook/static/pdfjs/images/toolbarButton-openFile.png
Normal file
|
After Width: | Height: | Size: 295 B |
BIN
cookbook/static/pdfjs/images/toolbarButton-openFile@2x.png
Normal file
|
After Width: | Height: | Size: 550 B |
BIN
cookbook/static/pdfjs/images/toolbarButton-pageDown.png
Normal file
|
After Width: | Height: | Size: 238 B |