Upgrade shakapacker, vue; switch to pinia

This commit is contained in:
Dan Elbert 2024-09-28 20:58:25 -05:00
parent bb2e29f25c
commit f246f71aa9
66 changed files with 7743 additions and 4796 deletions

View File

@ -4,4 +4,6 @@ db/*.sqlite*
tmp/*.*
public/assets
public/packs
node_modules/
node_modules/
.yarn
.pnp.*

2
.gitignore vendored
View File

@ -35,3 +35,5 @@ yarn-debug.log*
/yarn-error.log
yarn-debug.log*
.yarn-integrity
.yarn
.pnp.*

View File

@ -31,7 +31,7 @@ WORKDIR /parsley
COPY Gemfile* ./
RUN bundle install
COPY package.json yarn.lock ./
COPY package.json.org yarn.lock ./
RUN yarn install --production=true --frozen-lockfile
COPY . .

View File

@ -3,10 +3,11 @@ source 'https://rubygems.org'
gem 'rails', '7.2.1'
gem 'pg', '~> 1.5.8'
gem 'shakapacker', '6.5.4'
gem 'shakapacker', '8.0.2'
gem 'bootsnap', '>= 1.1.0', require: false
gem 'oj', '~> 3.16.6'
gem 'csv', '~> 3.3'
gem 'kaminari', '~> 1.2.2'
gem 'unitwise', '~> 2.3.0'

View File

@ -81,6 +81,7 @@ GEM
concurrent-ruby (1.3.4)
connection_pool (2.4.1)
crass (1.0.6)
csv (3.3.0)
dalli (3.2.8)
database_cleaner (2.0.2)
database_cleaner-active_record (>= 2, < 3)
@ -176,6 +177,7 @@ GEM
bigdecimal (>= 3.0)
ostruct (>= 0.2)
ostruct (0.6.0)
package_json (0.1.0)
parslet (2.0.0)
pg (1.5.8)
pry (0.14.2)
@ -261,8 +263,9 @@ GEM
rspec-support (3.13.1)
securerandom (0.3.1)
semantic_range (3.0.0)
shakapacker (6.5.4)
shakapacker (8.0.2)
activesupport (>= 5.2)
package_json
rack-proxy (>= 0.6.1)
railties (>= 5.2)
semantic_range (>= 2.3.0)
@ -296,6 +299,7 @@ PLATFORMS
DEPENDENCIES
bcrypt (~> 3.1.18)
bootsnap (>= 1.1.0)
csv (~> 3.3)
dalli (~> 3.2.8)
database_cleaner (~> 2.0.2)
factory_bot_rails (~> 6.4.3)
@ -309,7 +313,7 @@ DEPENDENCIES
rails-controller-testing
redcarpet (~> 3.6.0)
rspec-rails (~> 7.0.1)
shakapacker (= 6.5.4)
shakapacker (= 8.0.2)
sqlite3 (~> 2.1.0)
tzinfo-data
unitwise (~> 2.3.0)

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2020 Dan Elbert
Copyright (c) 2024 Dan Elbert
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,2 +1,2 @@
rails: bundle exec rails s -b 0.0.0.0
webpacker: bin/webpack-dev-server
shakapacker: bin/shakapacker-dev-server

View File

@ -5,7 +5,7 @@ A self hosted cookbook
Parsley is released under the MIT License.
Copyright (C) 2020 Dan Elbert
Copyright (C) 2024 Dan Elbert
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -4,13 +4,15 @@
<app-navbar></app-navbar>
<section id="main" class="">
<div class="container">
<transition name="fade" mode="out-in">
<router-view v-if="!hasError"></router-view>
<div v-else>
<h1>Error!</h1>
<p>{{error}}</p>
</div>
</transition>
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component v-if="!hasError" :is="Component" />
<div v-else>
<h1>Error!</h1>
<p>{{ error }}</p>
</div>
</transition>
</router-view>
</div>
</section>
</div>
@ -18,31 +20,31 @@
<script>
import { mapMutations, mapState } from "vuex";
import api from "../lib/Api";
import { mapState } from "pinia";
import TWEEN from '@tweenjs/tween.js';
import { useAppConfigStore } from "../stores/appConfig";
export default {
data() {
return {
api: api
};
},
computed: {
...mapState({
hasError: state => state.error !== null,
error: state => state.error,
authChecked: state => state.authChecked,
initialLoad: state => state.initialLoad
...mapState(useAppConfigStore, {
hasError: store => store.error !== null,
error: store => store.error,
authChecked: store => store.authChecked,
initialLoad: store => store.initialLoad
})
},
watch: {
isLoading(val) {
if (val) {
this.$Progress.start();
// this.$Progress.start();
} else {
this.$Progress.finish();
// this.$Progress.finish();
}
},

View File

@ -33,7 +33,7 @@
export default {
props: {
value: String,
modelValue: String,
id: String,
placeholder: String,
name: String,
@ -70,12 +70,12 @@
},
created() {
this.rawValue = this.value;
this.rawValue = this.modelValue;
},
watch: {
value(newValue) {
modelValue(newValue) {
this.rawValue = newValue;
}
},
@ -156,7 +156,7 @@
this.rawValue = newValue;
this.$emit("input", newValue);
this.$emit("update:modelValue", newValue);
if (newValue.length >= Math.max(1, this.minLength)) {
this.debouncedUpdateOptions(newValue);
@ -192,7 +192,7 @@
selectOption(opt) {
this.rawValue = this.optionValue(opt);
this.$emit("input", this.rawValue);
this.$emit("update:modelValue", this.rawValue);
this.$emit("optionSelected", opt);
this.isListOpen = false;
},

View File

@ -8,7 +8,7 @@
export default {
props: {
value: {
modelValue: {
required: false,
type: [Date, String]
},
@ -22,7 +22,7 @@
computed: {
stringValue() {
const d = DateTimeUtils.toDate(this.value);
const d = DateTimeUtils.toDate(this.modelValue);
return DateTimeUtils.formatDateForEdit(d);
}
},
@ -30,7 +30,7 @@
methods: {
input(val) {
let d = DateTimeUtils.toDate(val + " 00:00");
this.$emit("input", d);
this.$emit("update:modelValue", d);
}
}
}

View File

@ -132,7 +132,13 @@
created() {
if (LOADED_APIS[this.svgName] !== true) {
for (let sb of this.svgData.scriptBlocks) {
new Function(sb)(window);
try {
new Function(sb)(window);
} catch (e) {
console.log(sb);
console.log(e);
}
}
LOADED_APIS[this.svgName] = true;
}

View File

@ -1,6 +1,6 @@
<template>
<div ref="container">
<div ref="modal" :class="['popup', 'modal', { 'is-wide': wide, 'is-active': open && error === null }]">
<Teleport to="body">
<div :class="['popup', 'modal', { 'is-wide': wide, 'is-active': open && error === null }]">
<div class="modal-background" @click="close"></div>
<div class="modal-card">
<header class="modal-card-head">
@ -15,14 +15,17 @@
</section>
</div>
</div>
</div>
</Teleport>
</template>
<script>
import { mapState } from "vuex";
import { mapState } from "pinia";
import { useAppConfigStore } from "../stores/appConfig";
export default {
emits: ["dismiss"],
props: {
open: {
type: Boolean,
@ -35,16 +38,8 @@
}
},
mounted() {
this.$root.$el.appendChild(this.$refs.modal);
},
beforeDestroy() {
this.$refs.container.appendChild(this.$refs.modal);
},
computed: {
...mapState([
...mapState(useAppConfigStore, [
'error'
])
},

View File

@ -54,7 +54,8 @@
<script>
import UserLogin from "./UserLogin";
import { mapState } from "vuex";
import { mapState } from "pinia";
import { useAppConfigStore } from "../stores/appConfig";
import { swUpdate } from "../lib/ServiceWorker";
export default {
@ -65,7 +66,7 @@
},
computed: {
...mapState([
...mapState(useAppConfigStore, [
'route',
'user',
'updateAvailable'

View File

@ -32,7 +32,7 @@
default: 0.5
},
value: {
modelValue: {
required: false,
type: Number,
default: 0
@ -47,7 +47,7 @@
computed: {
ratingPercent() {
return ((this.value || 0) / this.starCount) * 100.0;
return ((this.modelValue || 0) / this.starCount) * 100.0;
},
temporaryPercent() {
@ -69,7 +69,7 @@
methods: {
handleClick(evt) {
if (this.temporaryValue !== null) {
this.$emit("input", this.temporaryValue);
this.$emit("update:modelValue", this.temporaryValue);
}
},

View File

@ -18,7 +18,7 @@ export default {
default: ""
},
value: {
modelValue: {
required: false,
type: String,
default: ""
@ -37,7 +37,7 @@ export default {
methods: {
triggerInput: debounce(function() {
this.$emit("input", this.text);
this.$emit("update:modelValue", this.text);
}, 250, {leading: false, trailing: true}),
userUpdateText(text) {
@ -55,7 +55,7 @@ export default {
},
created() {
this.$watch("value",
this.$watch("modelValue",
val => this.propUpdateText(val),
{
immediate: true

View File

@ -2,7 +2,7 @@
<div class="tag-editor control">
<input ref="input" type="text" class="input" :value="tagText" @input="inputHandler" @focus="getFocus" @blur="loseFocus">
<div class="tags">
<span v-for="t in value" :key="t" class="tag">{{t}}</span>
<span v-for="t in modelValue" :key="t" class="tag">{{t}}</span>
</div>
</div>
</template>
@ -11,7 +11,7 @@
export default {
props: {
value: {
modelValue: {
required: true,
type: Array
}
@ -25,7 +25,7 @@
computed: {
tagText() {
return this.value.join(" ");
return this.modelValue.join(" ");
}
},
@ -54,8 +54,8 @@
const newTags = [...new Set(str.toString().split(/\s+/).filter(t => t.length > 0))];
if (!this.arraysEqual(newTags, this.value)) {
this.$emit("input", newTags);
if (!this.arraysEqual(newTags, this.modelValue)) {
this.$emit("update:modelValue", newTags);
}
},

View File

@ -2,8 +2,8 @@
<div class="field">
<label v-if="label.length" class="label is-small-mobile">{{ label }}</label>
<div :class="controlClasses">
<textarea v-if="isTextarea" :class="inputClasses" :value="value" @input="input" :disabled="disabled"></textarea>
<input v-else :type="type" :class="inputClasses" :value="value" @input="input" :disabled="disabled">
<textarea v-if="isTextarea" :class="inputClasses" :value="modelValue" @input="input" :disabled="disabled"></textarea>
<input v-else :type="type" :class="inputClasses" :value="modelValue" @input="input" :disabled="disabled">
<app-icon class="is-right" icon="warning" v-if="validationError !== null"></app-icon>
</div>
<p v-if="helpMessage !== null" :class="helpClasses">
@ -21,7 +21,7 @@
type: String,
default: ""
},
value: {
modelValue: {
required: false,
type: [String, Number],
default: ""
@ -83,7 +83,7 @@
methods: {
input(evt) {
this.$emit("input", evt.target.value);
this.$emit("update:modelValue", evt.target.value);
}
}
}

View File

@ -62,11 +62,14 @@
<button class="button" type="button" @click="addUnit">Add Unit</button>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Grams</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="unit in visibleFoodUnits" :key="unit.id">
<td>
<div class="control">
@ -82,6 +85,7 @@
<button type="button" class="button is-danger" @click="removeUnit(unit)">X</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
@ -96,14 +100,18 @@
<div class="message-body">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Grams</th>
</tr>
</thead>
<tbody>
<tr v-for="unit in food.ndbn_units">
<td>{{unit.description}}</td>
<td>{{unit.gram_weight}}</td>
</tr>
</tbody>
</table>
</div>
@ -139,7 +147,8 @@
<script>
import api from "../lib/Api";
import { mapState } from "vuex";
import { mapState } from "pinia";
import { useNutrientStore } from "../stores/nutrient";
export default {
props: {
@ -166,7 +175,7 @@
},
computed: {
...mapState({
...mapState(useNutrientStore, {
nutrients: 'nutrientList'
}),

View File

@ -55,7 +55,8 @@
<script>
import { mapState } from "vuex";
import { mapState } from "pinia";
import { useNutrientStore } from "../stores/nutrient";
export default {
props: {
@ -66,7 +67,7 @@
},
computed: {
...mapState({
...mapState(useNutrientStore, {
nutrients: 'nutrientList'
}),
}

View File

@ -71,6 +71,7 @@
<th>Result</th>
</tr>
</thead>
<tbody>
<tr>
<td>Heading</td>
<td>
@ -155,6 +156,7 @@ _underline_
</p>
</td>
</tr>
</tbody>
</table>
<h3 class="title is-3">Basic Example</h3>
@ -206,7 +208,7 @@ _underline_
<script>
import autosize from "autosize";
//import autosize from "autosize";
import debounce from "lodash/debounce";
import api from "../lib/Api";

View File

@ -12,18 +12,22 @@
</div>
<div class="column is-half">
<table class="table is-bordered is-narrow is-size-7">
<thead>
<tr>
<th>#</th>
<th>Unit</th>
<th>Name</th>
<th>Prep</th>
</tr>
</thead>
<tbody>
<tr v-for="i in bulkIngredientPreview">
<td>{{i.quantity}}</td>
<td>{{i.units}}</td>
<td>{{i.name}}</td>
<td>{{i.preparation}}</td>
</tr>
</tbody>
</table>
</div>
</div>
@ -43,7 +47,8 @@
import RecipeEditIngredientItem from "./RecipeEditIngredientItem";
import { mapState } from "vuex";
import { mapState } from "pinia";
import { useMediaQueryStore } from "../stores/mediaQuery";
export default {
props: {
@ -61,8 +66,8 @@
},
computed: {
...mapState({
isMobile: state => state.mediaQueries.mobile
...mapState(useMediaQueryStore, {
isMobile: store => store.mobile
}),
bulkIngredientPreview() {
if (this.bulkEditText === null) {

View File

@ -65,14 +65,18 @@
<div class="message-header" @click="showNutrition = !showNutrition">Nutrition Data</div>
<div class="message-body" v-show="showNutrition">
<table class="table">
<thead>
<tr>
<th>Item</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr v-for="nutrient in recipe.nutrition_data.nutrients" :key="nutrient.name">
<td>{{nutrient.label}}</td>
<td>{{ roundValue(nutrient.value) }}</td>
</tr>
</tbody>
</table>
<h3 class="title is-5">Nutrition Calculation Warnings</h3>
@ -149,7 +153,8 @@
<script>
import api from "../lib/Api";
import { mapActions, mapMutations, mapState } from "vuex";
import { mapActions, mapState } from "pinia";
import { useTaskStore } from "../stores/task";
export default {
props: {
@ -185,7 +190,7 @@
},
computed: {
...mapState([
...mapState(useTaskStore, [
'taskLists'
]),
@ -235,11 +240,8 @@
},
methods: {
...mapActions([
'ensureTaskLists'
]),
...mapMutations([
...mapActions(useTaskStore, [
'ensureTaskLists',
'setCurrentTaskList'
]),

View File

@ -63,8 +63,8 @@
<script>
import * as Errors from '../lib/Errors';
import { mapActions } from "vuex";
import cloneDeep from "lodash/cloneDeep";
import { mapActions } from "pinia";
import { useTaskStore } from "../stores/task";
import TaskItemEdit from "./TaskItemEdit";
@ -115,7 +115,7 @@
},
methods: {
...mapActions([
...mapActions(useTaskStore, [
'createTaskItem',
'updateTaskItem',
'deleteTaskItems',

View File

@ -53,19 +53,20 @@
<style lang="scss" scoped>
@import "~styles/variables";
@use "bulma/sass" as bulma;
@use 'sass:color';
div.dropdown-item {
cursor: pointer;
&.hovered {
color: $black;
background-color: $background;
color: bulma.$black;
background-color: bulma.$background;
}
&.is-active {
color: $link-invert;
background-color: $link;
color: color.invert(bulma.$link);
background-color: bulma.$link;
}
}

View File

@ -7,7 +7,7 @@
</p>
<p>
Parsley is released under the MIT License. All code &copy; Dan Elbert 2020.
Parsley is released under the MIT License. All code &copy; Dan Elbert 2024.
</p>
<p>

View File

@ -15,7 +15,6 @@
<script>
import FoodShow from "./FoodShow";
import { mapState } from "vuex";
import api from "../lib/Api";
export default {
@ -26,9 +25,9 @@
},
computed: {
...mapState({
foodId: state => state.route.params.id,
})
foodId() {
return this.$route.params.id;
}
},
created() {

View File

@ -12,7 +12,6 @@
<script>
import FoodEdit from "./FoodEdit";
import { mapState } from "vuex";
import api from "../lib/Api";
import * as Errors from '../lib/Errors';

View File

@ -17,7 +17,6 @@
<script>
import FoodEdit from "./FoodEdit";
import { mapState } from "vuex";
import api from "../lib/Api";
import * as Errors from '../lib/Errors';
@ -30,9 +29,9 @@
},
computed: {
...mapState({
foodId: state => state.route.params.id,
})
foodId() {
return this.$route.params.id;
}
},
methods: {

View File

@ -17,7 +17,6 @@
<script>
import LogShow from "./LogShow";
import { mapState } from "vuex";
import api from "../lib/Api";
export default {
@ -28,9 +27,9 @@
},
computed: {
...mapState({
logId: state => state.route.params.id,
})
logId() {
return this.$route.params.id;
}
},
created() {

View File

@ -19,7 +19,6 @@
<script>
import LogEdit from "./LogEdit";
import { mapState } from "vuex";
import api from "../lib/Api";
import * as Errors from '../lib/Errors';
@ -37,9 +36,9 @@
},
computed: {
...mapState({
recipeId: state => state.route.params.recipeId,
})
recipeId() {
return this.$route.params.recipeId;
}
},
methods: {

View File

@ -18,7 +18,6 @@
<script>
import { mapState } from "vuex";
import api from "../lib/Api";
import * as Errors from "../lib/Errors";
import LogEdit from "./LogEdit";
@ -32,9 +31,9 @@
},
computed: {
...mapState({
logId: state => state.route.params.id,
})
logId() {
return this.$route.params.id;
}
},
methods: {

View File

@ -5,19 +5,23 @@
</h1>
<table class="table">
<thead>
<tr>
<th>Recipe</th>
<th>Date</th>
<th>Rating</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr v-for="l in logs" :key="l.id">
<td> <router-link :to="{name: 'log', params: {id: l.id}}">{{l.recipe.name}}</router-link></td>
<td><app-date-time :date-time="l.date" :show-time="false"></app-date-time> </td>
<td><app-rating :value="l.rating" readonly></app-rating></td>
<td>{{l.notes}}</td>
</tr>
</tbody>
</table>
</div>

View File

@ -9,12 +9,15 @@
</app-modal>
<table class="table">
<thead>
<tr>
<th>Note</th>
<th>Date</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="n in notes" :key="n.id">
<td>
{{ n.content }}
@ -28,6 +31,7 @@
</button>
</td>
</tr>
</tbody>
</table>
</div>

View File

@ -25,7 +25,6 @@
<script>
import RecipeShow from "./RecipeShow";
import { mapState } from "vuex";
import api from "../lib/Api";
export default {
@ -37,13 +36,11 @@
},
computed: {
...mapState({
recipeId: state => state.route.params.id,
routeQuery: state => state.route.query,
scale: state => state.route.query.scale || null,
system: state => state.route.query.system || null,
unit: state => state.route.query.unit || null
}),
recipeId() { return this.$route.params.id },
routeQuery() { return this.$route.query },
scale() { return this.$route.query.scale || null },
system() { return this.$route.query.system || null },
unit() { return this.$route.query.unit || null },
isScaled() {
return this.recipe.converted_scale !== null && this.recipe.converted_scale.length > 0 && this.recipe.converted_scale !== "1";

View File

@ -21,7 +21,6 @@
<script>
import RecipeEdit from "./RecipeEdit";
import { mapState } from "vuex";
import api from "../lib/Api";
import * as Errors from '../lib/Errors';
@ -34,9 +33,9 @@
},
computed: {
...mapState({
recipeId: state => state.route.params.id,
})
recipeId() {
return this.$route.params.id;
}
},
methods: {

View File

@ -11,7 +11,7 @@
<table class="table is-fullwidth" :class="{ small: mediaQueries.touch }">
<table class="table is-fullwidth" :class="{ small: isTouch }">
<thead>
<tr>
<th v-for="h in tableHeader" :key="h.name">
@ -91,9 +91,10 @@
<script>
import api from "../lib/Api";
import debounce from "lodash/debounce";
import { mapMutations, mapState } from "vuex";
import { mapWritableState, mapState } from "pinia";
import AppLoading from "./AppLoading";
import { useAppConfigStore } from "../stores/appConfig";
import { useMediaQueryStore } from "../stores/mediaQuery";
export default {
props: {
@ -112,8 +113,9 @@
},
computed: {
...mapState([
"mediaQueries"
...mapState(useMediaQueryStore, { isTouch: store => store.touch }),
...mapWritableState(useAppConfigStore, [
"initialLoad"
]),
search() {
@ -174,10 +176,6 @@
},
methods: {
...mapMutations([
"setInitialLoad"
]),
buildQueryParams() {
return {
name: this.searchQuery.name,
@ -302,7 +300,7 @@
created() {
this.$watch("search",
() => {
this.getList().then(() => this.setInitialLoad(true));
this.getList().then(() => this.initialLoad = true);
},
{
deep: true,

View File

@ -24,9 +24,9 @@
<script>
import api from "../lib/Api";
import * as Errors from '../lib/Errors';
import { mapActions, mapMutations, mapState } from "vuex";
import { mapActions, mapState } from "pinia";
import { useTaskStore } from "../stores/task";
import TaskListMiniForm from "./TaskListMiniForm";
import TaskListDropdownItem from "./TaskListDropdownItem";
@ -48,7 +48,7 @@
},
computed: {
...mapState([
...mapState(useTaskStore, [
'taskLists',
'currentTaskList'
]),
@ -62,14 +62,12 @@
},
methods: {
...mapActions([
...mapActions(useTaskStore, [
'refreshTaskLists',
'createTaskList',
'deleteTaskList',
'deleteTaskItems',
'completeTaskItems'
]),
...mapMutations([
'completeTaskItems',
'setCurrentTaskList'
]),

View File

@ -34,7 +34,7 @@
<button type="submit" class="button is-primary" :disabled="!enableSubmit">Login</button>
</div>
<div class="control">
<button class="button is-secondary" @click="showLogin = false">Cancel</button>
<button type="button" class="button is-secondary" @click="showLogin = false">Cancel</button>
</div>
</div>
@ -47,8 +47,8 @@
<script>
import api from "../lib/Api";
import { mapActions, mapState } from "vuex";
import { mapActions, mapState } from "pinia";
import { useAppConfigStore } from "../stores/appConfig";
export default {
data() {
@ -61,7 +61,7 @@
},
computed: {
...mapState([
...mapState(useAppConfigStore, [
'loginMessage'
]),
enableSubmit() {
@ -70,7 +70,7 @@
},
methods: {
...mapActions([
...mapActions(useAppConfigStore, [
'login'
]),
@ -86,6 +86,7 @@
this.loadResource(
this.login(params)
.then(data => {
console.log(data);
if (data.success) {
this.showLogin = false;
}

View File

@ -1,54 +1,6 @@
import Vue from 'vue';
import { mapActions, mapGetters, mapMutations, mapState } from 'vuex';
import api from "../lib/Api";
Vue.mixin({
data() {
return {
localLoadingCount: 0
};
},
computed: {
...mapGetters([
"isLoading",
"isLoggedIn",
"isAdmin"
]),
...mapState([
"user"
]),
localLoading() {
return this.localLoadingCount > 0;
}
},
methods: {
...mapActions([
'updateCurrentUser'
]),
...mapMutations([
'setError',
'setLoading'
]),
loadResource(promise) {
this.setLoading(true);
this.localLoadingCount = this.localLoadingCount + 1;
return promise
.catch(err => this.setError(err))
.then(res => {
this.setLoading(false);
this.localLoadingCount = this.localLoadingCount - 1;
return res;
});
},
checkAuthentication() {
return this.loadResource(this.updateCurrentUser());
}
}
});
import { mapActions, mapState } from "pinia";
import { useAppConfigStore } from "../stores/appConfig";
function clickStrikeClick(evt) {
const isStrikable = el => el && el.tagName === "LI";
@ -73,12 +25,59 @@ function clickStrikeClick(evt) {
}
}
Vue.directive('click-strike', {
bind(el) {
el.addEventListener("click", clickStrikeClick);
},
export function installMixins(app) {
app.directive('click-strike', {
beforeMount(el) {
el.addEventListener("click", clickStrikeClick);
},
unbind(el) {
el.removeEventListener("click", clickStrikeClick);
}
});
unmounted(el) {
el.removeEventListener("click", clickStrikeClick);
}
});
app.mixin({
data() {
return {
localLoadingCount: 0
};
},
computed: {
...mapState(useAppConfigStore, [
"isLoading",
"isLoggedIn",
"isAdmin",
"user"
]),
localLoading() {
return this.localLoadingCount > 0;
}
},
methods: {
...mapActions(useAppConfigStore, [
'setError',
'setLoading',
'updateCurrentUser'
]),
loadResource(promise) {
this.setLoading(true);
this.localLoadingCount = this.localLoadingCount + 1;
return promise
.catch(err => this.setError(err))
.then(res => {
this.setLoading(false);
this.localLoadingCount = this.localLoadingCount - 1;
return res;
});
},
checkAuthentication() {
return this.loadResource(this.updateCurrentUser());
}
}
});
}

View File

@ -1,4 +1,6 @@
import { useAppConfigStore } from '../stores/appConfig';
function trackInstall(worker, cb) {
worker.addEventListener('statechange', function() {
if (worker.state == 'installed') {
@ -18,7 +20,9 @@ function trackActive(worker, cb) {
export function swUpdate() {
navigator.serviceWorker.getRegistration().then(reg => {
if (reg && reg.waiting) {
trackActive(reg.waiting, () => window.location.reload(true));
trackActive(reg.waiting, () => {
window.location.reload(true)
});
reg.waiting.postMessage("skipWaiting");
} else {
window.location.reload(true);
@ -27,10 +31,17 @@ export function swUpdate() {
}
export function swInit(store) {
export function swInit() {
const updateReady = () => store.commit("setUpdateAvailable", true);
const clearUpdateReady = () => store.commit("setUpdateAvailable", false);
const updateReady = () => {
const store = useAppConfigStore();
store.updateAvailable = true;
};
const clearUpdateReady = () => {
const store = useAppConfigStore();
store.updateAvailable = false;
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
@ -53,6 +64,4 @@ export function swInit(store) {
console.log('Registration failed with ' + error);
});
}
}

View File

@ -0,0 +1,54 @@
import '../styles';
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import { swInit } from "../lib/ServiceWorker";
import config from '../lib/config';
import { installMixins } from "../lib/GlobalMixins";
import router from '../router';
import AppAutocomplete from "../components/AppAutocomplete";
import AppConfirm from "../components/AppConfirm";
import AppDateTime from "../components/AppDateTime";
import AppDatePicker from "../components/AppDatePicker";
import AppDropdown from "../components/AppDropdown";
import AppExpandTransition from "../components/AppExpandTransition";
import AppIcon from "../components/AppIcon";
import AppIconicIcon from "../components/AppIconicIcon";
import AppModal from "../components/AppModal";
import AppNavbar from "../components/AppNavbar";
import AppPager from "../components/AppPager";
import AppRating from "../components/AppRating";
import AppSearchText from "../components/AppSearchText";
import AppTagEditor from "../components/AppTagEditor";
import AppTextField from "../components/AppTextField";
import AppValidationErrors from "../components/AppValidationErrors";
import App from '../components/App.vue'
document.addEventListener('DOMContentLoaded', () => {
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.use(router);
swInit();
installMixins(app);
app.component("AppAutocomplete", AppAutocomplete);
app.component("AppConfirm", AppConfirm);
app.component("AppDateTime", AppDateTime);
app.component("AppDatePicker", AppDatePicker);
app.component("AppDropdown", AppDropdown);
app.component("AppExpandTransition", AppExpandTransition);
app.component("AppIcon", AppIcon);
app.component("AppIconicIcon", AppIconicIcon);
app.component("AppModal", AppModal);
app.component("AppNavbar", AppNavbar);
app.component("AppPager", AppPager);
app.component("AppRating", AppRating);
app.component("AppSearchText", AppSearchText);
app.component("AppTagEditor", AppTagEditor);
app.component("AppTextField", AppTextField);
app.component("AppValidationErrors", AppValidationErrors);
app.mount('#app');
});

View File

@ -1,5 +1,5 @@
import Vue from 'vue';
import Router from 'vue-router';
import { createRouter, createWebHashHistory } from "vue-router";
import { nextTick } from "vue";
import The404Page from '../components/The404Page';
import TheAboutPage from '../components/TheAboutPage';
@ -28,26 +28,12 @@ import TheUserEditor from '../components/TheUserEditor';
import TheAdminUserList from '../components/TheAdminUserList';
import TheAdminUserEditor from '../components/TheAdminUserEditor';
import $store from '../store';
import { useAppConfigStore } from "../stores/appConfig";
Vue.use(Router);
const router = new Router({
routes: []
});
router.afterEach((to, from) => {
if (to.meta.handleInitialLoad !== true && $store.state.initialLoad === false) {
$store.commit("setInitialLoad", true);
}
Vue.nextTick(() => {
document.title = to.meta.title || 'Parsley';
});
});
router.addRoutes(
[
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
redirect: '/recipes'
@ -148,10 +134,10 @@ router.addRoutes(
{
path: "/logout",
name: "logout",
beforeEnter: (to, from, next) => {
const $store = router.app.$store;
$store.dispatch("logout")
.then(() => next("/"));
beforeEnter: async (to, from, next) => {
const appConfig = useAppConfigStore();
await appConfig.logout();
return next("/");
}
},
@ -179,10 +165,22 @@ router.addRoutes(
},
{
path: '*',
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: The404Page
}
]
);
});
router.afterEach((to, from) => {
const appConfigStore = useAppConfigStore();
if (to.meta.handleInitialLoad !== true && appConfigStore.initialLoad === false) {
appConfigStore.initialLoad = true;
}
nextTick(() => {
document.title = to.meta.title || 'Parsley';
});
});
export default router;

View File

@ -0,0 +1,102 @@
import { defineStore } from 'pinia';
import { computed, ref } from 'vue';
import { useRoute } from "vue-router";
import api from "../lib/Api";
export const useAppConfigStore = defineStore('appConfig', () => {
const authChecked = ref(false);
const errorObject = ref(null);
const initialLoad = ref(false);
const loadingCount = ref(0);
const loginMessage = ref(null);
const updateAvailable = ref(false);
const userObject = ref(null);
const route = useRoute();
const isLoading = computed(() => {
return loadingCount.value > 0;
});
const isLoggedIn = computed(() => {
return userObject.value !== null;
});
const isAdmin = computed(() => {
return userObject.value?.admin === true;
});
const error = computed({
get: () => errorObject.value,
set: (val) => {
console.log(val);
errorObject.value = val;
}
});
const user = computed({
get: () => userObject.value,
set: (val) => {
userObject.value = val;
authChecked.value = true;
}
});
function setError(value) {
error.value = value;
}
function setLoading(value) {
if (value) {
loadingCount.value = loadingCount.value + 1;
} else {
loadingCount.value = loadingCount.value - 1;
}
}
async function updateCurrentUser() {
user.value = await api.getCurrentUser();
return user.value;
}
function login(authData) {
return api.postLogin(authData.username, authData.password)
.then(data => {
if (data.success) {
user.value = data.user;
loginMessage.value = null;
} else {
user.value = null;
loginMessage.value = data.message;
}
return data;
});
}
function logout() {
return api.getLogout()
.then(() => {
user.value = null;
});
}
return {
authChecked,
error,
initialLoad,
loadingCount,
loginMessage,
updateAvailable,
user,
route,
isAdmin,
isLoading,
isLoggedIn,
login,
logout,
setError,
setLoading,
updateCurrentUser
};
});

View File

@ -0,0 +1,35 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useMediaQueryStore = defineStore('mediaQuery', () => {
// Hard coded values taken directly from Bulma css
const mobileBp = 768;
const desktopBp = 1024;
const widscreenBp = 1216;
const fullHdBp = 1408;
const mediaQueries = {
mobile: `screen and (max-width: ${mobileBp}px)`,
tablet: `screen and (min-width: ${mobileBp + 1}px)`,
tabletOnly: `screen and (min-width: ${mobileBp + 1}px) and (max-width: ${desktopBp - 1}px)`,
touch: `screen and (max-width: ${desktopBp - 1}px)`,
desktop: `screen and (min-width: ${desktopBp}px)`,
desktopOnly: `screen and (min-width: ${desktopBp}px) and (max-width: ${widscreenBp - 1}px)`,
widescreen: `screen and (min-width: ${widscreenBp}px)`,
widescreenOnly: `screen and (min-width: ${widscreenBp}px) and (max-width: ${fullHdBp - 1}px)`,
fullhd: `screen and (min-width: ${fullHdBp}px)`
};
const store = {};
for (let device in mediaQueries) {
const query = window.matchMedia(mediaQueries[device]);
store[device] = ref(query.matches);
query.onchange = (q) => {
store[device].value = q.matches;
};
}
return store;
});

View File

@ -0,0 +1,36 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useNutrientStore = defineStore('nutrient', () => {
const nutrientList = ref({
kcal: { label: "Calories", unit: "kcal" },
protein: { label: "Protein", unit: "g" },
lipids: { label: "Fat", unit: "g" },
carbohydrates: { label: "Carbohydrates", unit: "g" },
water: { label: "Water", unit: "g" },
sugar: { label: "Sugar", unit: "g" },
fiber: { label: "Fiber", unit: "g" },
cholesterol: { label: "Cholesterol", unit: "mg" },
sodium: { label: "Sodium", unit: "mg" },
calcium: { label: "Calcium", unit: "mg" },
iron: { label: "Iron", unit: "mg" },
magnesium: { label: "Magnesium", unit: "mg" },
phosphorus: { label: "Phosphorus", unit: "mg" },
potassium: { label: "Potassium", unit: "mg" },
zinc: { label: "Zinc", unit: "mg" },
copper: { label: "Copper", unit: "mg" },
manganese: { label: "Manganese", unit: "mg" },
vit_a: { label: "Vitamin A", unit: "μg" },
vit_b6: { label: "Vitamin B6", unit: "mg" },
vit_b12: { label: "Vitamin B12", unit: "μg" },
vit_c: { label: "Vitamin C", unit: "mg" },
vit_d: { label: "Vitamin D", unit: "μg" },
vit_e: { label: "Vitamin E", unit: "mg" },
vit_k: { label: "Vitamin K", unit: "μg" },
ash: { label: "ash", unit: "g" }
});
return {
nutrientList
};
});

View File

@ -0,0 +1,114 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { useAppConfigStore } from "./appConfig";
import api from "../lib/Api";
import { createChannel } from "../lib/ActionCable";
export const useTaskStore = defineStore('task', () => {
const taskLists = ref([]);
const currentTaskList = ref(null);
let taskChannel = null;
function replaceTaskList(list) {
if (taskLists.value) {
const listIdx = taskLists.value.findIndex(l => l.id === list.id);
if (listIdx >= 0) {
taskLists.value.splice(listIdx, 1, list);
}
if (currentTaskList.value && currentTaskList.value.id === list.id) {
currentTaskList.value = list;
}
}
}
function ensureTaskListChannel() {
if (taskChannel === null) {
taskChannel = createChannel(null, "TaskChannel", {
received(data) {
if (data && data.action === 'updated') {
replaceTaskList(data.task_list);
}
}
});
}
}
function refreshTaskLists() {
const cb = function(data) {
taskLists.value = data || [];
let ctl = null;
if (currentTaskList.value) {
ctl = data.find(l => l.id === currentTaskList.value.id);
}
ctl = ctl || data[0] || null;
setCurrentTaskList(ctl);
ensureTaskListChannel();
};
return api.getTaskLists(cb)
}
function ensureTaskLists() {
const appConfig = useAppConfigStore();
if (appConfig.user && taskLists.value.length === 0) {
return refreshTaskLists();
} else {
return Promise.resolve();
}
}
function setCurrentTaskList(taskList) {
currentTaskList.value = taskList || null;
}
async function createTaskList(newList) {
currentTaskList.value = await api.postTaskList(newList);
return refreshTaskLists();
}
async function deleteTaskList(taskList) {
await api.deleteTaskList(taskList);
return refreshTaskLists();
}
function createTaskItem(taskItem) {
return api.postTaskItem(taskItem.task_list_id, taskItem)
.then(data => {
return data;
});
}
function updateTaskItem(taskItem) {
return api.patchTaskItem(taskItem.task_list_id, taskItem)
.then(data => {
return data;
});
}
function deleteTaskItems(payload) {
return api.deleteTaskItems(payload.taskList.id, payload.taskItems);
}
function completeTaskItems(payload) {
return api.completeTaskItems(payload.taskList.id, payload.taskItems, !payload.completed);
}
return {
currentTaskList,
taskLists,
createTaskList,
deleteTaskList,
ensureTaskLists,
refreshTaskLists,
setCurrentTaskList,
createTaskItem,
updateTaskItem,
deleteTaskItems,
completeTaskItems
};
});

View File

@ -1,21 +1,25 @@
@import "./variables";
// coolors.co pallet
$coolors-dark: rgba(29, 30, 24, 1);
$coolors-blue: rgba(67, 127, 151, 1);
$coolors-green: rgba(121, 167, 54, 1);
$coolors-red: #ab4c34;
$coolors-yellow: rgba(240, 162, 2, 1);
@import "~bulma/sass/utilities/_all";
@import "~bulma/sass/base/_all";
@import "~bulma/sass/components/dropdown";
@import "~bulma/sass/components/navbar";
@import "~bulma/sass/components/level";
@import "~bulma/sass/components/message";
@import "~bulma/sass/components/modal";
@import "~bulma/sass/components/pagination";
@import "~bulma/sass/components/panel";
@import "~bulma/sass/elements/_all";
@import "~bulma/sass/form/_all";
@import "~bulma/sass/grid/columns";
@import "~bulma/sass/layout/section";
$family-serif: Georgia, "Times New Roman", Times, serif;
@import "./responsive_controls";
@import "./wide_modal";
@use "bulma/sass" as bulma with (
$family-primary: $family-serif,
$green: $coolors-green,
$blue: $coolors-blue,
$red: $coolors-red,
$yellow: $coolors-yellow,
$dark: $coolors-dark,
$primary: $coolors-green,
$modal-content-width: 750px
);
// @import "./responsive_controls";
// @import "./wide_modal";
@import "./iconic";
@import "./transitions";
@ -28,7 +32,7 @@ body {
}
body {
background-color: $grey-dark;
background-color: bulma.$grey-dark;
padding-bottom: 2rem;
}
@ -37,13 +41,13 @@ body {
.container {
padding: 1rem;
background-color: $white;
background-color: bulma.$white;
min-height: 75vh;
}
}
.title, .subtitle, .navbar, .button, .pagination, .modal-card-title, th {
font-family: $family-sans-serif;
font-family: bulma.$family-sans-serif;
}
.pagination:not(:last-child) {

View File

@ -1,7 +1,7 @@
<%
manifest_data = Webpacker::manifest.refresh
manifest_timestamp = File.mtime(Webpacker::config.public_manifest_path).to_i
manifest_data = Shakapacker::manifest.refresh
manifest_timestamp = File.mtime(Shakapacker::config.public_manifest_path).to_i
pack_assets = manifest_data.select { |name, asset| name !~ /\.map$/ && name != 'entrypoints' }.values
%>

View File

@ -17,6 +17,9 @@ FileUtils.chdir APP_ROOT do
system! "gem install bundler --conservative"
system("bundle check") || system!("bundle install")
# Install JavaScript dependencies
system!("yarn install --no-immutable")
# puts "\n== Copying sample files =="
# unless File.exist?("config/database.yml")
# FileUtils.cp "config/database.yml.sample", "config/database.yml"

View File

@ -4,10 +4,10 @@ ENV["RAILS_ENV"] ||= "development"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
require "bundler/setup"
require "webpacker"
require "webpacker/webpack_runner"
require "shakapacker"
require "shakapacker/webpack_runner"
APP_ROOT = File.expand_path("..", __dir__)
Dir.chdir(APP_ROOT) do
Webpacker::WebpackRunner.run(ARGV)
Shakapacker::WebpackRunner.run(ARGV)
end

View File

@ -4,10 +4,10 @@ ENV["RAILS_ENV"] ||= "development"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
require "bundler/setup"
require "webpacker"
require "webpacker/dev_server_runner"
require "shakapacker"
require "shakapacker/dev_server_runner"
APP_ROOT = File.expand_path("..", __dir__)
Dir.chdir(APP_ROOT) do
Webpacker::DevServerRunner.run(ARGV)
Shakapacker::DevServerRunner.run(ARGV)
end

View File

@ -1,19 +0,0 @@
#!/usr/bin/env ruby
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
ENV["NODE_ENV"] ||= "development"
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
require "rubygems"
require "bundler/setup"
require "webpacker"
require "webpacker/webpack_runner"
APP_ROOT = File.expand_path("..", __dir__)
Dir.chdir(APP_ROOT) do
Webpacker::WebpackRunner.run(ARGV)
end

View File

@ -1,19 +0,0 @@
#!/usr/bin/env ruby
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
ENV["NODE_ENV"] ||= "development"
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
require "rubygems"
require "bundler/setup"
require "webpacker"
require "webpacker/dev_server_runner"
APP_ROOT = File.expand_path("..", __dir__)
Dir.chdir(APP_ROOT) do
Webpacker::DevServerRunner.run(ARGV)
end

View File

@ -1,18 +0,0 @@
#!/usr/bin/env ruby
APP_ROOT = File.expand_path("..", __dir__)
Dir.chdir(APP_ROOT) do
yarn = ENV["PATH"].split(File::PATH_SEPARATOR).
select { |dir| File.expand_path(dir) != __dir__ }.
product(["yarn", "yarnpkg", "yarn.cmd", "yarn.ps1"]).
map { |dir, file| File.expand_path(file, dir) }.
find { |file| File.executable?(file) }
if yarn
exec yarn, *ARGV
else
$stderr.puts "Yarn executable was not detected in the system."
$stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
exit 1
end
end

View File

@ -1,15 +1,17 @@
# Note: You must restart bin/webpacker-dev-server for changes to take effect
# Note: You must restart bin/shakapacker-dev-server for changes to take effect
# This file contains the defaults used by shakapacker.
default: &default
source_path: app/javascript
# You can have a subdirectory of the source_path, like 'packs' (recommended).
# Alternatively, you can use '/' to use the whole source_path directory.
source_entry_path: /
# Notice that this is a relative path to source_path
source_entry_path: packs
# If nested_entries is true, then we'll pick up subdirectories within the source_entry_path.
# You cannot set this option to true if you set source_entry_path to '/'
nested_entries: false
nested_entries: true
# While using a File-System-based automated bundle generation feature, miscellaneous warnings suggesting css order
# conflicts may arise due to the mini-css-extract-plugin. For projects where css ordering has been mitigated through
@ -19,10 +21,10 @@ default: &default
public_root_path: public
public_output_path: packs
cache_path: tmp/webpacker
cache_path: tmp/shakapacker
webpack_compile_output: true
# See https://github.com/shakacode/shakapacker#deployment
webpacker_precompile: true
shakapacker_precompile: true
# Location for manifest.json, defaults to {public_output_path}/manifest.json if unset
# manifest_path: public/packs/manifest.json
@ -35,48 +37,63 @@ default: &default
cache_manifest: false
# Select loader to use, available options are 'babel' (default), 'swc' or 'esbuild'
webpack_loader: 'babel'
webpack_loader: 'swc'
# Set to true to enable check for matching versions of shakapacker gem and NPM package - will raise an error if there is a mismatch or wildcard versioning is used
ensure_consistent_versioning: false
# Raises an error if there is a mismatch in the shakapacker gem and npm package being used
ensure_consistent_versioning: true
# Select whether the compiler will use SHA digest ('digest' option) or most most recent modified timestamp ('mtime') to determine freshness
compiler_strategy: digest
# Select whether the compiler will always use a content hash and not just in production
# Don't use contentHash except for production for performance
# https://webpack.js.org/guides/build-performance/#avoid-production-specific-tooling
useContentHash: false
# Setting the asset host here will override Rails.application.config.asset_host.
# Here, you can set different asset_host per environment. Note that
# SHAKAPACKER_ASSET_HOST will override both configurations.
# asset_host: custom-path
development:
<<: *default
compile: true
compiler_strategy: mtime
# Reference: https://webpack.js.org/configuration/dev-server/
# Keys not described there are documented inline and in https://github.com/shakacode/shakapacker/
dev_server:
https: false
# For running dev server with https, set `server: https`.
# server: https
host: localhost
port: 3035
# Hot Module Replacement updates modules while the application is running without a full reload
# Used instead of the `hot` key in https://webpack.js.org/configuration/dev-server/#devserverhot
hmr: false
# If HMR is on, CSS will by inlined by delivering it as part of the script payload via style-loader. Be sure
# that you add style-loader to your project dependencies.
#
# If you want to instead deliver CSS via <link> with the mini-extract-css-plugin, set inline_css to false.
# If you want to instead deliver CSS via <link> with the mini-css-extract-plugin, set inline_css to false.
# In that case, style-loader is not needed as a dependency.
#
# mini-extract-css-plugin is a required dependency in both cases.
# mini-css-extract-plugin is a required dependency in both cases.
inline_css: true
# Defaults to the inverse of hmr. Uncomment to manually set this.
# live_reload: true
live_reload: false
client:
# Should we show a full-screen overlay in the browser when there are compiler errors or warnings?
overlay: true
# May also be a string
# webSocketURL:
# hostname: "0.0.0.0"
# pathname: "/ws"
# hostname: '0.0.0.0'
# pathname: '/ws'
# port: 8080
# Should we use gzip compression?
compress: true
# Note that apps that do not check the host are vulnerable to DNS rebinding attacks
allowed_hosts: "all"
allowed_hosts: 'auto'
# Shows progress and colorizes output of bin/shakapacker[-dev-server]
pretty: true
headers:
'Access-Control-Allow-Origin': '*'
@ -91,17 +108,14 @@ test:
# Compile test packs to a separate directory
public_output_path: packs-test
production: &production
production:
<<: *default
# Production depends on precompilation of packs prior to booting for performance.
compile: false
# Use content hash for naming assets. Cannot be overridden by for production.
useContentHash: true
# Cache manifest.json for performance
cache_manifest: true
docker:
<<: *production
beta:
<<: *production

View File

@ -13,9 +13,11 @@ const iriElementsAndProperties = {
};
module.exports = function(content) {
this.cacheable && this.cacheable();
const $ = cheerio.load(content, {
xmlMode: true,
lowerCaseTags: false
});
@ -71,6 +73,7 @@ module.exports = function(content) {
const svgMarkup = $("svg").html();
return `module.exports = ${JSON.stringify({
attributes: attributes,
content: svgMarkup,

View File

@ -2,4 +2,4 @@ module.exports = {
resolve: {
extensions: ['.css', '.scss']
}
}
}

View File

@ -6,10 +6,9 @@ module.exports = {
{
test: /\/iconic\/svg\/.*\.svg$/,
use: [
'vue-loader',
path.join(__dirname, '../loaders/iconic_svg_loader.js')
{ loader: path.join(__dirname, '../loaders/iconic_svg_loader.js') }
],
type: 'asset/source'
type: 'javascript/auto'
}
]
},

View File

@ -1,16 +1,25 @@
const { env } = require('shakapacker')
const { DefinePlugin } = require('webpack')
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [new VueLoaderPlugin()],
resolve: {
extensions: ['.vue']
}
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [
new VueLoaderPlugin(),
new DefinePlugin({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: env.isDevelopment,
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: env.isDevelopment
})
],
resolve: {
extensions: ['.vue']
}
}

View File

@ -1,32 +1,34 @@
const { merge, mergeWithRules, webpackConfig } = require('shakapacker')
const cssConfig = require('./rules/css');
const vueConfig = require('./rules/vue');
const svgConfig = require('./rules/svg');
// See the shakacode/shakapacker README and docs directory for advice on customizing your webpackConfig.
const { generateWebpackConfig, merge, mergeWithRules } = require('shakapacker')
const cssConfig = require('./rules/css')
const svgConfig = require('./rules/svg')
const vueConfig = require('./rules/vue')
const webpackConfig = generateWebpackConfig()
let conf = merge(vueConfig, svgConfig);
conf = merge(cssConfig, conf);
conf = merge(conf, webpackConfig);
const updateFileLoaderConf = {
module: {
rules: [
{
type: 'asset/resource',
// This version does not include svg. See https://github.com/shakacode/shakapacker/blob/v6.2.0/package/rules/file.js
test: /\.(bmp|gif|jpe?g|png|tiff|ico|avif|webp|eot|otf|ttf|woff|woff2)$/
}
]
}
module: {
rules: [
{
type: 'asset/resource',
// This version does not include svg. See https://github.com/shakacode/shakapacker/blob/v6.2.0/package/rules/file.js
test: /\.(bmp|gif|jpe?g|png|tiff|ico|avif|webp|eot|otf|ttf|woff|woff2)$/
}
]
}
}
conf = mergeWithRules({
module: {
rules: {
type: "match",
test: "replace"
}
module: {
rules: {
type: "match",
test: "replace"
}
}
})(conf, updateFileLoaderConf);
module.exports = conf

View File

@ -1,24 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name ="theme-color" content="rgba(121, 167, 54, 1)">
<link rel="manifest" href="/manifest.json">
<title>Parsley</title>
</head>
<body>
<div id="app" data-url="http://localhost:3000/">
<div id="app-placeholder">
Loading...
</div>
</div>
<script src="/packs/application-3067a90281a92d120ca6.js"></script>
</body>
</html>

View File

@ -1,54 +0,0 @@
namespace :dev do
desc 'Run both rails server and webpack dev server'
task :run do
rails_pid = fork do
exec 'rails s'
end
webpack_pid = fork do
exec 'bin/webpack-dev-server'
end
running = true
shutdown = false
shutdown_start = nil
Signal.trap('SIGINT') do
shutdown = true
end
while running
rails_check ||= Process.waitpid(rails_pid, Process::WNOHANG)
webpack_check ||= Process.waitpid(webpack_pid, Process::WNOHANG)
running = rails_check.nil? || webpack_check.nil?
if shutdown
if shutdown_start.nil?
puts "Shutting down..."
shutdown_start = Time.now
#Process.kill("SIGINT", rails_pid) rescue Errno::ESRCH
#Process.kill("SIGINT", webpack_pid) rescue Errno::ESRCH
end
if (Time.now - shutdown_start) > 5
if rails_check.nil?
puts "Force killing rails..."
Process.kill("KILL", rails_pid)
end
if webpack_check.nil?
puts "Force killing webpack..."
Process.kill("KILL", webpack_pid)
end
end
end
sleep 0.25
end
end
end

View File

@ -1,44 +1,11 @@
{
"name": "parsley",
"packageManager": "yarn@4.5.0",
"name": "app",
"private": true,
"dependencies": {
"@babel/core": "7",
"@babel/plugin-transform-runtime": "7",
"@babel/preset-env": "7",
"@babel/runtime": "7",
"@rails/actioncable": "^7.0.4",
"@tweenjs/tween.js": "^18.6.4",
"autosize": "^5.0.2",
"babel-loader": "8",
"bulma": "0.9.4",
"cheerio": "^1.0.0-rc.12",
"compression-webpack-plugin": "9",
"css-loader": "^6.7.2",
"css-minimizer-webpack-plugin": "^4.2.2",
"lodash": "^4.17.21",
"mini-css-extract-plugin": "^2.7.1",
"sass": "^1.56.1",
"sass-loader": "^13.2.0",
"shakapacker": "6.5.4",
"style-loader": "^3.3.1",
"svg-loader": "^0.0.2",
"terser-webpack-plugin": "5",
"url-loader": "^4.1.1",
"vue": "^2.6.14",
"vue-loader": "^15.10.1",
"vue-progressbar": "^0.7.5",
"vue-resize": "^1.0.1",
"vue-router": "^3.6.5",
"vue-template-compiler": "^2.6.14",
"vuex": "^3.6.2",
"vuex-router-sync": "^5.0.0",
"webpack": "5",
"webpack-assets-manifest": "5",
"webpack-cli": "4",
"webpack-dev-server": "^4.11.1",
"webpack-merge": "5"
},
"version": "0.1.0",
"scripts": {
"console": "node"
},
"babel": {
"presets": [
"./node_modules/shakapacker/package/babel/preset.js"
@ -47,5 +14,40 @@
"browserslist": [
"defaults"
],
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
"dependencies": {
"@babel/core": "7",
"@babel/plugin-transform-runtime": "7",
"@babel/preset-env": "7",
"@babel/runtime": "7",
"@rails/actioncable": "7.2.100",
"@swc/core": "1.7.26",
"@tweenjs/tween.js": "^25.0.0",
"@types/babel__core": "7",
"@types/webpack": "5",
"babel-loader": "8",
"bulma": "^1.0.2",
"cheerio": "^1.0.0",
"compression-webpack-plugin": "9",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.0",
"lodash": "^4.17.21",
"mini-css-extract-plugin": "^2.9.1",
"pinia": "^2.2.2",
"sass": "~1.78.0",
"sass-loader": "^16.0.2",
"shakapacker": "8.0.2",
"style-loader": "^4.0.0",
"swc-loader": "^0.2.6",
"terser-webpack-plugin": "5",
"vue": "^3.5.10",
"vue-loader": "^17.4.2",
"vue-router": "^4.4.5",
"webpack": "5",
"webpack-assets-manifest": "5",
"webpack-cli": "4",
"webpack-merge": "5"
},
"devDependencies": {
"webpack-dev-server": "4"
}
}

View File

@ -1,12 +0,0 @@
module.exports = {
plugins: [
require('postcss-import'),
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009'
},
stage: 3
})
]
}

11270
yarn.lock

File diff suppressed because it is too large Load Diff