new settings page finished
This commit is contained in:
parent
64688ca5e1
commit
6605b87c5c
@ -10,4 +10,5 @@ def context_settings(request):
|
||||
'TERMS_URL': settings.TERMS_URL,
|
||||
'PRIVACY_URL': settings.PRIVACY_URL,
|
||||
'IMPRINT_URL': settings.IMPRINT_URL,
|
||||
'SHOPPING_MIN_AUTOSYNC_INTERVAL': settings.SHOPPING_MIN_AUTOSYNC_INTERVAL,
|
||||
}
|
||||
|
6
cookbook/static/themes/tandoor.min.css
vendored
6
cookbook/static/themes/tandoor.min.css
vendored
@ -6113,9 +6113,11 @@ a.close.disabled {
|
||||
.align-text-top {
|
||||
vertical-align: text-top !important
|
||||
}
|
||||
|
||||
/*!
|
||||
* technically the wrong color but not used anywhere besides nav and this way changing nav color is supported
|
||||
*/
|
||||
.bg-primary {
|
||||
background-color: #b98766 !important
|
||||
background-color: rgb(221, 191, 134) !important;
|
||||
}
|
||||
|
||||
a.bg-primary:focus, a.bg-primary:hover, button.bg-primary:focus, button.bg-primary:hover {
|
||||
|
@ -69,7 +69,7 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-{% nav_color request %} bg-header"
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-{% nav_color request %}"
|
||||
id="id_main_nav"
|
||||
style="{% sticky_nav request %}">
|
||||
|
||||
@ -408,6 +408,7 @@
|
||||
localStorage.setItem('BASE_PATH', "{% base_path request 'base' %}")
|
||||
localStorage.setItem('STATIC_URL', "{% base_path request 'static_base' %}")
|
||||
localStorage.setItem('DEBUG', "{% is_debug %}")
|
||||
localStorage.setItem('USER_ID', "{{request.user.pk}}")
|
||||
window.addEventListener("load", () => {
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.register("{% url 'service_worker' %}", {scope: "{% base_path request 'base' %}" + '/'}).then(function (reg) {
|
||||
|
@ -6,7 +6,7 @@
|
||||
{% block title %}{% trans 'Settings' %}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
{{ preference_form.media }}
|
||||
{{ search_form.media }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@ -15,209 +15,53 @@
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'view_settings' %}">{% trans 'Settings' %}</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{% trans 'Search' %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist" style="margin-bottom: 2vh">
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link {% if active_tab == 'account' %} active {% endif %}" id="account-tab" data-toggle="tab"
|
||||
href="#account" role="tab"
|
||||
aria-controls="account"
|
||||
aria-selected="{% if active_tab == 'account' %} 'true' {% else %} 'false' {% endif %}">
|
||||
{% trans 'Account' %}</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link {% if active_tab == 'preferences' %} active {% endif %}" id="preferences-tab"
|
||||
data-toggle="tab" href="#preferences" role="tab"
|
||||
aria-controls="preferences"
|
||||
aria-selected="{% if active_tab == 'preferences' %} 'true' {% else %} 'false' {% endif %}">
|
||||
{% trans 'Preferences' %}</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link {% if active_tab == 'api' %} active {% endif %}" id="api-tab" data-toggle="tab"
|
||||
href="#api" role="tab"
|
||||
aria-controls="api"
|
||||
aria-selected="{% if active_tab == 'api' %} 'true' {% else %} 'false' {% endif %}">
|
||||
{% trans 'API-Settings' %}</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link {% if active_tab == 'search' %} active {% endif %}" id="search-tab" data-toggle="tab"
|
||||
href="#search" role="tab"
|
||||
aria-controls="search"
|
||||
aria-selected="{% if active_tab == 'search' %} 'true' {% else %} 'false' {% endif %}">
|
||||
{% trans 'Search-Settings' %}</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link {% if active_tab == 'shopping' %} active {% endif %}" id="shopping-tab" data-toggle="tab"
|
||||
href="#shopping" role="tab"
|
||||
aria-controls="search"
|
||||
aria-selected="{% if active_tab == 'shopping' %} 'true' {% else %} 'false' {% endif %}">
|
||||
{% trans 'Shopping-Settings' %}</a>
|
||||
</li>
|
||||
<div class="tab-pane {% if active_tab == 'search' %} active {% endif %}" id="search" role="tabpanel"
|
||||
aria-labelledby="search-tab">
|
||||
<h4>{% trans 'Search Settings' %}</h4>
|
||||
{% trans 'There are many options to configure the search depending on your personal preferences.' %}
|
||||
{% trans 'Usually you do <b>not need</b> to configure any of them and can just stick with either the default or one of the following presets.' %}
|
||||
{% trans 'If you do want to configure the search you can read about the different options <a href="/docs/search/">here</a>.' %}
|
||||
|
||||
</ul>
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane {% if active_tab == 'account' %} active {% endif %}" id="account" role="tabpanel"
|
||||
aria-labelledby="account-tab">
|
||||
<h4>{% trans 'Name Settings' %}</h4>
|
||||
<form action="." method="post">
|
||||
{% csrf_token %}
|
||||
{{ user_name_form|crispy }}
|
||||
<button class="btn btn-success" type="submit" name="user_name_form"><i
|
||||
class="fas fa-save"></i> {% trans 'Save' %}</button>
|
||||
</form>
|
||||
|
||||
<h4>{% trans 'Account Settings' %}</h4>
|
||||
|
||||
<a href="{% url 'account_email' %}" class="btn btn-primary">{% trans 'Emails' %}</a>
|
||||
<a href="{% url 'account_change_password' %}" class="btn btn-primary">{% trans 'Password' %}</a>
|
||||
|
||||
<a href="{% url 'socialaccount_connections' %}" class="btn btn-primary">{% trans 'Social' %}</a>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="card-deck mt-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{% trans 'Fuzzy' %}</h5>
|
||||
<p class="card-text">{% trans 'Find what you need even if your search or the recipe contains typos. Might return more results than needed to make sure you find what you are looking for.' %}</p>
|
||||
<p class="card-text"><small class="text-muted">{% trans 'This is the default behavior' %}</small>
|
||||
</p>
|
||||
<button class="btn btn-primary card-link"
|
||||
onclick="applyPreset('fuzzy')">{% trans 'Apply' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{% trans 'Precise' %}</h5>
|
||||
<p class="card-text">{% trans 'Allows fine control over search results but might not return results if too many spelling mistakes are made.' %}</p>
|
||||
<p class="card-text"><small class="text-muted">{% trans 'Perfect for large Databases' %}</small></p>
|
||||
<button class="btn btn-primary card-link"
|
||||
onclick="applyPreset('precise')">{% trans 'Apply' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="tab-pane {% if active_tab == 'preferences' %} active {% endif %}" id="preferences" role="tabpanel"
|
||||
aria-labelledby="preferences-tab">
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<h4><i class="fas fa-language fa-fw"></i> {% trans 'Language' %}</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form action="{% url 'set_language' %}" method="post">{% csrf_token %}
|
||||
<input class="form-control" name="next" type="hidden" value="{{ redirect_to }}">
|
||||
<select name="language" class="form-control">
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% get_available_languages as LANGUAGES %}
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
{% for language in languages %}
|
||||
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %}
|
||||
selected{% endif %}>
|
||||
{{ language.name_local }} ({{ language.code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<br/>
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-save"></i> {% trans 'Save' %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<h4><i class="fas fa-palette fa-fw"></i> {% trans 'Style' %}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<form action="." method="post">
|
||||
{% csrf_token %}
|
||||
{{ preference_form|crispy }}
|
||||
<button class="btn btn-success" type="submit" name="preference_form"><i
|
||||
class="fas fa-save"></i> {% trans 'Save' %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="tab-pane {% if active_tab == 'api' %} active {% endif %}" id="api" role="tabpanel"
|
||||
aria-labelledby="api-tab">
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<h4><i class="fas fa-terminal fa-fw"></i> {% trans 'API Token' %}</h4>
|
||||
{% trans 'You can use both basic authentication and token based authentication to access the REST API.' %}
|
||||
<br/>
|
||||
<br/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<input class="form-control" value="{{ api_token }}" id="id_token">
|
||||
<div class="input-group-append">
|
||||
<button class="input-group-btn btn btn-primary" onclick="copyToken()"><i
|
||||
class="far fa-copy"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
{% trans 'Use the token as an Authorization header prefixed by the word token as shown in the following examples:' %}
|
||||
<br/>
|
||||
<code>Authorization: Bearer {{ api_token }}</code> {% trans 'or' %}<br/>
|
||||
<code>curl -X GET http://your.domain.com/api/recipes/ -H 'Authorization:
|
||||
Bearer {{ api_token }}'</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="tab-pane {% if active_tab == 'search' %} active {% endif %}" id="search" role="tabpanel"
|
||||
aria-labelledby="search-tab">
|
||||
<h4>{% trans 'Search Settings' %}</h4>
|
||||
{% trans 'There are many options to configure the search depending on your personal preferences.' %}
|
||||
{% trans 'Usually you do <b>not need</b> to configure any of them and can just stick with either the default or one of the following presets.' %}
|
||||
{% trans 'If you do want to configure the search you can read about the different options <a href="/docs/search/">here</a>.' %}
|
||||
|
||||
<div class="card-deck mt-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{% trans 'Fuzzy' %}</h5>
|
||||
<p class="card-text">{% trans 'Find what you need even if your search or the recipe contains typos. Might return more results than needed to make sure you find what you are looking for.' %}</p>
|
||||
<p class="card-text"><small class="text-muted">{% trans 'This is the default behavior' %}</small></p>
|
||||
<button class="btn btn-primary card-link" onclick="applyPreset('fuzzy')">{% trans 'Apply' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{% trans 'Precise' %}</h5>
|
||||
<p class="card-text">{% trans 'Allows fine control over search results but might not return results if too many spelling mistakes are made.' %}</p>
|
||||
<p class="card-text"><small class="text-muted">{% trans 'Perfect for large Databases' %}</small></p>
|
||||
<button class="btn btn-primary card-link" onclick="applyPreset('precise')">{% trans 'Apply' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
<form action="./#search" method="post" id="id_search_form">
|
||||
{% csrf_token %}
|
||||
{{ search_form|crispy }}
|
||||
<button class="btn btn-success" type="submit" name="search_form" id="search_form_button"><i
|
||||
class="fas fa-save"></i> {% trans 'Save' %}</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane {% if active_tab == 'shopping' %} active {% endif %}" id="shopping" role="tabpanel"
|
||||
aria-labelledby="shopping-tab">
|
||||
<h4>{% trans 'Shopping Settings' %}</h4>
|
||||
|
||||
<form action="./#shopping" method="post" id="id_shopping_form">
|
||||
{% csrf_token %}
|
||||
{{ shopping_form|crispy }}
|
||||
<button class="btn btn-success" type="submit" name="shopping_form" id="shopping_form_button"><i
|
||||
class="fas fa-save"></i> {% trans 'Save' %}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
<form action="./#search" method="post" id="id_search_form">
|
||||
{% csrf_token %}
|
||||
{{ search_form|crispy }}
|
||||
<button class="btn btn-success" type="submit" name="search_form" id="search_form_button"><i
|
||||
class="fas fa-save"></i> {% trans 'Save' %}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script type="application/javascript">
|
||||
$(function() {
|
||||
$(function () {
|
||||
$('#id_search-trigram_threshold').get(0).type = 'range';
|
||||
});
|
||||
|
||||
@ -225,44 +69,5 @@
|
||||
$('#id_search-preset').val(preset);
|
||||
$('#search_form_button').click();
|
||||
}
|
||||
|
||||
function copyToken() {
|
||||
let token = $('#id_token');
|
||||
token.select();
|
||||
document.execCommand("copy");
|
||||
}
|
||||
|
||||
// Javascript to enable link to tab
|
||||
var hash = location.hash.replace(/^#/, ''); // ^ means starting, meaning only match the first hash
|
||||
if (hash) {
|
||||
$('.nav-tabs a[href="#' + hash + '"]').tab('show');
|
||||
}
|
||||
|
||||
// Change hash for page-reload
|
||||
$('.nav-tabs a').on('shown.bs.tab', function(e) {
|
||||
window.location.hash = e.target.hash;
|
||||
})
|
||||
|
||||
{% comment %}
|
||||
// listen for events
|
||||
$(document).ready(function() {
|
||||
hideShow()
|
||||
// call hideShow when the user clicks on the mealplan_autoadd checkbox
|
||||
$("#id_shopping-mealplan_autoadd_shopping").click(function(event) {
|
||||
hideShow();
|
||||
});
|
||||
})
|
||||
|
||||
function hideShow() {
|
||||
if(document.getElementById('id_shopping-mealplan_autoadd_shopping').checked == true) {
|
||||
$('#div_id_shopping-mealplan_autoexclude_onhand').show();
|
||||
$('#div_id_shopping-mealplan_autoinclude_related').show();
|
||||
}
|
||||
else {
|
||||
$('#div_id_shopping-mealplan_autoexclude_onhand').hide();
|
||||
$('#div_id_shopping-mealplan_autoinclude_related').hide();
|
||||
}
|
||||
}
|
||||
{% endcomment %}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -19,6 +19,7 @@
|
||||
<script type="application/javascript">
|
||||
window.IMAGE_PLACEHOLDER = "{% static 'assets/recipe_no_image.svg' %}"
|
||||
window.CUSTOM_LOCALE = '{{ request.LANGUAGE_CODE }}'
|
||||
window.SHOPPING_MIN_AUTOSYNC_INTERVAL = {{ SHOPPING_MIN_AUTOSYNC_INTERVAL }}
|
||||
</script>
|
||||
|
||||
{% render_bundle 'shopping_list_view' %} {% endblock %}
|
||||
|
@ -29,6 +29,7 @@
|
||||
<script type="application/javascript">
|
||||
window.CUSTOM_LOCALE = '{{ request.LANGUAGE_CODE }}'
|
||||
window.USER_ID = {{ request.user.pk }}
|
||||
window.SHOPPING_MIN_AUTOSYNC_INTERVAL = {{ SHOPPING_MIN_AUTOSYNC_INTERVAL }}
|
||||
|
||||
<!--TODO build custom API endpoint for this -->
|
||||
{% get_available_languages as LANGUAGES %}
|
||||
|
@ -69,7 +69,7 @@ urlpatterns = [
|
||||
path('plan/', views.meal_plan, name='view_plan'),
|
||||
path('shopping/', lists.shopping_list, name='view_shopping'),
|
||||
path('settings/', views.user_settings, name='view_settings'),
|
||||
path('user-settings/', views.user_settings_new, name='view_user_settings'),
|
||||
path('settings-shopping/', views.shopping_settings, name='view_shopping_settings'),
|
||||
path('history/', views.history, name='view_history'),
|
||||
path('supermarket/', views.supermarket, name='view_supermarket'),
|
||||
path('ingredient-editor/', views.ingredient_editor, name='view_ingredient_editor'),
|
||||
|
@ -183,7 +183,11 @@ def view_profile(request, user_id):
|
||||
|
||||
|
||||
@group_required('guest')
|
||||
def user_settings_new(request):
|
||||
def user_settings(request):
|
||||
if request.space.demo:
|
||||
messages.add_message(request, messages.ERROR, _('This feature is not available in the demo version!'))
|
||||
return redirect('index')
|
||||
|
||||
return render(request, 'user_settings.html', {})
|
||||
|
||||
|
||||
@ -201,54 +205,16 @@ def ingredient_editor(request):
|
||||
|
||||
|
||||
@group_required('guest')
|
||||
def user_settings(request):
|
||||
def shopping_settings(request):
|
||||
if request.space.demo:
|
||||
messages.add_message(request, messages.ERROR, _('This feature is not available in the demo version!'))
|
||||
return redirect('index')
|
||||
|
||||
up = request.user.userpreference
|
||||
sp = request.user.searchpreference
|
||||
search_error = False
|
||||
active_tab = 'account'
|
||||
|
||||
user_name_form = UserNameForm(instance=request.user)
|
||||
|
||||
if request.method == "POST":
|
||||
if 'preference_form' in request.POST:
|
||||
active_tab = 'preferences'
|
||||
form = UserPreferenceForm(request.POST, prefix='preference', space=request.space)
|
||||
if form.is_valid():
|
||||
if not up:
|
||||
up = UserPreference(user=request.user)
|
||||
|
||||
up.theme = form.cleaned_data['theme']
|
||||
up.nav_color = form.cleaned_data['nav_color']
|
||||
up.default_unit = form.cleaned_data['default_unit']
|
||||
up.default_page = form.cleaned_data['default_page']
|
||||
up.plan_share.set(form.cleaned_data['plan_share'])
|
||||
up.ingredient_decimals = form.cleaned_data['ingredient_decimals'] # noqa: E501
|
||||
up.comments = form.cleaned_data['comments']
|
||||
up.use_fractions = form.cleaned_data['use_fractions']
|
||||
up.use_kj = form.cleaned_data['use_kj']
|
||||
up.sticky_navbar = form.cleaned_data['sticky_navbar']
|
||||
up.left_handed = form.cleaned_data['left_handed']
|
||||
|
||||
up.save()
|
||||
|
||||
elif 'user_name_form' in request.POST:
|
||||
user_name_form = UserNameForm(request.POST, prefix='name')
|
||||
if user_name_form.is_valid():
|
||||
request.user.first_name = user_name_form.cleaned_data['first_name']
|
||||
request.user.last_name = user_name_form.cleaned_data['last_name']
|
||||
request.user.save()
|
||||
|
||||
elif 'password_form' in request.POST:
|
||||
password_form = PasswordChangeForm(request.user, request.POST)
|
||||
if password_form.is_valid():
|
||||
user = password_form.save()
|
||||
update_session_auth_hash(request, user)
|
||||
|
||||
elif 'search_form' in request.POST:
|
||||
if 'search_form' in request.POST:
|
||||
active_tab = 'search'
|
||||
search_form = SearchPreferenceForm(request.POST, prefix='search')
|
||||
if search_form.is_valid():
|
||||
@ -297,39 +263,13 @@ def user_settings(request):
|
||||
sp.lookup = True
|
||||
sp.unaccent.set(SearchFields.objects.all())
|
||||
# full text on food is very slow, add search_vector field and index it (including Admin functions and postsave signal to rebuild index)
|
||||
sp.icontains.set([SearchFields.objects.get(name__in=['Name', 'Ingredients'])])
|
||||
sp.icontains.set([SearchFields.objects.get(name='Name')])
|
||||
sp.istartswith.set([SearchFields.objects.get(name='Name')])
|
||||
sp.trigram.clear()
|
||||
sp.fulltext.set(SearchFields.objects.filter(name__in=['Ingredients']))
|
||||
sp.trigram_threshold = 0.2
|
||||
|
||||
sp.save()
|
||||
elif 'shopping_form' in request.POST:
|
||||
shopping_form = ShoppingPreferenceForm(request.POST, prefix='shopping')
|
||||
if shopping_form.is_valid():
|
||||
if not up:
|
||||
up = UserPreference(user=request.user)
|
||||
|
||||
up.shopping_share.set(shopping_form.cleaned_data['shopping_share'])
|
||||
up.mealplan_autoadd_shopping = shopping_form.cleaned_data['mealplan_autoadd_shopping']
|
||||
up.mealplan_autoexclude_onhand = shopping_form.cleaned_data['mealplan_autoexclude_onhand']
|
||||
up.mealplan_autoinclude_related = shopping_form.cleaned_data['mealplan_autoinclude_related']
|
||||
up.shopping_auto_sync = shopping_form.cleaned_data['shopping_auto_sync']
|
||||
up.filter_to_supermarket = shopping_form.cleaned_data['filter_to_supermarket']
|
||||
up.default_delay = shopping_form.cleaned_data['default_delay']
|
||||
up.shopping_recent_days = shopping_form.cleaned_data['shopping_recent_days']
|
||||
up.shopping_add_onhand = shopping_form.cleaned_data['shopping_add_onhand']
|
||||
up.csv_delim = shopping_form.cleaned_data['csv_delim']
|
||||
up.csv_prefix = shopping_form.cleaned_data['csv_prefix']
|
||||
if up.shopping_auto_sync < settings.SHOPPING_MIN_AUTOSYNC_INTERVAL:
|
||||
up.shopping_auto_sync = settings.SHOPPING_MIN_AUTOSYNC_INTERVAL
|
||||
up.save()
|
||||
if up:
|
||||
preference_form = UserPreferenceForm(instance=up, space=request.space)
|
||||
shopping_form = ShoppingPreferenceForm(instance=up)
|
||||
else:
|
||||
preference_form = UserPreferenceForm(space=request.space)
|
||||
shopping_form = ShoppingPreferenceForm(space=request.space)
|
||||
|
||||
fields_searched = len(sp.icontains.all()) + len(sp.istartswith.all()) + len(sp.trigram.all()) + len(
|
||||
sp.fulltext.all())
|
||||
@ -338,9 +278,6 @@ def user_settings(request):
|
||||
elif not search_error:
|
||||
search_form = SearchPreferenceForm()
|
||||
|
||||
if (api_token := AccessToken.objects.filter(user=request.user).first()) is None:
|
||||
api_token = AccessToken.objects.create(user=request.user, token=f'tda_{str(uuid.uuid4()).replace("-","_")}', expires=(timezone.now() + timezone.timedelta(days=365*10)), scope='read write').token
|
||||
|
||||
# these fields require postgresql - just disable them if postgresql isn't available
|
||||
if not settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2',
|
||||
'django.db.backends.postgresql']:
|
||||
@ -350,12 +287,7 @@ def user_settings(request):
|
||||
search_form.fields['fulltext'].disabled = True
|
||||
|
||||
return render(request, 'settings.html', {
|
||||
'preference_form': preference_form,
|
||||
'user_name_form': user_name_form,
|
||||
'api_token': api_token,
|
||||
'search_form': search_form,
|
||||
'shopping_form': shopping_form,
|
||||
'active_tab': active_tab
|
||||
})
|
||||
|
||||
|
||||
|
@ -1,41 +1,77 @@
|
||||
<template>
|
||||
<div id="app" class="row">
|
||||
<div class="col-md-3 col-12">
|
||||
<b-nav vertical>
|
||||
<b-nav-item :active="visible_settings === 'cosmetic'" @click="visible_settings = 'cosmetic'"><i
|
||||
class="fas fa-fw fa-eye"></i> Cosmetic
|
||||
</b-nav-item>
|
||||
<b-nav-item :active="visible_settings === 'account'" @click="visible_settings = 'account'"><i
|
||||
class="fas fa-fw fa-user"></i> Account
|
||||
</b-nav-item>
|
||||
<b-nav-item :active="visible_settings === 'search'" @click="visible_settings = 'search'"><i
|
||||
class="fas fa-fw fa-search"></i> Search
|
||||
</b-nav-item>
|
||||
<b-nav-item :active="visible_settings === 'shopping'" @click="visible_settings = 'shopping'"><i
|
||||
class="fas fa-fw fa-shopping-cart"></i> Shopping
|
||||
</b-nav-item>
|
||||
<b-nav-item :active="visible_settings === 'meal_plan'" @click="visible_settings = 'meal_plan'"><i
|
||||
class="fas fa-fw fa-calendar"></i> Meal Plan
|
||||
</b-nav-item>
|
||||
<b-nav-item :active="visible_settings === 'api'" @click="visible_settings = 'api'"><i
|
||||
class="fas fa-fw fa-code"></i> API
|
||||
</b-nav-item>
|
||||
</b-nav>
|
||||
</div>
|
||||
<div class="col-md-9 col-12">
|
||||
<cosmetic-settings-component v-if="visible_settings === 'cosmetic'"
|
||||
:user_id="user_id"></cosmetic-settings-component>
|
||||
<account-settings-component v-if="visible_settings === 'account'"
|
||||
:user_id="user_id"></account-settings-component>
|
||||
<search-settings-component v-if="visible_settings === 'search'"
|
||||
:user_id="user_id"></search-settings-component>
|
||||
<shopping-settings-component v-if="visible_settings === 'shopping'"
|
||||
:user_id="user_id"></shopping-settings-component>
|
||||
<meal-plan-settings-component v-if="visible_settings === 'meal_plan'"
|
||||
:user_id="user_id"></meal-plan-settings-component>
|
||||
<a-p-i-settings-component v-if="visible_settings === 'api'" :user_id="user_id"></a-p-i-settings-component>
|
||||
|
||||
|
||||
<div id="app">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a :href="resolveDjangoUrl('view_settings')">{{
|
||||
$t('Settings')
|
||||
}}</a></li>
|
||||
<li class="breadcrumb-item" v-if="visible_settings === 'cosmetic'"
|
||||
@click="visible_settings = 'cosmetic'">{{ $t('Cosmetic') }}
|
||||
</li>
|
||||
<li class="breadcrumb-item" v-if="visible_settings === 'account'"
|
||||
@click="visible_settings = 'account'"> {{ $t('Account') }}
|
||||
</li>
|
||||
<li class="breadcrumb-item" v-if="visible_settings === 'search'"
|
||||
@click="visible_settings = 'search'">{{ $t('Search') }}
|
||||
</li>
|
||||
<li class="breadcrumb-item" v-if="visible_settings === 'shopping'"
|
||||
@click="visible_settings = 'shopping'">{{ $t('Shopping_list') }}
|
||||
</li>
|
||||
<li class="breadcrumb-item" v-if="visible_settings === 'meal_plan'"
|
||||
@click="visible_settings = 'meal_plan'">
|
||||
{{ $t('Meal_Plan') }}
|
||||
</li>
|
||||
<li class="breadcrumb-item" v-if="visible_settings === 'api'" @click="visible_settings = 'api'">
|
||||
{{ $t('API') }}
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-12">
|
||||
<b-nav vertical>
|
||||
<b-nav-item :active="visible_settings === 'cosmetic'" @click="visible_settings = 'cosmetic'"><i
|
||||
class="fas fa-fw fa-eye"></i> {{ $t('Cosmetic') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item :active="visible_settings === 'account'" @click="visible_settings = 'account'"><i
|
||||
class="fas fa-fw fa-user"></i> {{ $t('Account') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item :active="visible_settings === 'search'" @click="visible_settings = 'search'"><i
|
||||
class="fas fa-fw fa-search"></i> {{ $t('Search') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item :active="visible_settings === 'shopping'" @click="visible_settings = 'shopping'"><i
|
||||
class="fas fa-fw fa-shopping-cart"></i> {{ $t('Shopping_list') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item :active="visible_settings === 'meal_plan'" @click="visible_settings = 'meal_plan'"><i
|
||||
class="fas fa-fw fa-calendar"></i> {{ $t('Meal_Plan') }}
|
||||
</b-nav-item>
|
||||
<b-nav-item :active="visible_settings === 'api'" @click="visible_settings = 'api'"><i
|
||||
class="fas fa-fw fa-code"></i> {{ $t('API') }}
|
||||
</b-nav-item>
|
||||
</b-nav>
|
||||
</div>
|
||||
<div class="col-md-9 col-12">
|
||||
<cosmetic-settings-component v-if="visible_settings === 'cosmetic'"
|
||||
:user_id="user_id"></cosmetic-settings-component>
|
||||
<account-settings-component v-if="visible_settings === 'account'"
|
||||
:user_id="user_id"></account-settings-component>
|
||||
<search-settings-component v-if="visible_settings === 'search'"
|
||||
:user_id="user_id"></search-settings-component>
|
||||
<shopping-settings-component v-if="visible_settings === 'shopping'"
|
||||
:user_id="user_id"></shopping-settings-component>
|
||||
<meal-plan-settings-component v-if="visible_settings === 'meal_plan'"
|
||||
:user_id="user_id"></meal-plan-settings-component>
|
||||
<a-p-i-settings-component v-if="visible_settings === 'api'"
|
||||
:user_id="user_id"></a-p-i-settings-component>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -50,12 +86,13 @@ import SearchSettingsComponent from "@/components/Settings/SearchSettingsCompone
|
||||
import ShoppingSettingsComponent from "@/components/Settings/ShoppingSettingsComponent";
|
||||
import MealPlanSettingsComponent from "@/components/Settings/MealPlanSettingsComponent";
|
||||
import APISettingsComponent from "@/components/Settings/APISettingsComponent";
|
||||
import {ResolveUrlMixin} from "@/utils/utils";
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
export default {
|
||||
name: "ProfileView",
|
||||
mixins: [],
|
||||
mixins: [ResolveUrlMixin],
|
||||
components: {
|
||||
CosmeticSettingsComponent,
|
||||
AccountSettingsComponent,
|
||||
|
@ -33,7 +33,8 @@
|
||||
<template #title>
|
||||
<b-spinner v-if="loading" type="border" small class="d-inline-block"></b-spinner>
|
||||
<i v-if="!loading" class="fas fa-shopping-cart fa-fw d-inline-block d-md-none"></i>
|
||||
<span class="d-none d-md-inline-block">{{ $t('Shopping_list') + ` (${items.filter(x => x.checked === false).length})`}}</span>
|
||||
<span
|
||||
class="d-none d-md-inline-block">{{ $t('Shopping_list') + ` (${items.filter(x => x.checked === false).length})` }}</span>
|
||||
</template>
|
||||
<div class="container p-0 p-md-3" id="shoppinglist">
|
||||
<div class="row">
|
||||
@ -177,7 +178,7 @@
|
||||
<b-tab :title="$t('Recipes')">
|
||||
<template #title>
|
||||
<i class="fas fa-book fa-fw d-block d-md-none"></i>
|
||||
<span class="d-none d-md-block">{{ $t('Recipes') + ` (${Recipes.length})`}}</span>
|
||||
<span class="d-none d-md-block">{{ $t('Recipes') + ` (${Recipes.length})` }}</span>
|
||||
</template>
|
||||
<div class="container p-0">
|
||||
<div class="row">
|
||||
@ -234,7 +235,9 @@
|
||||
</thead>
|
||||
<tr v-for="r in Recipes" :key="r.list_recipe">
|
||||
<td>{{ r.recipe_mealplan.name }}</td>
|
||||
<td><a :href="resolveDjangoUrl('view_recipe', r.recipe_mealplan.recipe)">{{ r.recipe_mealplan.recipe_name }}</a></td>
|
||||
<td><a :href="resolveDjangoUrl('view_recipe', r.recipe_mealplan.recipe)">{{
|
||||
r.recipe_mealplan.recipe_name
|
||||
}}</a></td>
|
||||
<td class="block-inline">
|
||||
<b-form-input min="1" type="number" :debounce="300"
|
||||
:value="r.recipe_mealplan.servings"
|
||||
@ -258,7 +261,7 @@
|
||||
<b-tab>
|
||||
<template #title>
|
||||
<i class="fas fa-store-alt fa-fw d-block d-md-none"></i>
|
||||
<span class="d-none d-md-block">{{ $t('Supermarkets') + ` (${supermarkets.length})`}}</span>
|
||||
<span class="d-none d-md-block">{{ $t('Supermarkets') + ` (${supermarkets.length})` }}</span>
|
||||
</template>
|
||||
<div class="container p-0">
|
||||
<div class="row">
|
||||
@ -460,183 +463,7 @@
|
||||
</template>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-8">
|
||||
<b-card class="no-body">
|
||||
<div class="row">
|
||||
<div class="col col-md-6">{{ $t("mealplan_autoadd_shopping") }}</div>
|
||||
<div class="col col-md-6 text-right">
|
||||
<input type="checkbox" class="form-control settings-checkbox"
|
||||
v-model="settings.mealplan_autoadd_shopping" @change="saveSettings"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row sm mb-3">
|
||||
<div class="col">
|
||||
<em class="small text-muted">{{ $t("mealplan_autoadd_shopping_desc") }}</em>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="settings.mealplan_autoadd_shopping">
|
||||
<div class="row">
|
||||
<div class="col col-md-6">{{ $t("mealplan_autoexclude_onhand") }}</div>
|
||||
<div class="col col-md-6 text-right">
|
||||
<input type="checkbox" class="form-control settings-checkbox"
|
||||
v-model="settings.mealplan_autoexclude_onhand" @change="saveSettings"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row sm mb-3">
|
||||
<div class="col">
|
||||
<em class="small text-muted">{{ $t("mealplan_autoexclude_onhand_desc") }}</em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="settings.mealplan_autoadd_shopping">
|
||||
<div class="row">
|
||||
<div class="col col-md-6">{{ $t("mealplan_autoinclude_related") }}</div>
|
||||
<div class="col col-md-6 text-right">
|
||||
<input type="checkbox" class="form-control settings-checkbox"
|
||||
v-model="settings.mealplan_autoinclude_related" @change="saveSettings"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row sm mb-3">
|
||||
<div class="col">
|
||||
<em class="small text-muted">
|
||||
{{ $t("mealplan_autoinclude_related_desc") }}
|
||||
</em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-6">{{ $t("shopping_share") }}</div>
|
||||
<div class="col col-md-6 text-right">
|
||||
<generic-multiselect
|
||||
size="sm"
|
||||
@change="
|
||||
settings.shopping_share = $event.val
|
||||
saveSettings()
|
||||
"
|
||||
:model="Models.USER"
|
||||
:initial_selection="settings.shopping_share"
|
||||
label="display_name"
|
||||
:multiple="true"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
:placeholder="$t('User')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row sm mb-3">
|
||||
<div class="col">
|
||||
<em class="small text-muted">{{ $t("shopping_share_desc") }}</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-6">{{ $t("shopping_auto_sync") }}</div>
|
||||
<div class="col col-md-6 text-right">
|
||||
<input type="number" class="form-control" v-model="settings.shopping_auto_sync"
|
||||
@change="saveSettings"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row sm mb-3">
|
||||
<div class="col">
|
||||
<em class="small text-muted">
|
||||
{{ $t("shopping_auto_sync_desc") }}
|
||||
</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-6">{{ $t("shopping_add_onhand") }}</div>
|
||||
<div class="col col-md-6 text-right">
|
||||
<input type="checkbox" class="form-control settings-checkbox"
|
||||
v-model="settings.shopping_add_onhand" @change="saveSettings"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row sm mb-3">
|
||||
<div class="col">
|
||||
<em class="small text-muted">
|
||||
{{ $t("shopping_add_onhand_desc") }}
|
||||
</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-6">{{ $t("shopping_recent_days") }}</div>
|
||||
<div class="col col-md-6 text-right">
|
||||
<input type="number" class="form-control" v-model="settings.shopping_recent_days"
|
||||
@change="saveSettings"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row sm mb-3">
|
||||
<div class="col">
|
||||
<em class="small text-muted">
|
||||
{{ $t("shopping_recent_days_desc") }}
|
||||
</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-6">{{ $t("filter_to_supermarket") }}</div>
|
||||
<div class="col col-md-6 text-right">
|
||||
<input type="checkbox" class="form-control settings-checkbox"
|
||||
v-model="settings.filter_to_supermarket" @change="saveSettings"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row sm mb-3">
|
||||
<div class="col">
|
||||
<em class="small text-muted">
|
||||
{{ $t("filter_to_supermarket_desc") }}
|
||||
</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-6">{{ $t("default_delay") }}</div>
|
||||
<div class="col col-md-6 text-right">
|
||||
<input type="number" class="form-control" min="1" v-model="settings.default_delay"
|
||||
@change="saveSettings"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row sm mb-3">
|
||||
<div class="col">
|
||||
<em class="small text-muted">
|
||||
{{ $t("default_delay_desc") }}
|
||||
</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-6">{{ $t("csv_delim_label") }}</div>
|
||||
<div class="col col-md-6 text-right">
|
||||
<input class="form-control" v-model="settings.csv_delim" @change="saveSettings"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row sm mb-3">
|
||||
<div class="col">
|
||||
<em class="small text-muted">
|
||||
{{ $t("csv_delim_help") }}
|
||||
</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-6">{{ $t("csv_prefix_label") }}</div>
|
||||
<div class="col col-md-6 text-right">
|
||||
<input class="form-control" v-model="settings.csv_prefix" @change="saveSettings"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row sm mb-3">
|
||||
<div class="col">
|
||||
<em class="small text-muted">
|
||||
{{ $t("csv_prefix_help") }}
|
||||
</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-6">{{ $t("left_handed") }}</div>
|
||||
<div class="col col-md-6">
|
||||
<input type="checkbox" class="form-control settings-checkbox"
|
||||
v-model="settings.left_handed" @change="saveSettings"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row sm mb-3">
|
||||
<div class="col">
|
||||
<em class="small text-muted">
|
||||
{{ $t("left_handed_help") }}
|
||||
</em>
|
||||
</div>
|
||||
</div>
|
||||
</b-card>
|
||||
<shopping-settings-component @updated="settings = $event" :user_id="user_id"></shopping-settings-component>
|
||||
</div>
|
||||
</div>
|
||||
</b-tab>
|
||||
@ -783,6 +610,7 @@ import ShoppingModal from "@/components/Modals/ShoppingModal"
|
||||
|
||||
import {ApiMixin, getUserPreference, StandardToasts, makeToast, ResolveUrlMixin} from "@/utils/utils"
|
||||
import {ApiApiFactory} from "@/utils/openapi/api"
|
||||
import ShoppingSettingsComponent from "@/components/Settings/ShoppingSettingsComponent";
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
Vue.use(VueCookies)
|
||||
@ -790,7 +618,7 @@ let SETTINGS_COOKIE_NAME = "shopping_settings"
|
||||
|
||||
export default {
|
||||
name: "ShoppingListView",
|
||||
mixins: [ApiMixin,ResolveUrlMixin],
|
||||
mixins: [ApiMixin, ResolveUrlMixin],
|
||||
components: {
|
||||
ContextMenu,
|
||||
ContextMenuItem,
|
||||
@ -801,7 +629,8 @@ export default {
|
||||
DownloadCSV,
|
||||
CopyToClipboard,
|
||||
ShoppingModal,
|
||||
draggable
|
||||
draggable,
|
||||
ShoppingSettingsComponent
|
||||
},
|
||||
|
||||
data() {
|
||||
@ -837,6 +666,7 @@ export default {
|
||||
shopping_add_onhand: true,
|
||||
left_handed: false,
|
||||
},
|
||||
user_id: parseInt(localStorage.getItem('USER_ID')),
|
||||
editing_supermarket_categories: [],
|
||||
editing_supermarket: null,
|
||||
new_supermarket: {entrymode: false, value: undefined, editmode: undefined},
|
||||
@ -1315,16 +1145,6 @@ export default {
|
||||
|
||||
this.$refs.menu.open(e, value)
|
||||
},
|
||||
saveSettings: function () {
|
||||
let api = ApiApiFactory()
|
||||
api.partialUpdateUserPreference(this.settings.user, this.settings)
|
||||
.then((result) => {
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
|
||||
})
|
||||
.catch((err) => {
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
||||
})
|
||||
},
|
||||
saveThis: function (thisItem, toast = true) {
|
||||
let api = new ApiApiFactory()
|
||||
if (!thisItem?.id) {
|
||||
|
@ -1,20 +1,67 @@
|
||||
<template>
|
||||
<div>
|
||||
<code>Authorization: Bearer TOKEN</code> or<br/>
|
||||
|
||||
<b-alert show variant="danger">
|
||||
The API is made for developers to interact with the application.
|
||||
It is possible to break things using the API so be careful and create a backup first.
|
||||
The API definition can and will change in the future, make sure to read the changelog to spot changes early
|
||||
on.
|
||||
|
||||
<b-button-toolbar>
|
||||
<b-button-group class="mx-1">
|
||||
<a :href="resolveDjangoUrl('docs_api')" class="btn btn-info" target="_blank"
|
||||
rel="noreferrer nofollow">Docs</a>
|
||||
</b-button-group>
|
||||
<b-button-group class="mx-1">
|
||||
<a :href="resolveDjangoUrl('api:api-root')" class="btn btn-success" target="_blank"
|
||||
rel="noreferrer nofollow">Interactive API Browser</a>
|
||||
</b-button-group>
|
||||
</b-button-toolbar>
|
||||
|
||||
</b-alert>
|
||||
|
||||
Authentication works by proving the word <code>Bearer</code> followed by an API Token as a request Authorization
|
||||
header as shown below. <br/>
|
||||
<code>Authorization: Bearer TOKEN</code> -or-<br/>
|
||||
<code>curl -X GET http://your.domain.com/api/recipes/ -H 'Authorization:
|
||||
Bearer TOKEN'</code>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
You can have multiple tokens and each token can have its own scope. Currently there is <code>read</code>, <code>write</code>
|
||||
and <code>bookmarklet</code>.
|
||||
Read and write do what the name says, the bookmarklet scope is only used for the bookmarklet to limit access to
|
||||
it.
|
||||
|
||||
<b-alert show variant="info">Make sure to save your token after creation as they cannot be viewed afterwards.
|
||||
</b-alert>
|
||||
|
||||
<b-list-group class="mt-3">
|
||||
<b-list-group-item v-for="t in access_tokens" v-bind:key="t.id">{{ t.token }}<br/><small class="text-muted">{{
|
||||
t.scope
|
||||
}}
|
||||
- {{ t.expires }}</small>
|
||||
<b-button @click="active_token=t; generic_action = Actions.UPDATE;">Edit</b-button>
|
||||
<b-button @click="active_token=t; generic_action = Actions.DELETE;">Delete</b-button>
|
||||
<b-list-group-item v-for="t in access_tokens" v-bind:key="t.id">
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
{{ t.token }}<br/>
|
||||
<small>
|
||||
<span class="text-muted">Scope:</span> <code>{{ t.scope }}</code> <span class="text-muted">Expires:</span>
|
||||
{{ formatDate(t.expires) }}
|
||||
</small>
|
||||
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<b-button-group>
|
||||
<b-button variant="primary" @click="active_token=t; generic_action = Actions.UPDATE;"><i
|
||||
class="far fa-edit"></i></b-button>
|
||||
<b-button variant="danger" @click="active_token=t; generic_action = Actions.DELETE;"><i
|
||||
class="fas fa-trash-alt"></i></b-button>
|
||||
</b-button-group>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
|
||||
<b-button @click="generic_action=Actions.CREATE">NEW</b-button>
|
||||
<b-button class="mt-1" variant="success" @click="generic_action=Actions.CREATE">{{ $t('New') }}</b-button>
|
||||
|
||||
<generic-modal-form :model="Models.ACCESS_TOKEN" :action="generic_action" :show="generic_action !== null"
|
||||
:item1="active_token"
|
||||
@ -24,10 +71,11 @@
|
||||
|
||||
<script>
|
||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||
import {ApiMixin, StandardToasts} from "@/utils/utils";
|
||||
import {ApiMixin, ResolveUrlMixin, StandardToasts} from "@/utils/utils";
|
||||
|
||||
import axios from "axios";
|
||||
import GenericModalForm from "@/components/Modals/GenericModalForm";
|
||||
import moment from "moment";
|
||||
|
||||
axios.defaults.xsrfCookieName = 'csrftoken'
|
||||
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
|
||||
@ -35,7 +83,7 @@ axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
|
||||
export default {
|
||||
name: "APISettingsComponent",
|
||||
components: {GenericModalForm},
|
||||
mixins: [ApiMixin,],
|
||||
mixins: [ApiMixin, ResolveUrlMixin],
|
||||
props: {
|
||||
user_id: Number,
|
||||
},
|
||||
@ -52,6 +100,10 @@ export default {
|
||||
this.loadTokens()
|
||||
},
|
||||
methods: {
|
||||
formatDate: function (datetime) {
|
||||
moment.locale(window.navigator.language);
|
||||
return moment(datetime).format('L')
|
||||
},
|
||||
loadTokens: function () {
|
||||
let apiFactory = new ApiApiFactory()
|
||||
apiFactory.listAccessTokens().then(result => {
|
||||
|
@ -1,15 +1,32 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="user !== undefined">
|
||||
|
||||
<b-form-input v-model="user.username" @change="updateUser(false)" disabled></b-form-input>
|
||||
<b-form-input v-model="user.first_name" @change="updateUser(false)"></b-form-input>
|
||||
<b-form-input v-model="user.last_name" @change="updateUser(false)"></b-form-input>
|
||||
<div v-if="user !== undefined">
|
||||
<b-form-group :label="$t('Username')">
|
||||
<b-form-input v-model="user.username" @change="updateUser(false)" disabled></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :label="$t('First_name')">
|
||||
<b-form-input v-model="user.first_name" @change="updateUser(false)" :placeholder="$t('First_name')"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :label="$t('Last_name')">
|
||||
<b-form-input v-model="user.last_name" @change="updateUser(false)" :placeholder="$t('Last_name')"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
</div>
|
||||
|
||||
<a :href="resolveDjangoUrl('account_email')" class="btn btn-primary">Emails</a>
|
||||
<a :href="resolveDjangoUrl('account_change_password')" class="btn btn-primary">Password</a>
|
||||
<a :href="resolveDjangoUrl('socialaccount_connections')" class="btn btn-primary">Social</a>
|
||||
<b-button-toolbar>
|
||||
<b-button-group class="mx-1">
|
||||
<a :href="resolveDjangoUrl('account_email')" class="btn btn-primary">{{ $t('Manage_Emails') }}</a>
|
||||
</b-button-group>
|
||||
<b-button-group class="mx-1">
|
||||
<a :href="resolveDjangoUrl('account_change_password')" class="btn btn-primary">{{ $t('Change_Password') }}</a>
|
||||
</b-button-group>
|
||||
<b-button-group class="mx-1">
|
||||
<a :href="resolveDjangoUrl('socialaccount_connections')" class="btn btn-primary">{{ $t('Social_Authentication') }}</a>
|
||||
</b-button-group>
|
||||
</b-button-toolbar>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,54 +1,83 @@
|
||||
<template>
|
||||
<div v-if="user_preferences !== undefined">
|
||||
|
||||
<b-form-input v-model="user_preferences.default_unit" @change="updateSettings"></b-form-input>
|
||||
<b-form-group :label="$t('Default_Unit')">
|
||||
<b-form-input v-model="user_preferences.default_unit" @change="updateSettings(false)"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
{{ user_preferences.ingredient_decimals }}
|
||||
<b-form-input type="range" min="0" max="4" step="1" v-model="user_preferences.ingredient_decimals"
|
||||
@change="updateSettings"></b-form-input>
|
||||
<b-form-group :label="$t('Decimals')">
|
||||
<b-form-input type="number" min="0" max="4" step="1" v-model="user_preferences.ingredient_decimals"
|
||||
@change="updateSettings(false)"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-checkbox v-model="user_preferences.use_fractions" @change="updateSettings"></b-form-checkbox>
|
||||
<b-form-group :description="$t('Use_Fractions_Help')">
|
||||
<b-form-checkbox v-model="user_preferences.use_fractions" @change="updateSettings(false)">
|
||||
{{ $t('Use_Fractions') }}
|
||||
</b-form-checkbox>
|
||||
</b-form-group>
|
||||
|
||||
<hr/>
|
||||
Language
|
||||
<b-form-select v-model="$i18n.locale" @change="updateLanguage">
|
||||
<b-form-select-option v-bind:key="l[0]" v-for="l in languages" :value="l[1]">{{ l[0] }} ({{
|
||||
l[1]
|
||||
}})
|
||||
</b-form-select-option>
|
||||
</b-form-select>
|
||||
<b-form-group>
|
||||
<b-form-checkbox v-model="user_preferences.use_kj" @change="updateSettings(false);">{{ $t('Use_Kj') }}
|
||||
</b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group>
|
||||
<b-form-checkbox v-model="user_preferences.comments" @change="updateSettings(false);">
|
||||
{{ $t('Comments_setting') }}
|
||||
</b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group :description="$t('left_handed_help')">
|
||||
<b-form-checkbox v-model="user_preferences.left_handed" @change="updateSettings(false);">
|
||||
{{ $t('left_handed') }}
|
||||
</b-form-checkbox>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-select v-model="user_preferences.theme" @change="updateSettings(true);">
|
||||
<b-form-select-option value="TANDOOR">Tandoor</b-form-select-option>
|
||||
<b-form-select-option value="BOOTSTRAP">Bootstrap</b-form-select-option>
|
||||
<b-form-select-option value="DARKLY">Darkly</b-form-select-option>
|
||||
<b-form-select-option value="FLATLY">Flatly</b-form-select-option>
|
||||
<b-form-select-option value="SUPERHERO">Superhero</b-form-select-option>
|
||||
</b-form-select>
|
||||
|
||||
<b-form-checkbox v-model="user_preferences.sticky_navbar" @change="updateSettings(true);"></b-form-checkbox>
|
||||
|
||||
<b-form-select v-model="user_preferences.nav_color" @change="updateSettings(true);">
|
||||
<b-form-select-option value="PRIMARY">Primary</b-form-select-option>
|
||||
<b-form-select-option value="SECONDARY">Secondary</b-form-select-option>
|
||||
<b-form-select-option value="SUCCESS">Success</b-form-select-option>
|
||||
<b-form-select-option value="INFO">Info</b-form-select-option>
|
||||
<b-form-select-option value="WARNING">Warning</b-form-select-option>
|
||||
<b-form-select-option value="DANGER">Danger</b-form-select-option>
|
||||
<b-form-select-option value="LIGHT">Light</b-form-select-option>
|
||||
<b-form-select-option value="DARK">Dark</b-form-select-option>
|
||||
</b-form-select>
|
||||
|
||||
<hr/>
|
||||
|
||||
<b-form-checkbox v-model="user_preferences.use_kj" @change="updateSettings();"></b-form-checkbox>
|
||||
<b-form-checkbox v-model="user_preferences.comments" @change="updateSettings();"></b-form-checkbox>
|
||||
<b-form-checkbox v-model="user_preferences.left_handed" @change="updateSettings();"></b-form-checkbox>
|
||||
<b-form-group :label="$t('Language')">
|
||||
<b-form-select v-model="$i18n.locale" @change="updateLanguage">
|
||||
<b-form-select-option v-bind:key="l[0]" v-for="l in languages" :value="l[1]">{{ l[0] }} ({{
|
||||
l[1]
|
||||
}})
|
||||
</b-form-select-option>
|
||||
</b-form-select>
|
||||
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('Theme')">
|
||||
<b-form-select v-model="user_preferences.theme" @change="updateSettings(true);">
|
||||
<b-form-select-option value="TANDOOR">Tandoor</b-form-select-option>
|
||||
<b-form-select-option value="BOOTSTRAP">Bootstrap</b-form-select-option>
|
||||
<b-form-select-option value="DARKLY">Darkly</b-form-select-option>
|
||||
<b-form-select-option value="FLATLY">Flatly</b-form-select-option>
|
||||
<b-form-select-option value="SUPERHERO">Superhero</b-form-select-option>
|
||||
</b-form-select>
|
||||
|
||||
</b-form-group>
|
||||
<b-form-group :description="$t('Sticky_Nav_Help')">
|
||||
<b-form-checkbox v-model="user_preferences.sticky_navbar" @change="updateSettings(true);">
|
||||
{{ $t('Sticky_Nav') }}
|
||||
</b-form-checkbox>
|
||||
</b-form-group>
|
||||
<b-form-group :label="$t('Nav_Color')" :description="$t('Nav_Color_Help')">
|
||||
<b-form-select v-model="user_preferences.nav_color" @change="updateSettings(true);">
|
||||
<b-form-select-option value="PRIMARY">Primary</b-form-select-option>
|
||||
<b-form-select-option value="SECONDARY">Secondary</b-form-select-option>
|
||||
<b-form-select-option value="SUCCESS">Success</b-form-select-option>
|
||||
<b-form-select-option value="INFO">Info</b-form-select-option>
|
||||
<b-form-select-option value="WARNING">Warning</b-form-select-option>
|
||||
<b-form-select-option value="DANGER">Danger</b-form-select-option>
|
||||
<b-form-select-option value="LIGHT">Light</b-form-select-option>
|
||||
<b-form-select-option value="DARK">Dark</b-form-select-option>
|
||||
</b-form-select>
|
||||
|
||||
</b-form-group>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||
import {resolveDjangoUrl, StandardToasts} from "@/utils/utils";
|
||||
|
||||
@ -69,6 +98,8 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||
|
||||
this.user_preferences = this.preferences
|
||||
this.languages = window.AVAILABLE_LANGUAGES
|
||||
this.loadSettings()
|
||||
@ -86,7 +117,7 @@ export default {
|
||||
let apiFactory = new ApiApiFactory()
|
||||
apiFactory.partialUpdateUserPreference(this.user_id.toString(), this.user_preferences).then(result => {
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
|
||||
if (reload !== undefined) {
|
||||
if (reload) {
|
||||
location.reload()
|
||||
}
|
||||
}).catch(err => {
|
||||
|
@ -1,15 +1,16 @@
|
||||
<template>
|
||||
<div v-if="user_preferences !== undefined">
|
||||
|
||||
<generic-multiselect
|
||||
@change="updateSettings(false)"
|
||||
:model="Models.USER"
|
||||
:initial_selection="user_preferences.plan_share"
|
||||
label="display_name"
|
||||
:multiple="true"
|
||||
:placeholder="$t('User')"
|
||||
></generic-multiselect>
|
||||
|
||||
<b-form-group :label="$t('Share')" :description="$t('plan_share_desc')">
|
||||
<generic-multiselect
|
||||
@change="updateSettings(false)"
|
||||
:model="Models.USER"
|
||||
:initial_selection="user_preferences.plan_share"
|
||||
label="display_name"
|
||||
:multiple="true"
|
||||
:placeholder="$t('User')"
|
||||
></generic-multiselect>
|
||||
</b-form-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,13 +1,32 @@
|
||||
<template>
|
||||
<div v-if="user_preferences !== undefined">
|
||||
<div>
|
||||
|
||||
|
||||
<a :href="resolveDjangoUrl('view_shopping_settings')" class="btn btn-primary">Search Settings</a>
|
||||
<div v-if="false">
|
||||
<!--TODO search must fundamentally be reworked, thus i was to lazy to implement the settings -->
|
||||
|
||||
method
|
||||
|
||||
unpercice
|
||||
|
||||
accent
|
||||
|
||||
partial
|
||||
start with
|
||||
fuzzy
|
||||
full text
|
||||
trigram
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||
import {StandardToasts} from "@/utils/utils";
|
||||
import {ResolveUrlMixin, StandardToasts} from "@/utils/utils";
|
||||
|
||||
import axios from "axios";
|
||||
|
||||
@ -16,40 +35,20 @@ axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
|
||||
|
||||
export default {
|
||||
name: "SearchSettingsComponent",
|
||||
mixins: [ResolveUrlMixin],
|
||||
props: {
|
||||
user_id: Number,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
user_preferences: undefined,
|
||||
languages: [],
|
||||
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.user_preferences = this.preferences
|
||||
this.languages = window.AVAILABLE_LANGUAGES
|
||||
this.loadSettings()
|
||||
|
||||
},
|
||||
methods: {
|
||||
loadSettings: function () {
|
||||
let apiFactory = new ApiApiFactory()
|
||||
apiFactory.retrieveUserPreference(this.user_id.toString()).then(result => {
|
||||
this.user_preferences = result.data
|
||||
}).catch(err => {
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
|
||||
})
|
||||
},
|
||||
updateSettings: function (reload) {
|
||||
let apiFactory = new ApiApiFactory()
|
||||
apiFactory.partialUpdateUserPreference(this.user_id.toString(), this.user_preferences).then(result => {
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
|
||||
if (reload !== undefined) {
|
||||
location.reload()
|
||||
}
|
||||
}).catch(err => {
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
||||
})
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,32 +1,92 @@
|
||||
<template>
|
||||
<div v-if="user_preferences !== undefined">
|
||||
<b-form-group :label="$t('shopping_share')" :description="$t('shopping_share_desc')">
|
||||
<generic-multiselect
|
||||
@change="updateSettings(false)"
|
||||
:model="Models.USER"
|
||||
:initial_selection="user_preferences.shopping_share"
|
||||
label="display_name"
|
||||
:multiple="true"
|
||||
:placeholder="$t('User')"
|
||||
></generic-multiselect>
|
||||
</b-form-group>
|
||||
|
||||
<generic-multiselect
|
||||
@change="updateSettings(false)"
|
||||
:model="Models.USER"
|
||||
:initial_selection="user_preferences.shopping_share"
|
||||
label="display_name"
|
||||
:multiple="true"
|
||||
:placeholder="$t('User')"
|
||||
></generic-multiselect>
|
||||
<b-form-group :label="$t('shopping_auto_sync')" :description="$t('shopping_auto_sync_desc')">
|
||||
<b-form-input type="range" :min="SHOPPING_MIN_AUTOSYNC_INTERVAL" max="60" step="1" v-model="user_preferences.shopping_auto_sync"
|
||||
@change="updateSettings(false)"></b-form-input>
|
||||
<div class="text-center">
|
||||
<span v-if="user_preferences.shopping_auto_sync > 0">
|
||||
{{ Math.round(user_preferences.shopping_auto_sync) }}
|
||||
<span v-if="user_preferences.shopping_auto_sync === 1">{{ $t('Second') }}</span>
|
||||
<span v-else> {{ $t('Seconds') }}</span>
|
||||
</span>
|
||||
|
||||
<!--TODO load min autosync time from env -->
|
||||
<b-form-input type="range" min="0" max="60" step="1" v-model="user_preferences.shopping_auto_sync"
|
||||
@change="updateSettings(false)"></b-form-input>
|
||||
<span v-if="user_preferences.shopping_auto_sync < 1">{{ $t('Disable') }}</span>
|
||||
</div>
|
||||
<br/>
|
||||
<b-button class="btn btn-sm" @click="user_preferences.shopping_auto_sync = 0">{{ $t('Disabled') }}</b-button>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-checkbox v-model="user_preferences.mealplan_autoadd_shopping" @change="updateSettings(false)"></b-form-checkbox>
|
||||
<b-form-checkbox v-model="user_preferences.mealplan_autoexclude_onhand" @change="updateSettings(false)"></b-form-checkbox>
|
||||
<b-form-checkbox v-model="user_preferences.mealplan_autoinclude_related" @change="updateSettings(false)"></b-form-checkbox>
|
||||
<b-form-checkbox v-model="user_preferences.shopping_add_onhand" @change="updateSettings(false)"></b-form-checkbox>
|
||||
<b-form-group :description="$t('mealplan_autoadd_shopping_desc')">
|
||||
<b-form-checkbox v-model="user_preferences.mealplan_autoadd_shopping"
|
||||
@change="updateSettings(false)">{{ $t('mealplan_autoadd_shopping') }}
|
||||
</b-form-checkbox>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-input type="number" v-model="user_preferences.default_delay" @change="updateSettings(false)"></b-form-input>
|
||||
<b-form-checkbox v-model="user_preferences.filter_to_supermarket" @change="updateSettings(false)"></b-form-checkbox>
|
||||
<b-form-input type="range" min="0" max="14" step="1" v-model="user_preferences.shopping_recent_days"
|
||||
@change="updateSettings(false)"></b-form-input>
|
||||
<b-form-group :description="$t('mealplan_autoexclude_onhand_desc')">
|
||||
<b-form-checkbox v-model="user_preferences.mealplan_autoexclude_onhand"
|
||||
@change="updateSettings(false)">{{ $t('mealplan_autoexclude_onhand') }}
|
||||
</b-form-checkbox>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :description="$t('mealplan_autoinclude_related_desc')">
|
||||
<b-form-checkbox v-model="user_preferences.mealplan_autoinclude_related"
|
||||
@change="updateSettings(false)">{{ $t('mealplan_autoinclude_related') }}
|
||||
</b-form-checkbox>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :description="$t('shopping_add_onhand_desc')">
|
||||
<b-form-checkbox v-model="user_preferences.shopping_add_onhand"
|
||||
@change="updateSettings(false)">{{ $t('shopping_add_onhand') }}
|
||||
</b-form-checkbox>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :label="$t('default_delay')" :description="$t('default_delay_desc')">
|
||||
<b-form-input type="range" min="1" max="72" step="1" v-model="user_preferences.default_delay"
|
||||
@change="updateSettings(false)"></b-form-input>
|
||||
<div class="text-center">
|
||||
<span>{{ Math.round(user_preferences.default_delay) }}
|
||||
<span v-if="user_preferences.default_delay === 1">{{ $t('Hour') }}</span>
|
||||
<span v-else> {{ $t('Hours') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :description="$t('filter_to_supermarket_desc')">
|
||||
<b-form-checkbox v-model="user_preferences.filter_to_supermarket"
|
||||
@change="updateSettings(false)">{{ $t('filter_to_supermarket') }}
|
||||
</b-form-checkbox>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :label="$t('shopping_recent_days')" :description="$t('shopping_recent_days_desc')">
|
||||
<b-form-input type="range" min="0" max="14" step="1" v-model="user_preferences.shopping_recent_days"
|
||||
@change="updateSettings(false)"></b-form-input>
|
||||
<div class="text-center">
|
||||
<span>{{ Math.round(user_preferences.shopping_recent_days) }}
|
||||
<span v-if="user_preferences.shopping_recent_days === 1">{{ $t('Day') }}</span>
|
||||
<span v-else> {{ $t('Days') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :label="$t('csv_delim_label')" :description="$t('csv_delim_help')">
|
||||
<b-form-input v-model="user_preferences.csv_delim" @change="updateSettings(false)"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group :label="$t('csv_prefix_label')" :description="$t('csv_prefix_help')">
|
||||
<b-form-input v-model="user_preferences.csv_prefix" @change="updateSettings(false)"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
|
||||
<b-form-input v-model="user_preferences.csv_delim" @change="updateSettings(false)"></b-form-input>
|
||||
<b-form-input v-model="user_preferences.csv_prefix" @change="updateSettings(false)"></b-form-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -50,6 +110,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
user_preferences: undefined,
|
||||
SHOPPING_MIN_AUTOSYNC_INTERVAL: window.SHOPPING_MIN_AUTOSYNC_INTERVAL,
|
||||
languages: [],
|
||||
}
|
||||
},
|
||||
@ -69,9 +130,10 @@ export default {
|
||||
},
|
||||
updateSettings: function (reload) {
|
||||
let apiFactory = new ApiApiFactory()
|
||||
this.$emit('updated', this.user_preferences)
|
||||
apiFactory.partialUpdateUserPreference(this.user_id.toString(), this.user_preferences).then(result => {
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
|
||||
if (reload !== undefined) {
|
||||
if (reload) {
|
||||
location.reload()
|
||||
}
|
||||
}).catch(err => {
|
||||
|
@ -169,6 +169,8 @@
|
||||
"tree_root": "Root of Tree",
|
||||
"Icon": "Icon",
|
||||
"Unit": "Unit",
|
||||
"Decimals": "Decimals",
|
||||
"Default_Unit": "Default Unit",
|
||||
"No_Results": "No Results",
|
||||
"New_Unit": "New Unit",
|
||||
"Create_New_Shopping Category": "Create New Shopping Category",
|
||||
@ -219,6 +221,8 @@
|
||||
"Title_or_Recipe_Required": "Title or recipe selection required",
|
||||
"Color": "Color",
|
||||
"New_Meal_Type": "New Meal type",
|
||||
"Use_Fractions": "Use Fractions",
|
||||
"Use_Fractions_Help": "Automatically convert decimals to fractions when viewing a recipe.",
|
||||
"AddFoodToShopping": "Add {food} to your shopping list",
|
||||
"RemoveFoodFromShopping": "Remove {food} from your shopping list",
|
||||
"DeleteShoppingConfirm": "Are you sure that you want to remove all {food} from the shopping list?",
|
||||
@ -241,6 +245,8 @@
|
||||
"FoodInherit": "Food Inheritable Fields",
|
||||
"ShowUncategorizedFood": "Show Undefined",
|
||||
"GroupBy": "Group By",
|
||||
"Language": "Language",
|
||||
"Theme": "Theme",
|
||||
"SupermarketCategoriesOnly": "Supermarket Categories Only",
|
||||
"MoveCategory": "Move To: ",
|
||||
"CountMore": "...+{count} more",
|
||||
@ -259,6 +265,7 @@
|
||||
"mealplan_autoexclude_onhand": "Exclude Food On Hand",
|
||||
"mealplan_autoinclude_related": "Add Related Recipes",
|
||||
"default_delay": "Default Delay Hours",
|
||||
"plan_share_desc": "New Meal Plan entries will automatically be shared with selected users.",
|
||||
"shopping_share_desc": "Users will see all items you add to your shopping list. They must add you to see items on their list.",
|
||||
"shopping_auto_sync_desc": "Setting to 0 will disable auto sync. When viewing a shopping list the list is updated every set seconds to sync changes someone else might have made. Useful when shopping with multiple people but will use mobile data.",
|
||||
"mealplan_autoadd_shopping_desc": "Automatically add meal plan ingredients to shopping list.",
|
||||
@ -270,6 +277,12 @@
|
||||
"Auto_Planner": "Auto-Planner",
|
||||
"New_Cookbook": "New cookbook",
|
||||
"Hide_Keyword": "Hide keywords",
|
||||
"Hour": "Hour",
|
||||
"Hours": "Hours",
|
||||
"Day": "Day",
|
||||
"Days": "Days",
|
||||
"Second": "Second",
|
||||
"Seconds": "Seconds",
|
||||
"Clear": "Clear",
|
||||
"Users": "Users",
|
||||
"Invites": "Invites",
|
||||
@ -310,6 +323,9 @@
|
||||
"shopping_category_help": "Supermarkets can be ordered and filtered by Shopping Category according to the layout of the aisles.",
|
||||
"food_recipe_help": "Linking a recipe here will include the linked recipe in any other recipe that use this food",
|
||||
"Foods": "Foods",
|
||||
"Account": "Account",
|
||||
"Cosmetic": "Cosmetic",
|
||||
"API": "API",
|
||||
"enable_expert": "Enable Expert Mode",
|
||||
"expert_mode": "Expert Mode",
|
||||
"simple_mode": "Simple Mode",
|
||||
@ -353,6 +369,12 @@
|
||||
"App": "App",
|
||||
"Message": "Message",
|
||||
"Bookmarklet": "Bookmarklet",
|
||||
"Sticky_Nav": "Sticky Navigation",
|
||||
"Sticky_Nav_Help": "Always show the navigation menu at the top of the screen.",
|
||||
"Nav_Color": "Navigation Color",
|
||||
"Nav_Color_Help": "Change navigation color.",
|
||||
"Use_Kj": "Use kJ instead of kcal",
|
||||
"Comments_setting": "Show Comments",
|
||||
"click_image_import": "Click the image you want to import for this recipe",
|
||||
"no_more_images_found": "No additional images found on Website.",
|
||||
"import_duplicates": "To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.",
|
||||
@ -391,6 +413,9 @@
|
||||
"Ratings": "Ratings",
|
||||
"Internal": "Internal",
|
||||
"Units": "Units",
|
||||
"Manage_Emails": "Manage Emails",
|
||||
"Change_Password": "Change Password",
|
||||
"Social_Authentication": "Social Authentication",
|
||||
"Random Recipes": "Random Recipes",
|
||||
"parameter_count": "Parameter {count}",
|
||||
"select_keyword": "Select Keyword",
|
||||
@ -404,12 +429,17 @@
|
||||
"Select": "Select",
|
||||
"Supermarkets": "Supermarkets",
|
||||
"User": "User",
|
||||
"Username": "Username",
|
||||
"First_name": "First Name",
|
||||
"Last_name": "Last Name",
|
||||
"Keyword": "Keyword",
|
||||
"Advanced": "Advanced",
|
||||
"Page": "Page",
|
||||
"Single": "Single",
|
||||
"Multiple": "Multiple",
|
||||
"Reset": "Reset",
|
||||
"Disabled": "Disabled",
|
||||
"Disable": "Disable",
|
||||
"Options": "Options",
|
||||
"Create Food": "Create Food",
|
||||
"create_food_desc": "Create a food and link it to this recipe.",
|
||||
|
Loading…
Reference in New Issue
Block a user