mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-01 04:10:06 -05:00
copy shopping as markdown
This commit is contained in:
@@ -502,6 +502,8 @@ class ShoppingPreferenceForm(forms.ModelForm):
|
|||||||
'filter_to_supermarket': _('Filter shopping list to only include supermarket categories.'),
|
'filter_to_supermarket': _('Filter shopping list to only include supermarket categories.'),
|
||||||
'shopping_recent_days': _('Days of recent shopping list entries to display.'),
|
'shopping_recent_days': _('Days of recent shopping list entries to display.'),
|
||||||
'csv_delim': _('Delimiter to use for CSV exports.'),
|
'csv_delim': _('Delimiter to use for CSV exports.'),
|
||||||
|
'csv_prefix': _('Prefix to add when copying list to the clipboard.'),
|
||||||
|
|
||||||
}
|
}
|
||||||
labels = {
|
labels = {
|
||||||
'shopping_share': _('Share Shopping List'),
|
'shopping_share': _('Share Shopping List'),
|
||||||
@@ -513,6 +515,7 @@ class ShoppingPreferenceForm(forms.ModelForm):
|
|||||||
'filter_to_supermarket': _('Filter to Supermarket'),
|
'filter_to_supermarket': _('Filter to Supermarket'),
|
||||||
'shopping_recent_days': _('Recent Days'),
|
'shopping_recent_days': _('Recent Days'),
|
||||||
'csv_delim': _('CSV Delimiter'),
|
'csv_delim': _('CSV Delimiter'),
|
||||||
|
"csv_prefix_label": _("List Prefix")
|
||||||
}
|
}
|
||||||
|
|
||||||
widgets = {
|
widgets = {
|
||||||
|
|||||||
@@ -15,4 +15,9 @@ class Migration(migrations.Migration):
|
|||||||
name='csv_delim',
|
name='csv_delim',
|
||||||
field=models.CharField(default=',', max_length=2),
|
field=models.CharField(default=',', max_length=2),
|
||||||
),
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userpreference',
|
||||||
|
name='csv_prefix',
|
||||||
|
field=models.CharField(blank=True, max_length=3),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -334,6 +334,7 @@ class UserPreference(models.Model, PermissionModelMixin):
|
|||||||
default_delay = models.DecimalField(default=4, max_digits=8, decimal_places=4)
|
default_delay = models.DecimalField(default=4, max_digits=8, decimal_places=4)
|
||||||
shopping_recent_days = models.PositiveIntegerField(default=7)
|
shopping_recent_days = models.PositiveIntegerField(default=7)
|
||||||
csv_delim = models.CharField(max_length=2, default=",")
|
csv_delim = models.CharField(max_length=2, default=",")
|
||||||
|
csv_prefix = models.CharField(max_length=3, blank=True,)
|
||||||
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
space = models.ForeignKey(Space, on_delete=models.CASCADE, null=True)
|
space = models.ForeignKey(Space, on_delete=models.CASCADE, null=True)
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ class UserPreferenceSerializer(serializers.ModelSerializer):
|
|||||||
'user', 'theme', 'nav_color', 'default_unit', 'default_page', 'use_kj',
|
'user', 'theme', 'nav_color', 'default_unit', 'default_page', 'use_kj',
|
||||||
'search_style', 'show_recent', 'plan_share', 'ingredient_decimals',
|
'search_style', 'show_recent', 'plan_share', 'ingredient_decimals',
|
||||||
'comments', 'shopping_auto_sync', 'mealplan_autoadd_shopping', 'food_ignore_default', 'default_delay',
|
'comments', 'shopping_auto_sync', 'mealplan_autoadd_shopping', 'food_ignore_default', 'default_delay',
|
||||||
'mealplan_autoinclude_related', 'mealplan_autoexclude_onhand', 'shopping_share', 'shopping_recent_days', 'csv_delim'
|
'mealplan_autoinclude_related', 'mealplan_autoexclude_onhand', 'shopping_share', 'shopping_recent_days', 'csv_delim', 'csv_prefix'
|
||||||
)
|
)
|
||||||
read_only_fields = ['user']
|
read_only_fields = ['user']
|
||||||
|
|
||||||
|
|||||||
@@ -388,6 +388,8 @@ def user_settings(request):
|
|||||||
up.filter_to_supermarket = shopping_form.cleaned_data['filter_to_supermarket']
|
up.filter_to_supermarket = shopping_form.cleaned_data['filter_to_supermarket']
|
||||||
up.default_delay = shopping_form.cleaned_data['default_delay']
|
up.default_delay = shopping_form.cleaned_data['default_delay']
|
||||||
up.shopping_recent_days = shopping_form.cleaned_data['shopping_recent_days']
|
up.shopping_recent_days = shopping_form.cleaned_data['shopping_recent_days']
|
||||||
|
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:
|
if up.shopping_auto_sync < settings.SHOPPING_MIN_AUTOSYNC_INTERVAL:
|
||||||
up.shopping_auto_sync = settings.SHOPPING_MIN_AUTOSYNC_INTERVAL
|
up.shopping_auto_sync = settings.SHOPPING_MIN_AUTOSYNC_INTERVAL
|
||||||
up.save()
|
up.save()
|
||||||
|
|||||||
@@ -6,19 +6,18 @@
|
|||||||
<b-button variant="link" class="px-0">
|
<b-button variant="link" class="px-0">
|
||||||
<i class="btn fas fa-plus-circle fa-lg px-0" @click="entrymode = !entrymode" :class="entrymode ? 'text-success' : 'text-muted'" />
|
<i class="btn fas fa-plus-circle fa-lg px-0" @click="entrymode = !entrymode" :class="entrymode ? 'text-success' : 'text-muted'" />
|
||||||
</b-button>
|
</b-button>
|
||||||
<b-button variant="link" class="px-0">
|
<b-button variant="link" class="px-1">
|
||||||
<i class="fas fa-download fa-lg nav-link dropdown-toggle text-muted px-0" id="downloadShoppingLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></i>
|
<i class="fas fa-download fa-lg nav-link dropdown-toggle text-muted px-1" id="downloadShoppingLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></i>
|
||||||
|
|
||||||
<div class="dropdown-menu dropdown-menu-center" aria-labelledby="downloadShoppingLink">
|
<div class="dropdown-menu dropdown-menu-center" aria-labelledby="downloadShoppingLink">
|
||||||
<a class="dropdown-item disabled" @click="download('CSV')"><i class="fas fa-file-import"></i> {{ $t("Download csv") }}</a>
|
|
||||||
<DownloadPDF dom="#shoppinglist" name="shopping.pdf" :label="$t('download_pdf')" icon="far fa-file-pdf" />
|
<DownloadPDF dom="#shoppinglist" name="shopping.pdf" :label="$t('download_pdf')" icon="far fa-file-pdf" />
|
||||||
<DownloadCSV :items="csvData" :delim="settings.csv_delim" name="shopping.csv" :label="$t('download_csv')" icon="fas fa-file-csv" />
|
<DownloadCSV :items="csvData" :delim="settings.csv_delim" name="shopping.csv" :label="$t('download_csv')" icon="fas fa-file-csv" />
|
||||||
<a class="dropdown-item disabled" @click="download('clipboard')"><i class="fas fa-plus"></i> {{ $t("copy to clipboard") }}</a>
|
<CopyToClipboard :items="csvData" :settings="settings" :label="$t('copy_to_clipboard')" icon="fas fa-clipboard-list" />
|
||||||
<a class="dropdown-item disabled" @click="download('markdown')"><i class="fas fa-plus"></i> {{ $t("copy as markdown") }}</a>
|
<CopyToClipboard :items="csvData" :settings="settings" format="table" :label="$t('copy_markdown_table')" icon="fab fa-markdown" />
|
||||||
</div>
|
</div>
|
||||||
</b-button>
|
</b-button>
|
||||||
<b-button variant="link" id="id_filters_button" class="px-0">
|
<b-button variant="link" id="id_filters_button" class="px-1">
|
||||||
<i class="btn fas fa-filter text-decoration-none fa-lg px-0" :class="filterApplied ? 'text-danger' : 'text-muted'" />
|
<i class="btn fas fa-filter text-decoration-none fa-lg px-1" :class="filterApplied ? 'text-danger' : 'text-muted'" />
|
||||||
</b-button>
|
</b-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -360,6 +359,19 @@
|
|||||||
</em>
|
</em>
|
||||||
</div>
|
</div>
|
||||||
</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 type="string" size="sm" 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>
|
||||||
</b-card>
|
</b-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -466,6 +478,7 @@ import ContextMenuItem from "@/components/ContextMenu/ContextMenuItem"
|
|||||||
import ShoppingLineItem from "@/components/ShoppingLineItem"
|
import ShoppingLineItem from "@/components/ShoppingLineItem"
|
||||||
import DownloadPDF from "@/components/Buttons/DownloadPDF"
|
import DownloadPDF from "@/components/Buttons/DownloadPDF"
|
||||||
import DownloadCSV from "@/components/Buttons/DownloadCSV"
|
import DownloadCSV from "@/components/Buttons/DownloadCSV"
|
||||||
|
import CopyToClipboard from "@/components/Buttons/CopyToClipboard"
|
||||||
import GenericMultiselect from "@/components/GenericMultiselect"
|
import GenericMultiselect from "@/components/GenericMultiselect"
|
||||||
import GenericPill from "@/components/GenericPill"
|
import GenericPill from "@/components/GenericPill"
|
||||||
import LookupInput from "@/components/Modals/LookupInput"
|
import LookupInput from "@/components/Modals/LookupInput"
|
||||||
@@ -480,7 +493,7 @@ Vue.use(BootstrapVue)
|
|||||||
export default {
|
export default {
|
||||||
name: "ShoppingListView",
|
name: "ShoppingListView",
|
||||||
mixins: [ApiMixin],
|
mixins: [ApiMixin],
|
||||||
components: { ContextMenu, ContextMenuItem, ShoppingLineItem, GenericMultiselect, GenericPill, draggable, LookupInput, DownloadPDF, DownloadCSV },
|
components: { ContextMenu, ContextMenuItem, ShoppingLineItem, GenericMultiselect, GenericPill, draggable, LookupInput, DownloadPDF, DownloadCSV, CopyToClipboard },
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -504,6 +517,7 @@ export default {
|
|||||||
filter_to_supermarket: false,
|
filter_to_supermarket: false,
|
||||||
shopping_recent_days: 7,
|
shopping_recent_days: 7,
|
||||||
csv_delim: ",",
|
csv_delim: ",",
|
||||||
|
csv_prefix: undefined,
|
||||||
},
|
},
|
||||||
new_supermarket: { entrymode: false, value: undefined, editmode: undefined },
|
new_supermarket: { entrymode: false, value: undefined, editmode: undefined },
|
||||||
new_category: { entrymode: false, value: undefined },
|
new_category: { entrymode: false, value: undefined },
|
||||||
@@ -665,7 +679,6 @@ export default {
|
|||||||
|
|
||||||
this.settings = getUserPreference()
|
this.settings = getUserPreference()
|
||||||
this.delay = this.settings.default_delay || 4
|
this.delay = this.settings.default_delay || 4
|
||||||
this.delim = this.settings.csv_delim || ","
|
|
||||||
this.supermarket_categories_only = this.settings.filter_to_supermarket
|
this.supermarket_categories_only = this.settings.filter_to_supermarket
|
||||||
if (this.settings.shopping_auto_sync) {
|
if (this.settings.shopping_auto_sync) {
|
||||||
window.addEventListener("online", this.updateOnlineStatus)
|
window.addEventListener("online", this.updateOnlineStatus)
|
||||||
|
|||||||
62
vue/src/components/Buttons/CopyToClipboard.vue
Normal file
62
vue/src/components/Buttons/CopyToClipboard.vue
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a v-if="!button" class="dropdown-item" @click="clipboard"><i :class="icon"></i> {{ label }}</a>
|
||||||
|
<b-button v-if="button" @click="clipboard">{{ label }}</b-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { makeToast } from "@/utils/utils"
|
||||||
|
export default {
|
||||||
|
name: "CopyToClipboard",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
items: { type: Array },
|
||||||
|
icon: { type: String },
|
||||||
|
label: { type: String },
|
||||||
|
button: { type: Boolean, default: false },
|
||||||
|
settings: { type: Object },
|
||||||
|
format: { type: String, default: "delim" },
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clipboard: function () {
|
||||||
|
let text = ""
|
||||||
|
switch (this.format) {
|
||||||
|
case "delim":
|
||||||
|
text = this.delimited()
|
||||||
|
break
|
||||||
|
case "table":
|
||||||
|
text = this.table()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(text).then(makeToast(this.$t("Success"), this.$t("SuccessClipboard"), "success"))
|
||||||
|
},
|
||||||
|
delimited: function () {
|
||||||
|
let csvContent = ""
|
||||||
|
let delim = this.settings.csv_delim || ","
|
||||||
|
let prefix = this.settings.csv_prefix || ""
|
||||||
|
csvContent += [prefix + Object.keys(this.items[0]).join(delim), ...this.items.map((x) => prefix + Object.values(x).join(delim))].join("\n").replace(/(^\[)|(\]$)/gm, "")
|
||||||
|
return csvContent
|
||||||
|
},
|
||||||
|
table: function () {
|
||||||
|
let table = ""
|
||||||
|
let delim = "|"
|
||||||
|
table += [
|
||||||
|
delim + Object.keys(this.items[0]).join(delim) + delim,
|
||||||
|
delim +
|
||||||
|
Object.keys(this.items[0])
|
||||||
|
.map((x) => {
|
||||||
|
return ":---"
|
||||||
|
})
|
||||||
|
.join(delim) +
|
||||||
|
delim,
|
||||||
|
...this.items.map((x) => delim + Object.values(x).join(delim) + delim),
|
||||||
|
]
|
||||||
|
.join("\n")
|
||||||
|
.replace(/(^\[)|(\]$)/gm, "")
|
||||||
|
return table
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -269,5 +269,10 @@
|
|||||||
"download_pdf": "Download PDF",
|
"download_pdf": "Download PDF",
|
||||||
"download_csv": "Download CSV",
|
"download_csv": "Download CSV",
|
||||||
"csv_delim_help": "Delimiter to use for CSV exports.",
|
"csv_delim_help": "Delimiter to use for CSV exports.",
|
||||||
"csv_delim_label": "CSV Delimiter"
|
"csv_delim_label": "CSV Delimiter",
|
||||||
|
"SuccessClipboard": "Shopping list copied to clipboard",
|
||||||
|
"copy_to_clipboard": "Copy to Clipboard",
|
||||||
|
"csv_prefix_help": "Prefix to add when copying list to the clipboard.",
|
||||||
|
"csv_prefix_label": "List Prefix",
|
||||||
|
"copy_markdown_table": "Copy as Markdown Table"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user