mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-05 22:28:31 -05:00
various improvements
This commit is contained in:
@@ -7,7 +7,7 @@ class StyleTreeprocessor(Treeprocessor):
|
|||||||
def run_processor(self, node):
|
def run_processor(self, node):
|
||||||
for child in node:
|
for child in node:
|
||||||
if child.tag == "table":
|
if child.tag == "table":
|
||||||
child.set("class", "table table-bordered")
|
child.set("class", "markdown-body")
|
||||||
if child.tag == "img":
|
if child.tag == "img":
|
||||||
child.set("class", "img-fluid")
|
child.set("class", "img-fluid")
|
||||||
self.run_processor(child)
|
self.run_processor(child)
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ def render_instructions(step): # TODO deduplicate markdown cleanup code
|
|||||||
parsed_md = md.markdown(
|
parsed_md = md.markdown(
|
||||||
instructions,
|
instructions,
|
||||||
extensions=[
|
extensions=[
|
||||||
'markdown.extensions.fenced_code', TableExtension(),
|
'markdown.extensions.fenced_code', 'markdown.extensions.sane_lists', 'markdown.extensions.nl2br', TableExtension(),
|
||||||
UrlizeExtension(), MarkdownFormatExtension()
|
UrlizeExtension(), MarkdownFormatExtension()
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ django-debug-toolbar==4.3.0
|
|||||||
bleach==6.2.0
|
bleach==6.2.0
|
||||||
gunicorn==23.0.0
|
gunicorn==23.0.0
|
||||||
lxml==5.3.1
|
lxml==5.3.1
|
||||||
Markdown==3.5.1
|
Markdown==3.7
|
||||||
Pillow==11.1.0
|
Pillow==11.1.0
|
||||||
psycopg2-binary==2.9.10
|
psycopg2-binary==2.9.10
|
||||||
python-dotenv==1.0.0
|
python-dotenv==1.0.0
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="compiled_instructions" :ingredient_factor="ingredient_factor" :instructions_html="instructions_html"></component>
|
<component :is="compiled_instructions" :ingredient_factor="ingredient_factor" :instructions_html="instructions_html"></component>
|
||||||
<!-- <div v-html="instructions_html"></div>-->
|
<!-- <div v-html="instructions_html"></div>-->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -31,7 +31,8 @@ export default defineComponent({
|
|||||||
ingredient_factor: {type: Number, required: true},
|
ingredient_factor: {type: Number, required: true},
|
||||||
},
|
},
|
||||||
components: {ScalableNumber,},
|
components: {ScalableNumber,},
|
||||||
template: `<div>${this.instructions_html}</div>`
|
template: `
|
||||||
|
<div>${this.instructions_html}</div>`
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -41,3 +42,14 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
/**
|
||||||
|
vuetify removes all margins and paddings which break layout, re-add them by reverting vuetify change
|
||||||
|
*/
|
||||||
|
p, ol, ul, li {
|
||||||
|
padding: revert;
|
||||||
|
margin: revert;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<v-col cols="12" md="6" v-if="step.ingredients.length > 0">
|
<v-col cols="12" md="6" v-if="step.ingredients.length > 0">
|
||||||
<ingredients-table v-model="step.ingredients" :ingredient-factor="ingredientFactor"></ingredients-table>
|
<ingredients-table v-model="step.ingredients" :ingredient-factor="ingredientFactor"></ingredients-table>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6" class="markdown-body">
|
||||||
<instructions :instructions_html="step.instructionsMarkdown" :ingredient_factor="ingredientFactor" v-if="step.instructionsMarkdown != undefined"></instructions>
|
<instructions :instructions_html="step.instructionsMarkdown" :ingredient_factor="ingredientFactor" v-if="step.instructionsMarkdown != undefined"></instructions>
|
||||||
<!-- sub recipes dont have a correct schema, thus they use different variable naming -->
|
<!-- sub recipes dont have a correct schema, thus they use different variable naming -->
|
||||||
<instructions :instructions_html="step.instructions_markdown" :ingredient_factor="ingredientFactor" v-else></instructions>
|
<instructions :instructions_html="step.instructions_markdown" :ingredient_factor="ingredientFactor" v-else></instructions>
|
||||||
|
|||||||
@@ -55,15 +55,15 @@
|
|||||||
<v-row v-for="(ingredient, index) in step.ingredients" dense>
|
<v-row v-for="(ingredient, index) in step.ingredients" dense>
|
||||||
<v-col cols="2" v-if="!ingredient.isHeader">
|
<v-col cols="2" v-if="!ingredient.isHeader">
|
||||||
<v-text-field :id="`id_input_amount_${step.id}_${index}`" :label="$t('Amount')" type="number" v-model="ingredient.amount" density="compact"
|
<v-text-field :id="`id_input_amount_${step.id}_${index}`" :label="$t('Amount')" type="number" v-model="ingredient.amount" density="compact"
|
||||||
hide-details>
|
hide-details v-if="!ingredient.noAmount">
|
||||||
|
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<v-icon icon="$dragHandle" class="drag-handle cursor-grab"></v-icon>
|
<v-icon icon="$dragHandle" class="drag-handle cursor-grab"></v-icon>
|
||||||
</template>
|
</template>
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="3" v-if="!ingredient.isHeader">
|
<v-col cols="3" v-if="!ingredient.isHeader ">
|
||||||
<model-select model="Unit" v-model="ingredient.unit" density="compact" allow-create hide-details></model-select>
|
<model-select model="Unit" v-model="ingredient.unit" density="compact" allow-create hide-details v-if="!ingredient.noAmount"></model-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="3" v-if="!ingredient.isHeader">
|
<v-col cols="3" v-if="!ingredient.isHeader">
|
||||||
<model-select model="Food" v-model="ingredient.food" density="compact" allow-create hide-details></model-select>
|
<model-select model="Food" v-model="ingredient.food" density="compact" allow-create hide-details></model-select>
|
||||||
@@ -84,6 +84,9 @@
|
|||||||
<v-list-item link>
|
<v-list-item link>
|
||||||
<v-switch v-model="step.ingredients[index].isHeader" :label="$t('Headline')" hide-details></v-switch>
|
<v-switch v-model="step.ingredients[index].isHeader" :label="$t('Headline')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
<v-list-item link>
|
||||||
|
<v-switch v-model="step.ingredients[index].noAmount" :label="$t('Disable_Amount')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
<v-list-item @click="editingIngredientIndex = index; dialogIngredientSorter = true" prepend-icon="fa-solid fa-sort">{{
|
<v-list-item @click="editingIngredientIndex = index; dialogIngredientSorter = true" prepend-icon="fa-solid fa-sort">{{
|
||||||
$t('Move')
|
$t('Move')
|
||||||
}}
|
}}
|
||||||
@@ -120,7 +123,7 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-label>{{ $t('Instructions') }}</v-label>
|
<v-label>{{ $t('Instructions') }}</v-label>
|
||||||
<v-alert @click="dialogMarkdownEditor = true" class="mt-2 cursor-pointer" min-height="52px">
|
<v-alert @click="dialogMarkdownEditor = true" class="mt-2 cursor-pointer" min-height="52px" v-if="mobile">
|
||||||
<template v-if="step.instruction != '' && step.instruction != null">
|
<template v-if="step.instruction != '' && step.instruction != null">
|
||||||
{{ step.instruction }}
|
{{ step.instruction }}
|
||||||
</template>
|
</template>
|
||||||
@@ -128,6 +131,11 @@
|
|||||||
<i> {{ $t('InstructionsEditHelp') }} </i>
|
<i> {{ $t('InstructionsEditHelp') }} </i>
|
||||||
</template>
|
</template>
|
||||||
</v-alert>
|
</v-alert>
|
||||||
|
<template v-else>
|
||||||
|
<p>
|
||||||
|
<step-markdown-editor class="h-100" v-model="step"></step-markdown-editor>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
@@ -352,7 +360,11 @@ function handleIngredientNoteTab(event: KeyboardEvent, index: number) {
|
|||||||
* insert a new ingredient and focus its first input
|
* insert a new ingredient and focus its first input
|
||||||
*/
|
*/
|
||||||
function insertAndFocusIngredient() {
|
function insertAndFocusIngredient() {
|
||||||
let ingredient = {} as Ingredient
|
let ingredient = {
|
||||||
|
amount: 0,
|
||||||
|
unit: null,
|
||||||
|
food: null,
|
||||||
|
} as Ingredient
|
||||||
|
|
||||||
if (defaultUnit.value != null) {
|
if (defaultUnit.value != null) {
|
||||||
ingredient.unit = defaultUnit.value
|
ingredient.unit = defaultUnit.value
|
||||||
|
|||||||
@@ -2,23 +2,23 @@
|
|||||||
<mavon-editor v-model="step.instruction" :autofocus="false"
|
<mavon-editor v-model="step.instruction" :autofocus="false"
|
||||||
style="z-index: auto" :id="'id_instruction_' + step.id"
|
style="z-index: auto" :id="'id_instruction_' + step.id"
|
||||||
:language="'en'"
|
:language="'en'"
|
||||||
:toolbars="md_editor_toolbars" :defaultOpen="'edit'">
|
:toolbars="md_editor_toolbars" :defaultOpen="'edit'" ref="markdownEditor">
|
||||||
<template #left-toolbar-after>
|
<template #left-toolbar-after>
|
||||||
<span class="op-icon-divider"></span>
|
<span class="op-icon-divider"></span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="step.instruction+= ' {{ scale(100) }}'"
|
@click="insertTextAtPosition('{{ scale(100) }} ')"
|
||||||
class="op-icon fas fa-calculator"
|
class="op-icon fas fa-calculator"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
:title="$t('ScalableNumber')"
|
:title="$t('ScalableNumber')"
|
||||||
></button>
|
></button>
|
||||||
<button class="op-icon fa-solid fa-code">
|
<button class="op-icon fa-solid fa-code" v-if="templates.length > 0" type="button">
|
||||||
<v-menu activator="parent">
|
<v-menu activator="parent">
|
||||||
<v-list density="compact">
|
<v-list density="compact">
|
||||||
|
<v-list-subheader>{{$t('Ingredients')}}</v-list-subheader>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-for="template in templates"
|
v-for="template in templates"
|
||||||
@click="step.instruction+= template.template"
|
@click="insertTextAtPosition(template.template + ' ')"
|
||||||
>
|
>
|
||||||
<ingredient-string :ingredient="template.ingredient"></ingredient-string>
|
<ingredient-string :ingredient="template.ingredient"></ingredient-string>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
@@ -34,9 +34,11 @@
|
|||||||
import {Ingredient, Step} from "@/openapi";
|
import {Ingredient, Step} from "@/openapi";
|
||||||
import 'mavon-editor/dist/css/index.css'
|
import 'mavon-editor/dist/css/index.css'
|
||||||
import IngredientString from "@/components/display/IngredientString.vue";
|
import IngredientString from "@/components/display/IngredientString.vue";
|
||||||
import {computed} from "vue";
|
import {computed, nextTick, useTemplateRef} from "vue";
|
||||||
|
import editor from "mavon-editor";
|
||||||
|
|
||||||
const step = defineModel<Step>({required: true})
|
const step = defineModel<Step>({required: true})
|
||||||
|
const markdownEditor = useTemplateRef('markdownEditor')
|
||||||
|
|
||||||
type IngredientTemplate = {
|
type IngredientTemplate = {
|
||||||
name: string,
|
name: string,
|
||||||
@@ -58,6 +60,24 @@ const templates = computed(() => {
|
|||||||
return templateList
|
return templateList
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* insert the given text at the caret position into the text
|
||||||
|
* @param text
|
||||||
|
*/
|
||||||
|
function insertTextAtPosition(text: string){
|
||||||
|
let textarea = markdownEditor.value.getTextareaDom()
|
||||||
|
let position = textarea.selectionStart
|
||||||
|
if (step.value.instruction){
|
||||||
|
step.value.instruction = step.value.instruction.slice(0, position) + text + step.value.instruction.slice(position)
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
textarea.focus()
|
||||||
|
textarea.selectionStart = position + text.length
|
||||||
|
textarea.selectionEnd = position + text.length
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const md_editor_toolbars = {
|
const md_editor_toolbars = {
|
||||||
bold: true,
|
bold: true,
|
||||||
italic: true,
|
italic: true,
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
<v-btn color="delete" prepend-icon="$delete" v-if="isUpdate && !modelClass.model.disableDelete" :disabled="loading">{{ $t('Delete') }}
|
<v-btn color="delete" prepend-icon="$delete" v-if="isUpdate && !modelClass.model.disableDelete" :disabled="loading">{{ $t('Delete') }}
|
||||||
<delete-confirm-dialog :object-name="objectName" :model-name="$t(modelClass.model.localizationKey)" @delete="emit('delete')"></delete-confirm-dialog>
|
<delete-confirm-dialog :object-name="objectName" :model-name="$t(modelClass.model.localizationKey)" @delete="emit('delete')"></delete-confirm-dialog>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn color="save" prepend-icon="$create" @click="emit('save')" v-if="!isUpdate && !modelClass.model.disableCreate" :disabled="loading">{{ $t('Create') }}</v-btn>
|
<v-btn color="save" prepend-icon="$create" @click="emit('save')" v-if="!isUpdate && !modelClass.model.disableCreate" :loading="loading">{{ $t('Create') }}</v-btn>
|
||||||
<v-btn color="save" prepend-icon="$save" @click="emit('save')" v-if="isUpdate && !modelClass.model.disableUpdate" :disabled="loading">{{ $t('Save') }}</v-btn>
|
<v-btn color="save" prepend-icon="$save" @click="emit('save')" v-if="isUpdate && !modelClass.model.disableUpdate" :loading="loading"> {{ $t('Save') }}</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<v-file-upload v-model="file" @update:modelValue="updateUserFileName"
|
<v-file-upload v-model="file"
|
||||||
:title="(mobile) ? $t('Select_File') : $t('DragToUpload')"
|
:title="(mobile) ? $t('Select_File') : $t('DragToUpload')"
|
||||||
:browse-text="$t('Select_File')"
|
:browse-text="$t('Select_File')"
|
||||||
:divider-text="$t('or')"
|
:divider-text="$t('or')"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<v-card>
|
<v-card>
|
||||||
<v-card-text class="pt-2 pb-2">
|
<v-card-text class="pt-2 pb-2">
|
||||||
<v-btn variant="flat" @click="router.go(-1)" prepend-icon="fa-solid fa-arrow-left">{{ $t('Back') }}</v-btn>
|
<v-btn variant="flat" @click="router.go(-1)" prepend-icon="fa-solid fa-arrow-left">{{ $t('Back') }}</v-btn>
|
||||||
|
<v-btn variant="flat" @click="router.push({name : 'RecipeViewPage', params: {id: props.id}})" class="float-right" prepend-icon="fa-solid fa-eye" v-if="props.id && model.toLowerCase() == 'recipe'">{{$t('View')}}</v-btn>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
|
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {EditorSupportedModels, getGenericModelFromString} from "@/types/Models";
|
import {EditorSupportedModels, getGenericModelFromString} from "@/types/Models";
|
||||||
import {defineAsyncComponent, onMounted, PropType, shallowRef} from "vue";
|
import {defineAsyncComponent, onMounted, PropType, shallowRef, watch} from "vue";
|
||||||
import {useI18n} from "vue-i18n";
|
import {useI18n} from "vue-i18n";
|
||||||
|
|
||||||
const {t} = useI18n()
|
const {t} = useI18n()
|
||||||
@@ -35,6 +36,13 @@ const editorComponent = shallowRef(defineAsyncComponent(() => import(`@/componen
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
//TODO quick hack for some edge cases, move to proper reinitialization of all model editors should this case occur (currently only recipe editor create new via navigation btn)
|
||||||
|
watch(() => props.id, (newValue, oldValue) => {
|
||||||
|
if(newValue != oldValue){
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* after creation open object with correct URL in edit mode
|
* after creation open object with correct URL in edit mode
|
||||||
* @param obj obj that was created
|
* @param obj obj that was created
|
||||||
|
|||||||
@@ -1,27 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container>
|
<v-container>
|
||||||
|
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col>
|
||||||
<v-text-field v-model="dateTest1" label="Test 1 - text field type date" type="date"></v-text-field>
|
<p>Test P1</p>
|
||||||
|
<p>Test 2P</p>
|
||||||
|
<ol>
|
||||||
|
<li>Test 1</li>
|
||||||
|
<li>Test 2</li>
|
||||||
|
<li>Test 3</li>
|
||||||
|
</ol>
|
||||||
|
<p>Text Bla Bla3</p>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col>
|
|
||||||
{{ dateTest1 }}
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-date-input v-model="dateTest2" label="Test 2 - date input"></v-date-input>
|
|
||||||
</v-col>
|
|
||||||
<v-col> {{ dateTest2 }}</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-date-input v-model="dateTest3" label="Test 3 - date input with routeQueryModel"></v-date-input>
|
|
||||||
</v-col>
|
|
||||||
<v-col> {{ dateTest3 }}</v-col>
|
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
|
|
||||||
@@ -36,50 +25,6 @@ import {useRouteQuery} from "@vueuse/router";
|
|||||||
import {DateTime} from "luxon";
|
import {DateTime} from "luxon";
|
||||||
|
|
||||||
|
|
||||||
const dateTest1 = ref(null)
|
|
||||||
const dateTest2 = ref(null)
|
|
||||||
|
|
||||||
const dateTest3 = useRouteQuery('cookedonGte', null, {
|
|
||||||
transform: {
|
|
||||||
get: (value: string | null | Date) => {
|
|
||||||
|
|
||||||
if (value == null) {
|
|
||||||
console.log('get null')
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
console.log('get', new Date(value), (new Date(value)).getMonth())
|
|
||||||
return new Date(value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set: value => {
|
|
||||||
|
|
||||||
if (value == null) {
|
|
||||||
console.log('-- set null')
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
console.log('-- set', DateTime.fromJSDate(new Date(value)).toISODate())
|
|
||||||
return DateTime.fromJSDate(new Date(value)).toISODate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const typeOfDateTest1 = computed(() => {
|
|
||||||
return dateTest1 instanceof Date
|
|
||||||
})
|
|
||||||
|
|
||||||
const typeOfDateTest2 = computed(() => {
|
|
||||||
return dateTest2 instanceof Date
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(dateTest1, () => {
|
|
||||||
console.log(dateTest1.value, dateTest1.value.getMonth())
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(dateTest2, () => {
|
|
||||||
console.log(dateTest2.value, dateTest2.value.getMonth())
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user