This commit is contained in:
vabene1111 2021-01-13 15:21:51 +01:00
parent bf9b8a0230
commit 95aff5c998
15 changed files with 203 additions and 121 deletions

View File

@ -22,7 +22,7 @@ jobs:
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -r requirements.txt pip install -r requirements.txt
python3 manage.py collectstatic --noinput python3 manage.py collectstatic --noinput
python3 manage.py collectstatic_js_reverse --noinput python3 manage.py collectstatic_js_reverse
- name: Django Testing project - name: Django Testing project
run: | run: |
python3 manage.py test python3 manage.py test

View File

@ -0,0 +1,19 @@
# Generated by Django 3.1.5 on 2021-01-13 14:18
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0098_auto_20210113_1320'),
]
operations = [
migrations.AlterField(
model_name='cooklog',
name='created_at',
field=models.DateTimeField(default=django.utils.timezone.now),
),
]

View File

@ -7,6 +7,7 @@ from django.contrib import auth
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from django.core.validators import MinLengthValidator from django.core.validators import MinLengthValidator
from django.db import models from django.db import models
from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django_random_queryset import RandomManager from django_random_queryset import RandomManager
@ -443,7 +444,7 @@ class InviteLink(models.Model):
class CookLog(models.Model): class CookLog(models.Model):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE) recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
created_by = models.ForeignKey(User, on_delete=models.CASCADE) created_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(default=timezone.now)
rating = models.IntegerField(null=True) rating = models.IntegerField(null=True)
servings = models.IntegerField(default=0) servings = models.IntegerField(default=0)

View File

@ -314,9 +314,14 @@ class ShareLinkSerializer(serializers.ModelSerializer):
class CookLogSerializer(serializers.ModelSerializer): class CookLogSerializer(serializers.ModelSerializer):
def create(self, validated_data): # TODO make mixin
validated_data['created_by'] = self.context['request'].user
return super().create(validated_data)
class Meta: class Meta:
model = CookLog model = CookLog
fields = '__all__' fields = '__all__'
read_only_fields = ('id', 'created_by')
class ViewLogSerializer(serializers.ModelSerializer): class ViewLogSerializer(serializers.ModelSerializer):

View File

@ -22,7 +22,6 @@ router.register(r'sync-log', api.SyncLogViewSet)
router.register(r'keyword', api.KeywordViewSet) router.register(r'keyword', api.KeywordViewSet)
router.register(r'unit', api.UnitViewSet) router.register(r'unit', api.UnitViewSet)
router.register(r'food', api.FoodViewSet) router.register(r'food', api.FoodViewSet)
router.register(r'step', api.StepViewSet) router.register(r'step', api.StepViewSet)
router.register(r'recipe', api.RecipeViewSet) router.register(r'recipe', api.RecipeViewSet)
router.register(r'ingredient', api.IngredientViewSet) router.register(r'ingredient', api.IngredientViewSet)
@ -32,6 +31,7 @@ router.register(r'shopping-list', api.ShoppingListViewSet)
router.register(r'shopping-list-entry', api.ShoppingListEntryViewSet) router.register(r'shopping-list-entry', api.ShoppingListEntryViewSet)
router.register(r'shopping-list-recipe', api.ShoppingListRecipeViewSet) router.register(r'shopping-list-recipe', api.ShoppingListRecipeViewSet)
router.register(r'view-log', api.ViewLogViewSet) router.register(r'view-log', api.ViewLogViewSet)
router.register(r'cook-log', api.CookLogViewSet)
urlpatterns = [ urlpatterns = [
path('', views.index, name='index'), path('', views.index, name='index'),
@ -47,61 +47,29 @@ urlpatterns = [
path('settings/', views.user_settings, name='view_settings'), path('settings/', views.user_settings, name='view_settings'),
path('history/', views.history, name='view_history'), path('history/', views.history, name='view_history'),
path('offline/', views.offline, name='view_offline'), path('offline/', views.offline, name='view_offline'),
path( path('service-worker.js', (TemplateView.as_view(template_name="service-worker.js", content_type='application/javascript', )), name='service_worker'),
'service-worker.js', (
TemplateView.as_view(
template_name="service-worker.js",
content_type='application/javascript',
)
),
name='service_worker'
),
path('test/<int:pk>', views.test, name='view_test'), path('test/<int:pk>', views.test, name='view_test'),
path('import/', import_export.import_recipe, name='view_import'), path('import/', import_export.import_recipe, name='view_import'),
path('export/', import_export.export_recipe, name='view_export'), path('export/', import_export.export_recipe, name='view_export'),
path('view/recipe/<int:pk>', views.recipe_view, name='view_recipe'), path('view/recipe/<int:pk>', views.recipe_view, name='view_recipe'),
path( path('view/recipe/<int:pk>/<slug:share>', views.recipe_view, name='view_recipe'),
'view/recipe/<int:pk>/<slug:share>',
views.recipe_view,
name='view_recipe'
),
path( path('new/recipe-import/<int:import_id>/', new.create_new_external_recipe, name='new_recipe_import'),
'new/recipe-import/<int:import_id>/',
new.create_new_external_recipe,
name='new_recipe_import'
),
path('new/share-link/<int:pk>/', new.share_link, name='new_share_link'), path('new/share-link/<int:pk>/', new.share_link, name='new_share_link'),
path('edit/recipe/<int:pk>/', edit.switch_recipe, name='edit_recipe'), path('edit/recipe/<int:pk>/', edit.switch_recipe, name='edit_recipe'),
# for internal use only # for internal use only
path( path('edit/recipe/internal/<int:pk>/', edit.internal_recipe_update, name='edit_internal_recipe'),
'edit/recipe/internal/<int:pk>/', path('edit/recipe/external/<int:pk>/', edit.ExternalRecipeUpdate.as_view(), name='edit_external_recipe'),
edit.internal_recipe_update, path('edit/recipe/convert/<int:pk>/', edit.convert_recipe, name='edit_convert_recipe'),
name='edit_internal_recipe'
),
path(
'edit/recipe/external/<int:pk>/',
edit.ExternalRecipeUpdate.as_view(),
name='edit_external_recipe'
),
path(
'edit/recipe/convert/<int:pk>/',
edit.convert_recipe,
name='edit_convert_recipe'
),
path('edit/storage/<int:pk>/', edit.edit_storage, name='edit_storage'), path('edit/storage/<int:pk>/', edit.edit_storage, name='edit_storage'),
path('edit/ingredient/', edit.edit_ingredients, name='edit_food'), path('edit/ingredient/', edit.edit_ingredients, name='edit_food'),
path( path('delete/recipe-source/<int:pk>/', delete.delete_recipe_source, name='delete_recipe_source'),
'delete/recipe-source/<int:pk>/',
delete.delete_recipe_source,
name='delete_recipe_source'
),
# TODO move to generic "new" view # TODO move to generic "new" view
path('data/sync', data.sync, name='data_sync'), path('data/sync', data.sync, name='data_sync'),
@ -111,51 +79,25 @@ urlpatterns = [
path('data/statistics', data.statistics, name='data_stats'), path('data/statistics', data.statistics, name='data_stats'),
path('data/import/url', data.import_url, name='data_import_url'), path('data/import/url', data.import_url, name='data_import_url'),
path( path('api/get_external_file_link/<int:recipe_id>/', api.get_external_file_link, name='api_get_external_file_link'),
'api/get_external_file_link/<int:recipe_id>/', path('api/get_recipe_file/<int:recipe_id>/', api.get_recipe_file, name='api_get_recipe_file'),
api.get_external_file_link,
name='api_get_external_file_link'
),
path(
'api/get_recipe_file/<int:recipe_id>/',
api.get_recipe_file,
name='api_get_recipe_file'
),
path('api/sync_all/', api.sync_all, name='api_sync'), path('api/sync_all/', api.sync_all, name='api_sync'),
path( path('api/log_cooking/<int:recipe_id>/', api.log_cooking, name='api_log_cooking'),
'api/log_cooking/<int:recipe_id>/', path('api/plan-ical/<slug:from_date>/<slug:to_date>/', api.get_plan_ical, name='api_get_plan_ical'),
api.log_cooking, path('api/recipe-from-url/', api.recipe_from_url, name='api_recipe_from_url'),
name='api_log_cooking'
),
path(
'api/plan-ical/<slug:from_date>/<slug:to_date>/',
api.get_plan_ical,
name='api_get_plan_ical'
),
path(
'api/recipe-from-url/', api.recipe_from_url, name='api_recipe_from_url'
),
path('api/backup/', api.get_backup, name='api_backup'), path('api/backup/', api.get_backup, name='api_backup'),
path( path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),
'dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'
),
path('dal/food/', dal.IngredientsAutocomplete.as_view(), name='dal_food'), path('dal/food/', dal.IngredientsAutocomplete.as_view(), name='dal_food'),
path('dal/unit/', dal.UnitAutocomplete.as_view(), name='dal_unit'), path('dal/unit/', dal.UnitAutocomplete.as_view(), name='dal_unit'),
path('docs/markdown/', views.markdown_info, name='docs_markdown'), path('docs/markdown/', views.markdown_info, name='docs_markdown'),
path('docs/api/', views.api_info, name='docs_api'), path('docs/api/', views.api_info, name='docs_api'),
path('openapi', get_schema_view( path('openapi', get_schema_view(title="Django Recipes", version=VERSION_NUMBER), name='openapi-schema'),
title="Django Recipes",
version=VERSION_NUMBER
), name='openapi-schema'),
path('api/', include((router.urls, 'api'))), path('api/', include((router.urls, 'api'))),
path( path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
'api-auth/',
include('rest_framework.urls', namespace='rest_framework')
),
] ]

View File

@ -49,7 +49,7 @@ from cookbook.serializer import (FoodSerializer, IngredientSerializer,
StorageSerializer, SyncLogSerializer, StorageSerializer, SyncLogSerializer,
SyncSerializer, UnitSerializer, SyncSerializer, UnitSerializer,
UserNameSerializer, UserPreferenceSerializer, UserNameSerializer, UserPreferenceSerializer,
ViewLogSerializer) ViewLogSerializer, CookLogSerializer)
class UserNameViewSet(viewsets.ReadOnlyModelViewSet): class UserNameViewSet(viewsets.ReadOnlyModelViewSet):
@ -329,6 +329,15 @@ class ViewLogViewSet(viewsets.ModelViewSet):
return queryset return queryset
class CookLogViewSet(viewsets.ModelViewSet):
queryset = CookLog.objects.all()
serializer_class = CookLogSerializer
permission_classes = [CustomIsOwner]
def get_queryset(self):
queryset = ViewLog.objects.filter(created_by=self.request.user).all()[:5]
return queryset
# -------------- non django rest api views -------------------- # -------------- non django rest api views --------------------
def get_recipe_provider(recipe): def get_recipe_provider(recipe):

View File

@ -11,6 +11,7 @@
"axios": "^0.21.1", "axios": "^0.21.1",
"bootstrap-vue": "^2.21.2", "bootstrap-vue": "^2.21.2",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"moment": "^2.29.1",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-template-compiler": "^2.6.12", "vue-template-compiler": "^2.6.12",
"vuex": "^3.6.0" "vuex": "^3.6.0"

View File

@ -1,5 +1,6 @@
<template> <template>
<div id="app" v-if="!loading"> <div id="app" v-if="!loading">
<div class="row"> <div class="row">
<div class="col-12" style="text-align: center"> <div class="col-12" style="text-align: center">
<h3>{{ recipe.name }}</h3> <h3>{{ recipe.name }}</h3>
@ -179,6 +180,9 @@ export default {
this.loadRecipe(this.recipe_id) this.loadRecipe(this.recipe_id)
}, },
methods: { methods: {
openCookLogModal: function () {
this.$bvModal.show('id_modal_cook_log')
},
loadRecipe: function (recipe_id) { loadRecipe: function (recipe_id) {
apiLoadRecipe(recipe_id).then(recipe => { apiLoadRecipe(recipe_id).then(recipe => {
this.recipe = recipe this.recipe = recipe

View File

@ -0,0 +1,68 @@
<template>
<div>
<b-modal class="modal" id="id_modal_cook_log" :title="_('Log Recipe Cooking')" :ok-title="_('Save')"
:cancel-title="_('Close')" @ok="logCook()">
<p>{{ _('All fields are optional and can be left empty.') }}</p>
<form>
<label for="id_log_servings">{{ _('Servings') }}</label>
<input class="form-control" type="number" id="id_log_servings" v-model="logObject.servings">
<label style="margin-top: 2vh" for="id_log_rating">{{ _('Rating') }} - <span
id="id_rating_show">{{ logObject.rating }}/5</span></label>
<input type="range" class="custom-range" min="0" max="5" id="id_log_rating" name="log_rating"
value="0" v-model="logObject.rating">
<label for="id_date" style="margin-top: 2vh">{{ _('Date') }}</label>
<input type="datetime-local" id="id_date" class="form-control" v-model="logObject.created_at">
</form>
</b-modal>
</div>
</template>
<script>
import {GettextMixin} from "@/utils/utils";
import moment from 'moment'
Vue.prototype.moment = moment
import Vue from "vue";
import {BootstrapVue} from "bootstrap-vue";
import {apiLogCooking} from "@/utils/api";
Vue.use(BootstrapVue)
export default {
name: 'CookLog',
mixins: [
GettextMixin,
],
props: {
recipe: Object,
},
data() {
return {
logObject: {
recipe: this.recipe.id,
servings: 0,
rating: 0,
created_at: moment().format('yyyy-MM-DDTHH:MM')
}
}
},
methods: {
logCook: function () {
let obj = JSON.parse(JSON.stringify(this.logObject))
obj.created_at = moment(obj.created_at, 'yyyy-MM-DDTHH:MM').format('yyyy-MM-DD HH:MM')
console.log('updating: ', obj)
apiLogCooking(this.logObject)
},
}
}
</script>

View File

@ -12,7 +12,7 @@
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<i class="fas fa-fire fa-fw"></i> {{ _('Calories') }} <i class="fas fa-fire fa-fw text-primary"></i> {{ _('Calories') }}
</div> </div>
<div class="col-6"> <div class="col-6">
{{ calculateAmount(recipe.nutrition.calories) }} kcal {{ calculateAmount(recipe.nutrition.calories) }} kcal
@ -21,7 +21,7 @@
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<i class="fas fa-bread-slice fa-fw"></i> {{ _('Carbohydrates') }} <i class="fas fa-bread-slice fa-fw text-primary"></i> {{ _('Carbohydrates') }}
</div> </div>
<div class="col-6"> <div class="col-6">
@ -31,7 +31,7 @@
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<i class="fas fa-cheese fa-fw"></i> {{ _('Fats') }} <i class="fas fa-cheese fa-fw text-primary"></i> {{ _('Fats') }}
</div> </div>
<div class="col-6"> <div class="col-6">
{{ calculateAmount(recipe.nutrition.fats) }} g {{ calculateAmount(recipe.nutrition.fats) }} g
@ -40,7 +40,7 @@
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<i class="fas fa-drumstick-bite fa-fw"></i> {{ _('Proteins') }} <i class="fas fa-drumstick-bite fa-fw text-primary"></i> {{ _('Proteins') }}
</div> </div>
<div class="col-6"> <div class="col-6">
{{ calculateAmount(recipe.nutrition.proteins) }} g {{ calculateAmount(recipe.nutrition.proteins) }} g

View File

@ -1,53 +1,54 @@
<template> <template>
<div> <div>
<div class="dropdown"> <div class="dropdown">
<a class="btn shadow-none" href="#" role="button" id="dropdownMenuLink" <a class="btn shadow-none" href="#" role="button" id="dropdownMenuLink"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-ellipsis-v"></i> <i class="fas fa-ellipsis-v"></i>
</a> </a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuLink"> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuLink">
<a class="dropdown-item" :href="resolveDjangoUrl('edit_recipe', recipe.id)"><i <a class="dropdown-item" :href="resolveDjangoUrl('edit_recipe', recipe.id)"><i
class="fas fa-pencil-alt fa-fw"></i> {{ _('Edit') }}</a> class="fas fa-pencil-alt fa-fw"></i> {{ _('Edit') }}</a>
<button class="dropdown-item" onclick="$('#bookmarkModal').modal({'show':true})"> <button class="dropdown-item" onclick="$('#bookmarkModal').modal({'show':true})">
<i class="fas fa-bookmark fa-fw"></i> {{ _('Add to Book') }} <i class="fas fa-bookmark fa-fw"></i> {{ _('Add to Book') }}
</button> </button>
<a class="dropdown-item" :href="recipe.name" v-if="true"> <!--TODO implement --> <a class="dropdown-item" :href="recipe.name" v-if="true"> <!--TODO implement -->
<i class="fas fa-shopping-cart fa-fw"></i> {{ _('Add to Shopping') }} </a> <i class="fas fa-shopping-cart fa-fw"></i> {{ _('Add to Shopping') }} </a>
<a class="dropdown-item" :href="resolveDjangoUrl('new_meal_plan', recipe.id)"><i <a class="dropdown-item" :href="resolveDjangoUrl('new_meal_plan', recipe.id)"><i
class="fas fa-calendar fa-fw"></i> {{ _('Add to Plan') }}</a> class="fas fa-calendar fa-fw"></i> {{ _('Add to Plan') }}</a>
<!--
<button class="dropdown-item" :onclick="openCookLogModal(recipe.id)"><i
class="fas fa-clipboard-list fa-fw"></i> {{ _('Log Cooking') }}
</button>
-->
<button class="dropdown-item" onclick="window.print()"><i
class="fas fa-print fa-fw"></i> {{ _('Print') }}
</button>
<a class="dropdown-item" :href="resolveDjangoUrl('view_export', recipe.id)" target="_blank" <button class="dropdown-item" @click="$bvModal.show('id_modal_cook_log')"><i
rel="noopener noreferrer"><i class="fas fa-file-export fa-fw"></i> {{ _('Export') }}</a> class="fas fa-clipboard-list fa-fw"></i> {{ _('Log Cooking') }}
</button>
<a class="dropdown-item" :href="resolveDjangoUrl('new_share_link', recipe.id)" target="_blank" <button class="dropdown-item" onclick="window.print()"><i
rel="noopener noreferrer" v-if="recipe.internal"><i class="fas fa-share-alt fa-fw"></i> {{ class="fas fa-print fa-fw"></i> {{ _('Print') }}
_('Share') </button>
}}</a>
</div>
<a class="dropdown-item" :href="resolveDjangoUrl('view_export', recipe.id)" target="_blank"
rel="noopener noreferrer"><i class="fas fa-file-export fa-fw"></i> {{ _('Export') }}</a>
<a class="dropdown-item" :href="resolveDjangoUrl('new_share_link', recipe.id)" target="_blank"
rel="noopener noreferrer" v-if="recipe.internal"><i class="fas fa-share-alt fa-fw"></i> {{
_('Share')
}}</a>
</div>
</div> </div>
<cook-log :recipe="recipe"></cook-log>
</div> </div>
</template> </template>
<script> <script>
import {GettextMixin, ResolveUrlMixin} from "@/utils/utils"; import {GettextMixin, ResolveUrlMixin} from "@/utils/utils";
import CookLog from "@/components/CookLog";
export default { export default {
name: 'RecipeContextMenu', name: 'RecipeContextMenu',
@ -55,6 +56,9 @@ export default {
ResolveUrlMixin, ResolveUrlMixin,
GettextMixin GettextMixin
], ],
components: {
CookLog
},
props: { props: {
recipe: Object, recipe: Object,
} }

View File

@ -1,12 +1,37 @@
import axios from "axios"; import axios from "axios";
import {makeToast} from "@/utils/utils"; import {djangoGettext as _, makeToast} from "@/utils/utils";
import {resolveDjangoUrl} from "@/utils/utils"; import {resolveDjangoUrl} from "@/utils/utils";
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
export function apiLoadRecipe(recipe_id) { export function apiLoadRecipe(recipe_id) {
return axios.get(resolveDjangoUrl('api:recipe-detail', recipe_id)).then((response) => { return axios.get(resolveDjangoUrl('api:recipe-detail', recipe_id)).then((response) => {
return response.data return response.data
}).catch((err) => { }).catch((err) => {
console.log(err) console.log(err.response)
makeToast('Error', 'There was an error loading a resource!', 'danger') makeToast('Error', 'There was an error loading a resource!', 'danger')
}) })
} }
export function apiLogCooking(cook_log) {
return axios.post(resolveDjangoUrl('api:cooklog-list',), cook_log).then((response) => {
console.log(response)
makeToast('Saved', 'Cook Log entry saved!', 'success')
}).catch((err) => {
handleError(err, 'There was an error creating a resource!', 'danger')
})
}
function handleError(error, message) {
if ('response' in error) {
console.log(error.response)
let title = (('statusText' in error.response) ? error.response.statusText : _('Error'))
message += '\n\n' + JSON.stringify(error.response.data);
makeToast(title, message, 'danger')
} else {
makeToast('Error', message, 'danger')
console.log(error)
}
}

View File

@ -59,8 +59,12 @@ export const ResolveUrlMixin = {
} }
} }
export function resolveDjangoUrl(url, params) { export function resolveDjangoUrl(url, params=null) {
return window.Urls[url](params) if (params !== null) {
return window.Urls[url](params)
} else {
return window.Urls[url]()
}
} }
/* /*

View File

@ -1 +1 @@
{"status":"compiling","publicPath":"http://localhost:8080/"} {"status":"done","publicPath":"http://localhost:8080/","chunks":{"chunk-vendors":[{"name":"js/chunk-vendors.js","publicPath":"http://localhost:8080/js/chunk-vendors.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\js\\chunk-vendors.js"}],"recipe_view":[{"name":"js/recipe_view.js","publicPath":"http://localhost:8080/js/recipe_view.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\js\\recipe_view.js"}]},"error":"ModuleError","message":"Module Error (from ./node_modules/eslint-loader/index.js):\n\nF:\\Developement\\Django\\recipes\\vue\\src\\utils\\utils.js\n 63:8 error Parsing error: Unexpected token, expected \"(\"\n\n 61 | \n 62 | export function resolveDjangoUrl(url, params=null) {\n> 63 | if params !== null\n | ^\n 64 | return window.Urls[url](params)\n 65 | else:\n 66 | return window.Urls[url]\n\n✖ 1 problem (1 error, 0 warnings)\n"}

View File

@ -3179,11 +3179,6 @@ dir-glob@^2.0.0, dir-glob@^2.2.2:
dependencies: dependencies:
path-type "^3.0.0" path-type "^3.0.0"
django-js-reverse@^0.10.1-a.0:
version "0.10.1-a.0"
resolved "https://registry.yarnpkg.com/django-js-reverse/-/django-js-reverse-0.10.1-a.0.tgz#ff915561930d3292c7e33c8b4d6f10ae8cecc8a0"
integrity sha512-t0lHG4LMjkpXBtRL6oQwBX1S5aygZgddQ4IEhTX9icRvRcGv2sx5zyrf1sKj6e8xtO7boibzWI94bSphoG6llw==
dns-equal@^1.0.0: dns-equal@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.npm.taobao.org/dns-equal/download/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" resolved "https://registry.npm.taobao.org/dns-equal/download/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
@ -5549,6 +5544,11 @@ mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1:
dependencies: dependencies:
minimist "^1.2.5" minimist "^1.2.5"
moment@^2.29.1:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
move-concurrently@^1.0.1: move-concurrently@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.npm.taobao.org/move-concurrently/download/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" resolved "https://registry.npm.taobao.org/move-concurrently/download/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"