32 Commits

Author SHA1 Message Date
8780245986 v1.0.2 2018-09-13 16:32:43 -07:00
90f3fba45a chore: sync version on version bump 2018-09-13 16:19:25 -07:00
b1d244e34c chore: add version sync script 2018-09-13 16:19:25 -07:00
01871f083b v1.0.1 2018-09-13 15:53:42 -07:00
2020e76b3e chore: remove oao
i wanted it to sync versions, which it only does on publish, so it's kind of useless for this project
2018-09-13 15:52:52 -07:00
d5b0cd0730 fix: correct localstorage fallback 2018-09-13 15:48:06 -07:00
5bdd35b74b docs: update readme with usable scripts 2018-09-13 15:47:49 -07:00
fa2db8d430 fix: scrape strains alphabetically
ensures that we get everything
2018-09-13 15:16:42 -07:00
a2bea32fb6 v1.0.0 2018-09-08 18:03:45 -07:00
bebd51b047 chore: bump esm 2018-09-08 18:01:44 -07:00
4539515bf7 chore: fix scraper package name 2018-09-08 17:55:31 -07:00
81d41f74b4 chore: add oao for monorepo management 2018-09-08 17:54:05 -07:00
7d70224bdf chore: add prepush script
lints projects before allowing pushing
2018-09-08 17:44:26 -07:00
9c4641dd0a chore: convert everything to vue sfc 2018-09-08 17:43:32 -07:00
5e47f71f31 chore: add and configure poi 2018-09-08 17:43:31 -07:00
0414e1aae6 chore: fix linting script 2018-09-08 17:43:31 -07:00
e3e95e7c2b fix: assign key to strain cards
this fixes incorrect re-use of the component
2018-09-06 18:21:43 -07:00
4591a47dba fix: remove listener on form unmount 2018-09-06 18:16:34 -07:00
50a3b1db92 feat: add fav/unfav control
persisted in localstorage
2018-09-06 18:16:03 -07:00
c652eee096 chore: add listener for favorite changes 2018-09-06 18:15:35 -07:00
a15deb9602 chore: build a simple store
based on localstorage
2018-09-06 18:15:18 -07:00
5665bdeaa9 feat: adjust card tag label by screen size 2018-09-04 18:19:37 -07:00
712c5c095c feat: sort by adjusted rating
based on the calculation here: https://stats.stackexchange.com/questions/6418/rating-system-taking-account-of-number-of-votes/6423#6423
2018-09-04 18:19:19 -07:00
fe38c452b5 chore: update readmes 2018-08-31 16:52:09 -07:00
943c0917c7 feat: show errors, sort tags 2018-08-31 16:46:30 -07:00
d3b21103eb feat: functional search on site 2018-08-31 16:22:17 -07:00
9a1f2f9785 feat: even more site functionality 2018-08-31 15:12:14 -07:00
fc158da8a1 chore: add firebase deploy 2018-08-31 13:37:11 -07:00
804fac1afb feat: add tags to scraper output 2018-08-31 10:47:33 -07:00
22a95c3fe8 feat: mocked up search form on site 2018-08-30 20:34:32 -07:00
699a03c7c0 feat: simple site build and dev server 2018-08-30 20:34:08 -07:00
37dcabde5e feat: add a search site package 2018-08-30 19:21:36 -07:00
25 changed files with 7021 additions and 115 deletions

4
.gitignore vendored
View File

@@ -7,6 +7,8 @@ yarn-error.log
.env
coverage
.nyc_output
.firebase
.firebaserc
coverage.lcov
/lib
dist/
db.json

3
AUTHORS.md Normal file
View File

@@ -0,0 +1,3 @@
### Authors
- joe fleming ([w33ble](https://github.com/w33ble))

24
CHANGELOG.md Normal file
View File

@@ -0,0 +1,24 @@
### Changelog
#### [v1.0.2](https://git.w33ble.com/w33ble/strain-tools/compare/v1.0.1...v1.0.2) (13 September 2018)
#### [v1.0.1](https://git.w33ble.com/w33ble/strain-tools/compare/v1.0.0...v1.0.1) (13 September 2018)
- fix: correct localstorage fallback [`d5b0cd0`](https://git.w33ble.com/w33ble/strain-tools/commit/d5b0cd07307dbf06d8d4530803a00c2b2ab8c4c5)
- docs: update readme with usable scripts [`5bdd35b`](https://git.w33ble.com/w33ble/strain-tools/commit/5bdd35b74b417706c76bd50d7162567b60dbdcb2)
- fix: scrape strains alphabetically [`fa2db8d`](https://git.w33ble.com/w33ble/strain-tools/commit/fa2db8d4304b0b0d91221b4ef942daa74ab3df94)
#### v1.0.0 (9 September 2018)
- fix: assign key to strain cards [`e3e95e7`](https://git.w33ble.com/w33ble/strain-tools/commit/e3e95e7c2b390ca52633e84604cf6a4a9b239d54)
- fix: remove listener on form unmount [`4591a47`](https://git.w33ble.com/w33ble/strain-tools/commit/4591a47dbaa62cbd330bbc9b96ace2be1ace8bd6)
- feat: add fav/unfav control [`50a3b1d`](https://git.w33ble.com/w33ble/strain-tools/commit/50a3b1db92a64fcd7e10e35ba045a3970a1440ba)
- feat: adjust card tag label by screen size [`5665bde`](https://git.w33ble.com/w33ble/strain-tools/commit/5665bdeaa99bcbf4fc5a9d13d98803b17a42404b)
- feat: sort by adjusted rating [`712c5c0`](https://git.w33ble.com/w33ble/strain-tools/commit/712c5c095c52269d7ffbd7f763cdf325b11369a4)
- feat: show errors, sort tags [`943c091`](https://git.w33ble.com/w33ble/strain-tools/commit/943c0917c71db1612aefd9f2fc8bde098000364c)
- feat: functional search on site [`d3b2110`](https://git.w33ble.com/w33ble/strain-tools/commit/d3b21103eb5227101f581fcc853714bc78ea9950)
- feat: even more site functionality [`9a1f2f9`](https://git.w33ble.com/w33ble/strain-tools/commit/9a1f2f9785e36bd2de2a3a1fbf46bbc9cfdb90d3)
- feat: add tags to scraper output [`804fac1`](https://git.w33ble.com/w33ble/strain-tools/commit/804fac1afb68dc52f482cbfa393856b14b64400a)
- feat: mocked up search form on site [`22a95c3`](https://git.w33ble.com/w33ble/strain-tools/commit/22a95c3fe827ec0651c44164f58950dcc283354b)
- feat: simple site build and dev server [`699a03c`](https://git.w33ble.com/w33ble/strain-tools/commit/699a03c7c06da011b64bdc358287f5d653c96c92)
- feat: add a search site package [`37dcabd`](https://git.w33ble.com/w33ble/strain-tools/commit/37dcabde5e20a6ecea803f40798ae43c72e3afe8)
- feat: working scraper [`cc5e15d`](https://git.w33ble.com/w33ble/strain-tools/commit/cc5e15dbb11f388ba260da6c196fe6827e947d3c)
- initial commit [`c81b30b`](https://git.w33ble.com/w33ble/strain-tools/commit/c81b30be896009b48e66005007f88e55fb159c9c)

View File

@@ -1,10 +1,8 @@
# strain-scraper
# strain-tools
scrapes strain info, stores for later reference.
![license](https://img.shields.io/badge/license-MIT-blue.svg)
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/w33ble/strain-scraper/master/LICENSE)
[![npm](https://img.shields.io/npm/v/strain-scraper.svg)](https://www.npmjs.com/package/strain-scraper)
[![Project Status](https://img.shields.io/badge/status-experimental-orange.svg)](https://nodejs.org/api/documentation.html#documentation_stability_index)
Monorepo for cannabis strain tools. For repos, check in `packages`.
#### License

View File

@@ -1,14 +1,16 @@
{
"name": "strain-tools",
"version": "0.0.0",
"version": "1.0.2",
"description": "strain tools",
"main": "index",
"module": "index.mjs",
"private": true,
"scripts": {
"lint": "eslint \"*.{js,mjs}\" \"src/**/*.{js,mjs}\"",
"lint": "eslint \"packages/*/*.{js,mjs,vue}\" \"packages/*/src/**/*.{js,mjs,vue}\"",
"precommit": "lint-staged",
"version": "auto-changelog -p && auto-authors && git add CHANGELOG.md AUTHORS.md",
"prepush": "npm run lint",
"sync-versions": "node -r esm scripts/version-sync.mjs",
"version": "npm run sync-versions && auto-changelog -p && auto-authors && git add CHANGELOG.md AUTHORS.md packages",
"start": "node .",
"dev": "nodemon --ignore db.json ."
},

View File

@@ -0,0 +1,10 @@
# leafly-scraper
Scrapes strain info, stores for later reference.
Clone repo and run the command. Resulting data can be found in `db.json`.
```
yarn install
yarn start
```

View File

@@ -1,6 +1,6 @@
{
"name": "leafly-scraper",
"version": "0.0.0",
"name": "scraper",
"version": "1.0.2",
"private": true,
"description": "scrapes strain info, stores for later reference",
"main": "index",
@@ -29,7 +29,7 @@
},
"dependencies": {
"axios": "^0.18.0",
"esm": "^3.0.17",
"esm": "^3.0.82",
"lodash": "^4.17.10",
"lowdb": "^1.0.0"
},

View File

@@ -8,12 +8,14 @@ const adapter = new FileAsync('db.json');
const xhr = axios.create({
headers: {
Accept: 'application/json, text/plain, */*',
Referer: 'https://www.leafly.com/explore',
Referer: 'https://www.leafly.com/explore/sort-alpha',
},
});
const pSeries = tasks => tasks.reduce((c, task) => c.then(task), Promise.resolve());
const getPage = async num => {
const url = `https://www.leafly.com/explore/page-${num}`;
const url = `https://www.leafly.com/explore/page-${num}/sort-alpha`;
const response = await xhr.get(url, {
responseType: 'json',
});
@@ -45,13 +47,25 @@ export default async function scrapeLeafly(startFrom = 1, endAt = Infinity) {
let finished = false;
const db = await low(adapter);
await db.defaults({ strains: [] }).write();
async function writeTag(type, tag) {
const res = await db
.get(type)
.indexOf(tag)
.value();
while (!finished) {
console.log(`Fetching page ${pageNum}`);
const data = await getPage(pageNum);
if (res < 0) {
await db
.get(type)
.push(tag)
.write();
}
}
data.strains.forEach(async strain => {
async function writeTags(type, tags) {
await pSeries(tags.map(tag => () => writeTag(type, tag)));
}
async function writeDoc(strain) {
// check for value
const doc = db
.get('strains')
@@ -65,8 +79,31 @@ export default async function scrapeLeafly(startFrom = 1, endAt = Infinity) {
.get('strains')
.push(strain)
.write();
await writeTags('effects', strain.effects);
await writeTags('negative_effects', strain.negative_effects);
await writeTags('uses', strain.uses);
await writeTags('conditions', strain.conditions);
await writeTags('flavors', strain.flavors);
}
});
}
await db
.defaults({
strains: [],
effects: [],
negative_effects: [],
uses: [],
conditions: [],
flavors: [],
})
.write();
while (!finished) {
console.log(`Fetching page ${pageNum}`);
const data = await getPage(pageNum);
await pSeries(data.strains.map(strain => () => writeDoc(strain)));
if (pageNum >= endAt || !data.strains.length || data.page.isLastPage) finished = true;
pageNum += 1;

View File

@@ -0,0 +1,23 @@
{
"extends": [
"../../.eslintrc",
"plugin:vue/essential"
],
"parserOptions": {
"parser": "babel-eslint",
"ecmaVersion": 8,
"sourceType": "module"
},
"plugins": [
"vue"
],
"rules": {
"import/no-extraneous-dependencies": [
"error", {
"devDependencies": ["packages/search-site/poi.config.js"],
"optionalDependencies": false,
"peerDependencies": false
}
]
}
}

View File

@@ -0,0 +1,17 @@
# search-site
![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)
Strain search static website. Use it to search for strains by name, effects, medical uses, and other tags.
Based on data from leafly. You'll need to run the scraper first since that's where the data comes from.
## Usage
- `yarn start`: starts the dev server, with HMR
- `yarn build`: builds the static site into the `dist` path
- `yarn deploy`: builds and deploys the app onto firebase
#### License
MIT © [w33ble](https://github.com/w33ble)

View File

@@ -0,0 +1,12 @@
{
"hosting": {
"public": "dist",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}

View File

@@ -0,0 +1,5 @@
/* eslint no-global-assign: 0 */
require = require('esm')(module);
const mod = require('./src/index.mjs').default;
mod();

View File

@@ -0,0 +1,3 @@
import mod from './src/index.mjs';
mod();

View File

@@ -0,0 +1,35 @@
{
"name": "search-site",
"version": "1.0.2",
"private": true,
"main": "index",
"module": "index.mjs",
"description": "strain search static website",
"scripts": {
"start": "poi",
"build": "poi build",
"deploy": "npm run build && firebase deploy"
},
"author": "joe fleming (https://github.com/w33ble)",
"license": "MIT",
"esm": {
"cjs": true
},
"dependencies": {
"ejs": "^2.6.1",
"esm": "^3.0.82",
"lunr": "^2.3.3",
"mitt": "^1.1.3",
"vue": "^2.5.17",
"vue-router": "^3.0.1",
"vue-template-compiler": "^2.5.17"
},
"devDependencies": {
"@poi/plugin-vue-static": "^1.0.7",
"babel-eslint": "^9.0.0",
"eslint-plugin-vue": "^4.7.1",
"firebase-tools": "^4.2.1",
"nodemon": "^1.18.4",
"poi": "^10.2.10"
}
}

View File

@@ -0,0 +1,8 @@
/* eslint global-require: 0 */
const path = require('path');
module.exports = {
entry: path.resolve(__dirname, 'src/index.mjs'),
outDir: 'dist',
plugins: [require('@poi/plugin-vue-static')()],
};

View File

@@ -0,0 +1,138 @@
<template>
<form @submit.prevent="handleSubmit">
<div class="container">
<h1 class="title">
Strain Search
</h1>
<!-- Name Search Input -->
<div class="field">
<label class="label">Search By Name</label>
<div class="control">
<input ref="name" class="input" type="text" placeholder="Text input">
<p v-if="error.length" class="help is-danger">{{error}}</p>
</div>
</div>
<!-- Multi-Selects -->
<div class="columns filters">
<div class="column is-half">
<div class="columns is-mobile">
<div class="column is-half">
<div class="field">
<label class="label">Desired Effects</label>
<div class="select is-multiple">
<select ref="effects" multiple size="6">
<option v-for="effect in effects" :key="effect" :value="effect">{{effect | capitalize}}</option>
</select>
</div>
</div>
</div>
<div class="column is-half">
<div class="field">
<label class="label">Medical Use</label>
<div class="select is-multiple">
<select ref="uses" multiple size="6">
<option v-for="use in uses" :key="use" :value="use">{{use | capitalize}}</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="column is-half">
<div class="columns is-mobile">
<div class="column is-half">
<div class="field">
<label class="label">Condition</label>
<div class="select is-multiple">
<select ref="conditions" multiple size="6">
<option v-for="condition in conditions" :key="condition" :value="condition">{{condition | capitalize}}</option>
</select>
</div>
</div>
</div>
<div class="column is-half">
<div class="field">
<label class="label">Flavor</label>
<div class="select is-multiple">
<select ref="flavors" multiple size="6">
<option v-for="flavor in flavors" :key="flavor" :value="flavor">{{flavor | capitalize}}</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Form Submits -->
<div class="field is-grouped">
<div class="control">
<button type="submit" class="button is-link">Submit</button>
</div>
<div class="control">
<button type="button" class="button is-text" @click.prevent="resetForm">Clear</button>
</div>
</div>
</div>
</form>
</template>
<script>
import emitter from '../lib/emitter.mjs';
const getMultiValues = node => Array.from(node.selectedOptions).map(o => o.value);
export default {
props: {
effects: Array,
uses: Array,
conditions: Array,
flavors: Array,
},
data() {
return {
error: '',
};
},
created() {
emitter.on('error', this.setError);
},
beforeDestroy() {
emitter.off('error', this.setError);
},
methods: {
handleSubmit() {
const requirements = {
name: this.$refs.name.value,
effects: getMultiValues(this.$refs.effects),
uses: getMultiValues(this.$refs.uses),
conditions: getMultiValues(this.$refs.conditions),
flavors: getMultiValues(this.$refs.flavors),
};
this.error = '';
emitter.emit('search', requirements);
},
resetForm() {
this.$el.reset();
},
setError(msg) {
this.error = msg;
},
},
};
</script>
<style scoped>
.filters .select {
display: block;
}
.filters .select select[multiple] {
width: 100%;
}
</style>

View File

@@ -0,0 +1,75 @@
<template>
<div class="card">
<header class="card-header">
<p class="card-header-title">{{strain.name}}</p>
<div class="tags" style="margin: 0 12px; padding: 6px 0;">
<span v-if="strain.category === 'indica'" class="tag is-rounded is-indica">{{strain.category}}</span>
<span v-if="strain.category === 'sativa'" class="tag is-rounded is-sativa">{{strain.category}}</span>
<span v-if="strain.category === 'hybrid'" class="tag is-rounded is-hybrid">{{strain.category}}</span>
<span class="tag is-rounded is-light">{{strain.rating | round}} ({{strain.rating_count}})</span>
</div>
</header>
<div class="card-content">
<TagList title="Effects" :name="strain.name" :tags="strain.effects" />
<TagList title="Uses" :name="strain.name" :tags="strain.uses" />
<TagList title="Conditions" :name="strain.name" :tags="strain.conditions" />
<TagList title="Flavors" :name="strain.name" :tags="strain.flavors" />
</div>
<footer class="card-footer">
<div class="card-footer-item">
<button
class="button"
:class="{ 'is-success': favorite }"
@click.prevent="toggleFavorite(strain.id)"
>
Save
</button>
</div>
</footer>
</div>
</template>
<script>
import store from '../lib/store.mjs';
import emitter from '../lib/emitter.mjs';
import TagList from './TagList.vue';
export default {
components: {
TagList,
},
props: {
strain: Object,
},
data() {
const favs = store.get('favorites') || [];
return {
favorite: favs.indexOf(this.strain.id) >= 0,
};
},
methods: {
toggleFavorite() {
this.favorite = !this.favorite;
emitter.emit('favorite', { id: this.strain.id, isFav: this.favorite });
},
},
};
</script>
<style scoped>
.tag.is-indica {
background-color: hsl(217, 71%, 53%);
color: #fff;
}
.tag.is-sativa {
background-color: hsl(348, 100%, 61%);
color: #fff;
}
.tag.is-hybrid {
background-color: hsl(271, 100%, 71%);
color: #fff;
}
</style>

View File

@@ -0,0 +1,62 @@
<template>
<div class="container">
<div>
<h3 v-if="strains.length === 0" class="title is-3">No Matching Strains :(</h3>
<h3 v-if="strains.length > 0" class="title is-3">Found {{strains.length}} Strains</h3>
</div>
<div class="cards">
<StrainCard v-for="strain in strains" :key="strain.id" :strain="strain" />
</div>
</div>
</template>
<script>
import StrainCard from './StrainCard.vue';
export default {
components: {
StrainCard,
},
props: {
strains: {
type: Array,
required: true,
},
},
};
</script>
<style scoped>
.cards {
display: flex;
flex-wrap: wrap;
}
.cards .card {
width: 100%;
margin: 12px;
}
.cards .card .tag-list--title {
font-size: 0.8em;
}
@media screen and (min-width: 769px) {
.cards .card {
width: 45%;
}
}
@media screen and (min-width: 1280px) {
.cards .card {
width: 30%;
}
}
@media screen and (max-width: 860px) {
.cards .card .tag-list--title {
font-size: 0.7em;
}
}
</style>

View File

@@ -0,0 +1,22 @@
<template>
<div v-if="tags.length" class="tag-list columns is-mobile">
<div class="tag-list--title column is-one-quarter">
{{title}}
</div>
<div class="tag-list--tag column">
<div v-if="tags.length" class="tags">
<span v-for="tag in tags" :key="name + tag" class="tag is-rounded">{{tag}}</span>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
tags: Array,
title: String,
name: String,
},
};
</script>

View File

@@ -0,0 +1,33 @@
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router);
Vue.filter('capitalize', value => {
if (!value) return '';
const v = value.toString();
return v.charAt(0).toUpperCase() + v.slice(1);
});
Vue.filter('round', (value, digits = 2) => {
const v = parseFloat(value, 10);
return Math.round(v * (10 * digits)) / (10 * digits);
});
const router = new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: () => import(/* webpackChunkName: "homeapp" */ './pages/Home.vue'),
},
],
});
const app = new Vue({
router,
render: h => h('div', { attrs: { id: 'app' } }, [h('router-view')]),
});
export default app;

View File

@@ -0,0 +1,5 @@
import mitt from 'mitt';
const emitter = mitt();
export default emitter;

View File

@@ -0,0 +1,18 @@
/* eslint-env browser */
const storage = (() => {
try {
return window.localStorage;
} catch (err) {
// return a mock localstorage in the server env
return {
getItem: () => null,
setItem: () => null,
};
}
})();
export default {
get: id => JSON.parse(storage.getItem(id)),
set: (id, val) => storage.setItem(id, JSON.stringify(val)),
};

View File

@@ -0,0 +1,149 @@
<template>
<div>
<section class="section">
<SearchForm :effects="effects" :uses="uses" :conditions="conditions" :flavors="flavors" />
</section>
<section class="section">
<StrainList :strains="matches" />
</section>
</div>
</template>
<script>
import lunr from 'lunr';
import SearchForm from '../components/SearchForm.vue';
import StrainList from '../components/StrainList.vue';
import data from '../../../scraper/db.json';
import emitter from '../lib/emitter.mjs';
import store from '../lib/store.mjs';
export default {
name: 'Home',
components: {
SearchForm,
StrainList,
},
head: {
title: 'Strain Search',
link: [
{
rel: 'stylesheet',
href: 'https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css',
},
],
},
data() {
return {
...data,
matches: [],
requirements: {
name: '',
effects: [],
uses: [],
conditions: [],
flavors: [],
},
};
},
computed: {
hasName() {
return this.requirements.name.length > 0;
},
hasFilters() {
return (
this.requirements.effects.length > 0 ||
this.requirements.uses.length > 0 ||
this.requirements.conditions.length > 0 ||
this.requirements.flavors.length > 0
);
},
searchParams() {
const searchParts = [this.requirements.name];
this.requirements.effects.forEach(t => searchParts.push(`+effects:${t}`));
this.requirements.uses.forEach(t => searchParts.push(`+uses:${t}`));
this.requirements.conditions.forEach(t => searchParts.push(`+conditions:${t}`));
this.requirements.flavors.forEach(t => searchParts.push(`+flavors:${t}`));
return searchParts.join(' ');
},
},
methods: {
showDefaults(limit = 40) {
// const favs = store.get('favorites') || [];
// const favStrains = this.strains.filter(({ id }) => favs.indexOf(id) !== -1);
// this.matches = favStrains.concat(this.strains.slice(0, limit - favStrains.length));
this.matches = this.strains.slice(0, limit);
},
updateMatches(limit = 40) {
if (!this.hasName && !this.hasFilters) {
this.showDefaults(limit);
return;
}
try {
const hits = this.idx.search(this.searchParams);
// .slice(0, limit);
const refs = hits.map(({ ref }) => parseInt(ref, 10));
this.matches = this.strains
.map(strain => {
const idx = refs.indexOf(strain.id);
if (idx < 0) return null;
return Object.assign({ score: hits[idx].score }, strain);
})
.filter(Boolean)
.sort((a, b) => {
if (a.score === b.score) {
// sort matching search score by adjusted rating
if (a.rating_adjusted === b.rating_adjusted) return 0;
return a.rating_adjusted > b.rating_adjusted ? -1 : 1;
}
return a.score > b.score ? -1 : 1;
});
} catch (err) {
this.matches = [];
emitter.emit('error', err.message);
}
},
},
created() {
// lunr setup
this.idx = lunr(function lunrSetup() {
this.ref('id');
this.field('name');
this.field('effects');
this.field('uses');
this.field('conditions');
this.field('flavors');
data.strains.forEach(doc => this.add(doc));
});
// function to handle search form submissions
this.searchListener = reqs => {
this.requirements = reqs;
this.updateMatches();
};
// listen for search form submissions
emitter.on('search', r => this.searchListener(r));
// listen for favorite changes
emitter.on('favorite', ({ id, isFav }) => {
const favs = store.get('favorites') || [];
const idx = favs.indexOf(id);
// remove previously favorited strain
if (idx >= 0 && !isFav) {
store.set('favorites', favs.filter(f => f !== id));
} else if (idx === -1 && isFav) {
store.set('favorites', favs.concat(id));
}
});
this.updateMatches(); // set initial match list
},
beforeDestroy() {
emitter.off('search', r => this.searchListener(r));
},
};
</script>

26
scripts/version-sync.mjs Normal file
View File

@@ -0,0 +1,26 @@
import fs from 'fs';
import { promisify } from 'util';
import { join } from 'path';
import pkg from '../package.json';
const readDir = promisify(fs.readdir);
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
async function syncPackageVersions() {
const packagesPath = 'packages'; // path for all packages
const { version } = pkg;
const packages = await readDir(packagesPath);
packages.forEach(async pack => {
const packagePath = join(packagesPath, pack, 'package.json');
const p = JSON.parse(await readFile(packagePath, 'utf-8'));
p.version = version;
await writeFile(packagePath, `${JSON.stringify(p, null, 2)}\n`);
});
console.log(`Versions with root: ${version}`);
}
syncPackageVersions();

6365
yarn.lock

File diff suppressed because it is too large Load Diff