├── sample-data-old ├── tasktxt.txt ├── _BHxrxC3nSY2cEBjWlaEFW.metadata ├── bitcoin-price.txt ├── math-pack.txt ├── hikes.txt ├── crm.txt ├── time-tracking.txt ├── todos.txt ├── bitcoin-price.metadata ├── all-ingredients.metadata ├── stonks-portfolio.txt ├── splitwise.txt ├── goodreads.1.highlighter ├── goodreads.3.highlighter ├── hikes.2.highlighter ├── movie-log.2.highlighter ├── crm.2.highlighter ├── crm.1.highlighter ├── movie-log.3.highlighter ├── splitwise.3.highlighter ├── trip-plan.city.highlighter ├── _hCDH5F2LuEr2CrRiS42Gv.highlighter ├── goodreads.2.highlighter ├── pizza-dough.txt ├── bar.price.highlighter ├── splitwise.2.highlighter ├── splitwise.4.highlighter ├── blocks.block.highlighter ├── tasktxt.2.highlighter ├── trip-plan.day.highlighter ├── common.date.highlighter ├── time-tracking.time.highlighter ├── movie-log.1.highlighter ├── workshop.txt ├── math-pack.operator.highlighter ├── aeropress.txt ├── movie-log.txt ├── goodreads.txt ├── recipe-pack.txt ├── _1F0eRt4v6Onwr8fDfaXQU.highlighter ├── crm.metadata ├── common.emoji.highlighter ├── hikes.1.highlighter ├── hikes.metadata ├── todos.metadata ├── welcome.metadata ├── common.number.highlighter ├── plant-watering.txt ├── stonks-portfolio.metadata ├── youtube.highlighter ├── bar.txt ├── bar.total.highlighter ├── trip-plan.time.highlighter ├── default-searches.txt ├── _qvqxfvCRfMf4jMbieqxcv.highlighter ├── common.duration.highlighter ├── trip-plan.timeSpan.highlighter ├── all-ingredients.names.highlighter ├── workshop.time.highlighter ├── trip-plan.txt ├── stonks-portfolio.1.highlighter ├── tasktxt.1.highlighter ├── todos.1.highlighter ├── food.scale.highlighter ├── _BHxrxC3nSY2cEBjWlaEFW.txt ├── bitcoin-price.1.highlighter ├── goodreads.metadata ├── pizza.metadata ├── todos.2.highlighter ├── workout.metadata ├── pizza-dough.inputs.highlighter ├── plant-watering.metadata ├── workshop.metadata ├── math-pack.metadata ├── _AJ5yvielohvnk3xvY1ea5.txt ├── pizza-dough.metadata ├── ice-cream.metadata ├── recipe-pack.metadata ├── time-tracking.open-time.highlighter ├── stonks-portfolio.2.highlighter ├── tasktxt.metadata ├── movie-log.metadata ├── splitwise.metadata ├── tasktxt.3.highlighter ├── workout.txt ├── trip-plan.activity.highlighter ├── blocks.example-ingredient.highlighter ├── plant-watering.1.highlighter ├── splitwise.1.highlighter ├── food.quantity.highlighter ├── trip-plan.openingHours.highlighter ├── blocks.collapse.highlighter ├── ice-cream.txt ├── time-tracking.metadata ├── markdown.highlighter ├── gochujang-pork.metadata ├── time-tracking.project.highlighter ├── trip-plan.openingRange.highlighter ├── _EPSPtlS2leXEhRmSEoQZh.highlighter ├── blocks.shuffle.highlighter ├── bar.metadata ├── plant-watering.2.highlighter ├── aeropress.metadata ├── blocks.sort.highlighter ├── workout.exercise.highlighter ├── blocks.bla.highlighter ├── blocks.remove.highlighter ├── pizza-dough.water.highlighter ├── default-searches.metadata ├── blocks.txt ├── pizza-dough.number-of-balls.highlighter ├── _AJ5yvielohvnk3xvY1ea5.metadata ├── _oHItjl0ecKP4fV4fh15L6.metadata ├── food.ingredient.highlighter ├── bar.sales.highlighter ├── workout.workout.highlighter ├── math-pack.formula.highlighter ├── blocks.metadata ├── _iiy0UQcJ4SleIwm7XOkYg.txt ├── blocks.sorting-hat.highlighter ├── _oHItjl0ecKP4fV4fh15L6.txt ├── tasktxt.4.highlighter ├── trip-plan.metadata ├── workshop.duration.highlighter ├── _iiy0UQcJ4SleIwm7XOkYg.metadata ├── pizza.txt └── gochujang-pork.txt ├── src ├── vite-env.d.ts ├── assets │ ├── plus-icon.svg │ ├── chevron-forward.svg │ ├── slider-handle.svg │ ├── stop-icon.svg │ ├── pause-icon.svg │ ├── play-icon.svg │ ├── slider-track.svg │ ├── asterisk-icon.svg │ ├── whiteout.svg │ └── splat-underline-2-default.svg ├── refactor │ ├── cleanup-highlighters.mjs │ ├── utils.mjs │ └── watcher.mjs ├── embedded-main.tsx ├── main.tsx ├── data │ ├── officialFoods.ts │ └── measure_unit.csv ├── EmbeddedDocument.tsx ├── NumberSliderComponent.tsx ├── markdown.ts ├── index.css ├── utils.ts ├── HighlightHoverCard.tsx ├── SheetCalendar.tsx ├── compute.ts └── highlight.ts ├── documents ├── video.mp4 ├── todos.json ├── stonks.json ├── plant-tracker.js └── coffee.json ├── postcss.config.js ├── sample-data ├── hikes.txt ├── stonks-portfolio.txt ├── todos.txt ├── bar.txt ├── time-tracking.txt ├── workshop.txt ├── pizza-dough.txt ├── all-ingredients.metadata ├── math-pack.txt ├── splitwise.3.highlighter ├── splitwise.txt ├── _hCDH5F2LuEr2CrRiS42Gv.highlighter ├── bar.price.highlighter ├── splitwise.2.highlighter ├── splitwise.4.highlighter ├── common.date.highlighter ├── math-pack.operator.highlighter ├── aeropress.txt ├── recipe-pack.txt ├── _1F0eRt4v6Onwr8fDfaXQU.highlighter ├── common.emoji.highlighter ├── hikes.1.highlighter ├── hikes.metadata ├── common.number.highlighter ├── plant-watering.txt ├── hikes.2.highlighter ├── _obENYhG63Nd13x43qDRr0.highlighter ├── youtube.highlighter ├── _wqlqE65AnWhAK9dX8pm14.highlighter ├── _qvqxfvCRfMf4jMbieqxcv.highlighter ├── common.duration.highlighter ├── all-ingredients.names.highlighter ├── time-tracking.time.highlighter ├── workshop.time.highlighter ├── todos.1.highlighter ├── default-searches.txt ├── todos.metadata ├── food.scale.highlighter ├── todos.2.highlighter ├── pizza-dough.inputs.highlighter ├── math-pack.metadata ├── plant-watering.metadata ├── stonks-portfolio.metadata ├── bar.total.highlighter ├── pizza-dough.metadata ├── recipe-pack.metadata ├── time-tracking.open-time.highlighter ├── _nzNLA8UWxTsnQ01510U1e.highlighter ├── stonks-portfolio.1.highlighter ├── splitwise.metadata ├── workshop.metadata ├── welcome.metadata ├── workout.metadata ├── plant-watering.1.highlighter ├── splitwise.1.highlighter ├── food.quantity.highlighter ├── bar.sales.highlighter ├── _p8Ot2ukI8aaITNa5bY5JU.highlighter ├── workout.txt ├── markdown.highlighter ├── gochujang-pork.metadata ├── time-tracking.project.highlighter ├── _EPSPtlS2leXEhRmSEoQZh.highlighter ├── bar.metadata ├── aeropress.metadata ├── workout.exercise.highlighter ├── plant-watering.2.highlighter ├── default-searches.metadata ├── stonks-portfolio.2.highlighter ├── pizza-dough.water.highlighter ├── pizza-dough.number-of-balls.highlighter ├── meeting.metadata ├── food.ingredient.highlighter ├── time-tracking.metadata ├── workout.workout.highlighter ├── math-pack.formula.highlighter ├── chilli.txt ├── meeting.txt ├── chilli.metadata ├── workshop.duration.highlighter └── gochujang-pork.txt ├── tsconfig.node.json ├── tailwind.config.js ├── .gitignore ├── embedded.html ├── tsconfig.json ├── index.html ├── vite.config.ts ├── README.md ├── embedded-test.html └── package.json /sample-data-old/tasktxt.txt: -------------------------------------------------------------------------------- 1 | tasktxt 2 | [ ] one task 15s -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /sample-data-old/_BHxrxC3nSY2cEBjWlaEFW.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [] 3 | } -------------------------------------------------------------------------------- /sample-data-old/bitcoin-price.txt: -------------------------------------------------------------------------------- 1 | bitcoin price tracker 2 | $EUR 3 | $USD 4 | $GBP 5 | $CAD -------------------------------------------------------------------------------- /documents/video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inkandswitch/potluck/HEAD/documents/video.mp4 -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /sample-data/hikes.txt: -------------------------------------------------------------------------------- 1 | Hikes 2 | 🌲 Big Pine Creek 14 miles 3 | 🌲 Sea to Summit 7 miles 4 | 🌲 Redwood Regional Park 8 miles 5 | -------------------------------------------------------------------------------- /sample-data/stonks-portfolio.txt: -------------------------------------------------------------------------------- 1 | Stock portfolio 2 | # Stocks 3 | 4 | $GME 250@$5.00 5 | $AMC 100@$30.00 6 | $GOOGL 100@$20 7 | 8 | Portfolio value: -------------------------------------------------------------------------------- /sample-data/todos.txt: -------------------------------------------------------------------------------- 1 | Todos 2 | # Todos 3 | 4 | [ ] Read work email 5 | [x] Finish wordle 6 | [ ] Do laundry 7 | [ ] Pick up order from bookstore 8 | -------------------------------------------------------------------------------- /sample-data-old/math-pack.txt: -------------------------------------------------------------------------------- 1 | math pack 2 | 3 | Evaluates math expressions in your document 4 | 5 | 1 + 2 6 | 7 | 3000 $ / 4 people 8 | 9 | (100 / 50) + 5 * 2 -------------------------------------------------------------------------------- /sample-data/bar.txt: -------------------------------------------------------------------------------- 1 | Cash register 2 | ## Prices 3 | cake 🍰 = $ 4 4 | coffee ☕ = $ 2 5 | cupcake 🧁 = $ 2 6 | 7 | ## Sales 8 | 🍰 9 | 🧁☕️ 10 | 11 | Total: -------------------------------------------------------------------------------- /sample-data-old/hikes.txt: -------------------------------------------------------------------------------- 1 | hikes 2 | using emoji as semantic marker is fun 3 | 4 | 🌲 Big Pine Creek 14miles 5 | 🌲 Sea to Summit 7 miles 6 | 🌲 Redwood Regional Park 8miles 7 | -------------------------------------------------------------------------------- /sample-data/time-tracking.txt: -------------------------------------------------------------------------------- 1 | Timekeeping 2 | # Website relaunch 3 | 4 | 9/10 5 | 14:10 - 15:00 work on logo 6 | 15:00 - 17:00 redesign feedback form 7 | 8 | Total: 9 | 10 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /sample-data/workshop.txt: -------------------------------------------------------------------------------- 1 | Workshop 2 | # Kickoff meeting 3 | 4 | Start 19:00 5 | 6 | 30 minutes Introduction 7 | 30 minutes Discuss goals 8 | 60 minutes Breakup into groups 9 | 30 minutes Wrap up -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | "./index.html", 4 | "./src/**/*.{js,ts,jsx,tsx}", 5 | ], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | } -------------------------------------------------------------------------------- /sample-data-old/crm.txt: -------------------------------------------------------------------------------- 1 | CRM 2 | using double newline as 3 | 4 | sonnentag 5 | he owes me money 6 | (123) 456-7890 7 | 8 | max 9 | doesn't owe me money 10 | 11 | geoffrey 12 | (123) 456-7890 13 | -------------------------------------------------------------------------------- /sample-data-old/time-tracking.txt: -------------------------------------------------------------------------------- 1 | time tracking 2 | # Website relaunch 3 | 4 | 18:55 - 18:56 5 | 17:19 - 17:20 6 | 17:00 - 17:17 7 | 8 | 9 | # Bottle app 10 | 17:19 - 17:20 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /sample-data-old/todos.txt: -------------------------------------------------------------------------------- 1 | natto todos 2 | simple todo list with conditional formatting 3 | 4 | [ ] make a gallery view 5 | [ ] announce multiplayer 08/15/22 6 | [ ] add keyboard shortcuts 7 | [x] add zoom ability -------------------------------------------------------------------------------- /sample-data/pizza-dough.txt: -------------------------------------------------------------------------------- 1 | Ooni pizza dough 2 | number of dough balls: 10 3 | ball weight: 40 4 | water %: 50% 5 | salt: 3% 6 | oil: 1% 7 | proof hours: 3 8 | room temp: 75 F 9 | 10 | Flour: 11 | Water: -------------------------------------------------------------------------------- /sample-data-old/bitcoin-price.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_AbeJEC5Ryyyjb5IDRccEN", 5 | "configId": "bitcoin-price.1", 6 | "hideHighlightsInDocument": false 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /sample-data/all-ingredients.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "Jr0j5bubFWrDIxolnsfdq", 5 | "configId": "all-ingredients.names", 6 | "hideHighlightsInDocument": false 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /sample-data-old/all-ingredients.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "Jr0j5bubFWrDIxolnsfdq", 5 | "configId": "all-ingredients.names", 6 | "hideHighlightsInDocument": false 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /sample-data-old/stonks-portfolio.txt: -------------------------------------------------------------------------------- 1 | stonks portfolio 2 | would like to import outside data 3 | doing row-wise computation is pretty straightforward in potluck 4 | 5 | $GME 250@$5.00 6 | $AMC 100@$1.00 7 | 8 | portfolio value -------------------------------------------------------------------------------- /sample-data-old/splitwise.txt: -------------------------------------------------------------------------------- 1 | splitwise 2 | 6/1/22 3 | scarlett $50 4 | sean $20 5 | paul $10 6 | 7 | 6/2/22 8 | sean $50 9 | sean $30 10 | paul $20 11 | 12 | total [scarlett] 13 | total [sean] 14 | total [paul] 15 | 16 | 17 | -------------------------------------------------------------------------------- /sample-data/math-pack.txt: -------------------------------------------------------------------------------- 1 | Math pack 2 | 3 | Evaluates math expressions in your document 4 | 5 | 1 + 2 6 | 7 | 3000 $ / 4 people 8 | 9 | (100 / 50) + 5 * 2 10 | 11 | 12 | I have 10 guests 13 | 14 | 15 | vegetarian guests = -------------------------------------------------------------------------------- /sample-data-old/goodreads.1.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "goodreads.1", 3 | "name": "tags", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "#{/\\\\S+/}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/goodreads.3.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "goodreads.3", 3 | "name": "#inbox", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "#inbox", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/hikes.2.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "hikes.2", 3 | "name": "distance", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{number}miles", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/movie-log.2.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "movie-log.2", 3 | "name": "rating", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/⭐+/}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/crm.2.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "crm.2", 3 | "name": "person", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/\\\\n\\\\n/}{/.*/:name}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data/splitwise.3.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "splitwise.3", 3 | "name": "money", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "${number:amount}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/crm.1.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "crm.1", 3 | "name": "phone", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "({number}) {number}-{number}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/movie-log.3.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "movie-log.3", 3 | "name": "tmdb", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "tmdb{/\\\\d+/:id}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/splitwise.3.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "splitwise.3", 3 | "name": "money", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "${number:amount}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/trip-plan.city.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "trip-plan.city", 3 | "name": "city", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{markdown.h1}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data/splitwise.txt: -------------------------------------------------------------------------------- 1 | Split expenses 2 | 6/1/22 3 | scarlett $50 for tickets 4 | sean $20 5 | paul $10 6 | 7 | 6/2/22 8 | sean $50 9 | sean $30 10 | paul $20 11 | 12 | total [scarlett] 13 | total [sean] 14 | total [paul] 15 | 16 | 17 | -------------------------------------------------------------------------------- /sample-data-old/_hCDH5F2LuEr2CrRiS42Gv.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "_hCDH5F2LuEr2CrRiS42Gv", 3 | "name": "time", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "time", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/goodreads.2.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "goodreads.2", 3 | "name": "#read", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "#read-{number:rating}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/pizza-dough.txt: -------------------------------------------------------------------------------- 1 | ooni pizza dough 2 | number of dough balls: 10 3 | ball weight: 40 4 | water %: 50% 5 | salt: 3% 6 | oil: 1% 7 | proof hours: 3 8 | room temp: 75 F 9 | 10 | --- 11 | 12 | Flour: 13 | Water: 14 | Salt: 15 | Yeast: 16 | Oil -------------------------------------------------------------------------------- /sample-data/_hCDH5F2LuEr2CrRiS42Gv.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "_hCDH5F2LuEr2CrRiS42Gv", 3 | "name": "time", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "time", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data/bar.price.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "bar.price", 3 | "name": "price", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{emoji:item} = $ {number:price} ", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data/splitwise.2.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "splitwise.2", 3 | "name": "userMoney", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{name:name} {money:money}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data/splitwise.4.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "splitwise.4", 3 | "name": "name", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/scarlett|yiwen|paul|sean/}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /src/assets/plus-icon.svg: -------------------------------------------------------------------------------- 1 | Add -------------------------------------------------------------------------------- /sample-data-old/bar.price.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "bar.price", 3 | "name": "price", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{emoji:item} = {number:price} Euro", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/splitwise.2.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "splitwise.2", 3 | "name": "userMoney", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{name:name} {money:money}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/splitwise.4.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "splitwise.4", 3 | "name": "name", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/scarlett|yiwen|paul|sean/}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/blocks.block.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "blocks.block", 3 | "name": "block", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/\\\\[(\\\\n|[^\\\\]])+\\\\]/}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/tasktxt.2.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "tasktxt.2", 3 | "name": "startTime", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "[{number:h}:{number:m}:{number:s}]", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/trip-plan.day.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "trip-plan.day", 3 | "name": "day", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/Mon|Tue|Wed|Thu|Fri|Sat|Sun/}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data/common.date.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "common.date", 3 | "name": "dates", 4 | "properties": [ 5 | { 6 | "name": "date", 7 | "formula": "{number:month}/{number:day}/{number:year}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/common.date.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "common.date", 3 | "name": "dates", 4 | "properties": [ 5 | { 6 | "name": "date", 7 | "formula": "{number:month}/{number:day}/{number:year}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/time-tracking.time.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "time-tracking.time", 3 | "name": "time", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{number:hours}:{number:minutes}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /src/assets/chevron-forward.svg: -------------------------------------------------------------------------------- 1 | Chevron Forward -------------------------------------------------------------------------------- /sample-data-old/movie-log.1.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "movie-log.1", 3 | "name": "{dates} {tmdb} {rating} {/.*/}", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{dates} {tmdb} {rating} {/.*/}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/workshop.txt: -------------------------------------------------------------------------------- 1 | Plan schedule of workshop 2 | 3 | make it easy to come up with a schedule and rearrange items, insert new ones 4 | 5 | Start 19:00 6 | 7 | 30 minutes Introduction 8 | 30 minutes Discuss goals 9 | 60 minutes Breakup into groups 10 | 30 minutes Wrap up -------------------------------------------------------------------------------- /sample-data/math-pack.operator.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "math-pack.operator", 3 | "name": "operator", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/\\\\/|\\\\+|\\\\*|\\\\-|\\\\(|\\\\)/}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/math-pack.operator.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "math-pack.operator", 3 | "name": "operator", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/\\\\/|\\\\+|\\\\*|\\\\-|\\\\(|\\\\)/}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data/aeropress.txt: -------------------------------------------------------------------------------- 1 | James Hoffmann Aeropress 2 | ## Recipe 3 | Grind 11 g coffee, medium-fine. 4 | Add 200 g water, brew 2 minutes, plunge! 5 | 6 | scale by 7 | 8 | ## Notes 9 | 6/22/22: Pretty good, but forgot to swirl. 10 | 6/23/22: Felt weak and under-extracted. Grind finer? -------------------------------------------------------------------------------- /sample-data-old/aeropress.txt: -------------------------------------------------------------------------------- 1 | ☕️ James Hoffmann Aeropress 2 | ## Recipe 3 | Grind 11 g coffee, medium-fine. 4 | Add 200 g water, brew 2 minutes, plunge! 5 | 6 | scale by 7 | 8 | ## Notes 9 | 6/22/22: Pretty good, but forgot to swirl. 10 | 6/23/22: Felt weak and under-extracted. Grind finer? -------------------------------------------------------------------------------- /sample-data-old/movie-log.txt: -------------------------------------------------------------------------------- 1 | movie log 2 | fun to use num star emoji as rating although inefficient to input 3 | 4 | 06/12/22 tmdb453395 ⭐⭐ Doctor Strange in the Multiverse of Madness 5 | this movie was better than people said 6 | 06/01/22 tmdb453395 ⭐⭐⭐⭐ Top Gun 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /sample-data-old/goodreads.txt: -------------------------------------------------------------------------------- 1 | goodreads 2 | extracting tags and having rating inside tag 3 | 4 | Seeing like a State #inbox 5 | Whole Earth #read-5 8/1/22 6 | - many lives of Stewart Brand 7 | The Whole Earth Catalog #read-3 8 | A Gentleman in Moscow #read-4 9 | - really enjoyed this book by Amor Towles -------------------------------------------------------------------------------- /sample-data/recipe-pack.txt: -------------------------------------------------------------------------------- 1 | Recipe pack 2 | This pack of searches gives you the following features! 3 | 4 | Durations are turned into timers 5 | 1 minute 6 | 7 | Quantities are recognized 8 | 15 g of flour and 2 cups of water 9 | 10 | which can be scaled by when you use the text "scale by" 11 | scale by -------------------------------------------------------------------------------- /sample-data-old/recipe-pack.txt: -------------------------------------------------------------------------------- 1 | recipe pack 2 | This pack of searches gives you the following features! 3 | 4 | Durations are turned into timers 5 | 1 minute 6 | 7 | Quantities are recognized 8 | 15 g of flour and 2 cups of water 9 | 10 | which can be scaled by when you use the text "scale by" 11 | scale by -------------------------------------------------------------------------------- /src/assets/slider-handle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /sample-data/_1F0eRt4v6Onwr8fDfaXQU.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "_1F0eRt4v6Onwr8fDfaXQU", 3 | "name": "highlightedTeammate", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "Put your own name here to highlight yours specifically: {teammate:teammate}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/_1F0eRt4v6Onwr8fDfaXQU.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "_1F0eRt4v6Onwr8fDfaXQU", 3 | "name": "highlightedTeammate", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "Put your own name here to highlight yours specifically: {teammate:teammate}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/crm.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_hKJvOA9WQ0xR3GLVX1ZDr", 5 | "configId": "crm.1", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_ST62pNjoINl4A0k8zGaBh", 10 | "configId": "crm.2", 11 | "hideHighlightsInDocument": false 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /sample-data/common.emoji.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "common.emoji", 3 | "name": "emoji", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/(\\u00a9|\\u00ae|[\\u2000-\\u3300]|\\ud83c[\\ud000-\\udfff]|\\ud83d[\\ud000-\\udfff]|\\ud83e[\\ud000-\\udfff])/}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data/hikes.1.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "hikes.1", 3 | "name": "hikes", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "🌲", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "col2", 12 | "formula": "TextAfter($)", 13 | "visibility": "HIDDEN" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data/hikes.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_iePFJrk4l7HU0IS6H18Yb", 5 | "configId": "hikes.1", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_RLKSxiJoTh8r5YvnWUzpE", 10 | "configId": "hikes.2", 11 | "hideHighlightsInDocument": false 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /sample-data-old/common.emoji.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "common.emoji", 3 | "name": "emoji", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/(\\u00a9|\\u00ae|[\\u2000-\\u3300]|\\ud83c[\\ud000-\\udfff]|\\ud83d[\\ud000-\\udfff]|\\ud83e[\\ud000-\\udfff])/}", 8 | "visibility": "HIDDEN" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /sample-data-old/hikes.1.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "hikes.1", 3 | "name": "hikes", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "🌲", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "col2", 12 | "formula": "TextAfter($)", 13 | "visibility": "HIDDEN" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data-old/hikes.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_iePFJrk4l7HU0IS6H18Yb", 5 | "configId": "hikes.1", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_RLKSxiJoTh8r5YvnWUzpE", 10 | "configId": "hikes.2", 11 | "hideHighlightsInDocument": false 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /sample-data-old/todos.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_Do0kWwRB2norSG0x4paId", 5 | "configId": "todos.1", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_LyD5a1lA4LFoMpVDuEz2g", 10 | "configId": "todos.2", 11 | "hideHighlightsInDocument": false 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /sample-data-old/welcome.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_sDm7E0wtxnm19VrCbnfFh", 5 | "configId": "youtube", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_ww9wvgHRl83zAO8h9CtoG", 10 | "configId": "markdown", 11 | "hideHighlightsInDocument": true 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /sample-data/common.number.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "common.number", 3 | "name": "number", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/[0-9][0-9\\.]*/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "value", 12 | "formula": "ParseFloat($)", 13 | "visibility": "HIDDEN" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /src/assets/stop-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /sample-data-old/common.number.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "common.number", 3 | "name": "number", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/[0-9][0-9\\.]*/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "value", 12 | "formula": "ParseFloat($)", 13 | "visibility": "HIDDEN" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data-old/plant-watering.txt: -------------------------------------------------------------------------------- 1 | Plant Watering Tracker 2 | ## Plants 3 | 4 | 🌱 Fiddle leaf: every 6 days, last watered on 8/19/2022 5 | 🌱 Montsera: every 4 days, last watered on 8/21/2022 6 | 🌱 Yuzu tree: every 5 days, last watered on 8/17/2022 7 | - The Yuzu tree needs some holes in the pot so that water can drain. 8 | 🌱 Pine Bonsai: every 4 days, last watered on 8/19/2022 9 | -------------------------------------------------------------------------------- /sample-data/plant-watering.txt: -------------------------------------------------------------------------------- 1 | Plant Tracker 2 | 🌱 Fiddle leaf (every 6 days) 3 | - last watered on 8/19/2022 4 | 🌱 Montsera (every 4 days) 5 | - last watered on 8/21/2022 6 | 🌱 Yuzu tree (every 5 days) 7 | - last watered on 8/17/2022 8 | - needs some holes in the pot so that water can drain 9 | 🌱 Pine Bonsai (every 4 days) 10 | - last watered on 8/19/2022 -------------------------------------------------------------------------------- /sample-data-old/stonks-portfolio.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_vYbP2uJkzwO2zAXyV9ZDY", 5 | "configId": "stonks-portfolio.1", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_ns9sZr9wgelKJDzzVezCe", 10 | "configId": "stonks-portfolio.2", 11 | "hideHighlightsInDocument": false 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /sample-data/hikes.2.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "hikes.2", 3 | "name": "distance", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{number:miles} miles", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "km", 12 | "formula": "`= ${Round(miles * 1.60934, 1)} km`", 13 | "visibility": "INLINE" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data/_obENYhG63Nd13x43qDRr0.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "_obENYhG63Nd13x43qDRr0", 3 | "name": "hint", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "ℹ️{/.*/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "background-color", 12 | "formula": "\"#fff9c4\"", 13 | "visibility": "STYLE" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data/youtube.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "youtube", 3 | "name": "video", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "https://www.youtube.com/watch?v={/.{11}/:id}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "col2", 12 | "formula": "YoutubeVideo($, id)", 13 | "visibility": "REPLACE" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /embedded.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Potluck 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /sample-data-old/youtube.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "youtube", 3 | "name": "youtube", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "https://www.youtube.com/watch?v={/.{11}/:id}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "col2", 12 | "formula": "YoutubeVideo($, id)", 13 | "visibility": "REPLACE" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data-old/bar.txt: -------------------------------------------------------------------------------- 1 | bake sale 2 | # Prices 3 | 4 | cake 🍰 = 4 Euro 5 | cupcake 🧁 = 3 Euro 6 | apple juice 🍎 = 2 Euro 7 | coke🥤 = 2 Euro 8 | coffee ☕ = 2 Euro 9 | 10 | # Sales 11 | enter emojis for each order on a new line 12 | 13 | 🍎🥤 14 | 🧁 15 | 🧁☕️ 16 | ☕️ 17 | 🍰 18 | 19 | Cake is too expensive 🍰 = 3 Euros 20 | 21 | 🍰 22 | 23 | Total: 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /sample-data-old/bar.total.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "bar.total", 3 | "name": "sheet2", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "Total: ", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "col7", 12 | "formula": "FindAll(\"sales\").reduce((sum, sale) => sum + sale.data.sum, 0)", 13 | "visibility": "INLINE" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data-old/trip-plan.time.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "trip-plan.time", 3 | "name": "time", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{number:hours}:{number:minutes}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "time", 12 | "formula": "DateTime.fromObject({hours, minutes})", 13 | "visibility": "HIDDEN" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data/_wqlqE65AnWhAK9dX8pm14.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "_wqlqE65AnWhAK9dX8pm14", 3 | "name": "link", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "[{/[^\\\\]]+/:label}]({/[^\\\\)]+/:url})", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "col2", 12 | "formula": "Link($, url, label)", 13 | "visibility": "REPLACE" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data-old/default-searches.txt: -------------------------------------------------------------------------------- 1 | Default Searches 2 | Here are all the searches that are installed by default on docs in Potluck. 3 | 4 | Of course, you can always remove any/all of these from a given doc, and add more searches as well. 5 | 6 | Markdown: **bold**, *italic* 7 | number: 123 8 | duration: 2 minutes 9 | tags: #tag 10 | phone: (123) 456-7890 11 | date: 1/1/22 12 | youtube: https://www.youtube.com/watch?v=jNQXAC9IVRw 13 | -------------------------------------------------------------------------------- /sample-data/_qvqxfvCRfMf4jMbieqxcv.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "_qvqxfvCRfMf4jMbieqxcv", 3 | "name": "search1", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "## Ingredients", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "col2", 12 | "formula": "`\\n${FindAll(\"ingredients\").map(h => TextOfHighlight(h)).join(\"\\n\")}`", 13 | "visibility": "INLINE" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data/common.duration.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "common.duration", 3 | "name": "durations", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/((\\\\d+\\\\s+(hours?|minutes?|seconds?))\\\\s*)*(\\\\d+\\\\s+(hours?|minutes?|seconds?))/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "timer", 12 | "formula": "Timer($)", 13 | "visibility": "INLINE" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data-old/_qvqxfvCRfMf4jMbieqxcv.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "_qvqxfvCRfMf4jMbieqxcv", 3 | "name": "search1", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "## Ingredients", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "col2", 12 | "formula": "`\\n${FindAll(\"ingredients\").map(h => TextOfHighlight(h)).join(\"\\n\")}`", 13 | "visibility": "INLINE" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data-old/common.duration.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "common.duration", 3 | "name": "durations", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/((\\\\d+\\\\s+(hours?|minutes?|seconds?))\\\\s*)*(\\\\d+\\\\s+(hours?|minutes?|seconds?))/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "timer", 12 | "formula": "Timer($)", 13 | "visibility": "INLINE" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data-old/trip-plan.timeSpan.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "trip-plan.timeSpan", 3 | "name": "timeSpan", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{time:start} - {time:end}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "isActive", 12 | "formula": "start.data.time <= DateTime.now() &&\nend.data.time >= DateTime.now()", 13 | "visibility": "HIDDEN" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data/all-ingredients.names.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "all-ingredients.names", 3 | "name": "allIngredients", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "=SplitLines(\",\")", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "officialName", 12 | "formula": "First(Filter(MatchRegexp(\"USDA name: (.*),?\"), SameLine($)))", 13 | "visibility": "HIDDEN" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data-old/all-ingredients.names.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "all-ingredients.names", 3 | "name": "allIngredients", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "=SplitLines(\",\")", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "officialName", 12 | "formula": "First(Filter(MatchRegexp(\"USDA name: (.*),?\"), SameLine($)))", 13 | "visibility": "HIDDEN" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data-old/workshop.time.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "workshop.time", 3 | "name": "time", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{number:hours}:{number:minutes}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "dateTime", 12 | "formula": "DateTime.fromObject({\n hours: ParseInt(hours),\n minutes: ParseInt(minutes)\n})", 13 | "visibility": "HIDDEN" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data/time-tracking.time.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "time-tracking.time", 3 | "name": "time2", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{number:hours}:{number:minutes}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "time", 12 | "formula": "DateTime.fromObject({\n hour: $.data.hours,\n minutes: $.data.minutes\n})", 13 | "visibility": "HIDDEN" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data/workshop.time.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "workshop.time", 3 | "name": "time", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{number:hours}:{number:minutes}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "dateTime", 12 | "formula": "DateTime.fromObject({\n hours: ParseInt(hours),\n minutes: ParseInt(minutes)\n})", 13 | "visibility": "HIDDEN" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /src/assets/pause-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /sample-data-old/trip-plan.txt: -------------------------------------------------------------------------------- 1 | vacation - activities 2 | # La Spezia 3 | 4 | ## Grande Mercato 5 | Local food market, great vegetables and fresh fish 6 | 7 | Open: Mon - Sun 12:00 - 20:00 8 | 9 | # Genua 10 | 11 | ## Acquario di Genova 12 | biggest in europe 13 | book in advance to avoid queue 14 | Price: 27 Euro 15 | Open: Mon - Sun 9:00 - 20:00 16 | 17 | ## Lugano Cathedral 18 | 19 | Open: 20 | Wed - Fri 8:00 - 12:00, 15:00 - 19:00 21 | Mon - Tue 8:00 - 13:00 -------------------------------------------------------------------------------- /sample-data/todos.1.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "todos.1", 3 | "name": "completed", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "[x]", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "task", 12 | "formula": "TextAfter($)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "text-decoration", 17 | "formula": "\"line-through\"", 18 | "visibility": "STYLE" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data-old/stonks-portfolio.1.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "stonks-portfolio.1", 3 | "name": "portfolio value", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "portfolio value", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "col8", 12 | "formula": "FindAll(\"holdings\").map(h => h.data.currentPrice * TextOfHighlight(h.data.shares)).reduce((a, b) => a + b, 0)", 13 | "visibility": "INLINE" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data-old/tasktxt.1.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "tasktxt.1", 3 | "name": "[x]", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "[x]", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "col10", 12 | "formula": "TextAfter($)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "text-decoration", 17 | "formula": "\"line-through\"", 18 | "visibility": "STYLE" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data-old/todos.1.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "todos.1", 3 | "name": "completed", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "[x]", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "task", 12 | "formula": "TextAfter($)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "text-decoration", 17 | "formula": "\"line-through\"", 18 | "visibility": "STYLE" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data/default-searches.txt: -------------------------------------------------------------------------------- 1 | Default Searches 2 | Here are all the searches that are installed by default on docs in Potluck. 3 | 4 | Of course, you can always remove any/all of these from a given doc, and add more searches as well. 5 | 6 | Markdown: **bold**, *italic* 7 | number: 123 8 | duration: 2 minutes 9 | tags: #tag 10 | phone: (123) 456-7890 11 | date: 1/1/22 12 | link [ink & switch](https://inkandswitch.com) 13 | youtube: https://www.youtube.com/watch?v=jNQXAC9IVRw 14 | -------------------------------------------------------------------------------- /sample-data/todos.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_Qd5pNiak8SHmdcMeWiOWv", 5 | "configId": "markdown", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_Do0kWwRB2norSG0x4paId", 10 | "configId": "todos.1", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_LyD5a1lA4LFoMpVDuEz2g", 15 | "configId": "todos.2", 16 | "hideHighlightsInDocument": false 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /sample-data/food.scale.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "food.scale", 3 | "name": "scale", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "scale by", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "slider", 12 | "formula": "Slider($)", 13 | "visibility": "SUPERSCRIPT" 14 | }, 15 | { 16 | "name": "sliderValue", 17 | "formula": "slider.data.value", 18 | "visibility": "INLINE" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data-old/food.scale.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "food.scale", 3 | "name": "scale", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "scale by", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "slider", 12 | "formula": "Slider($)", 13 | "visibility": "SUPERSCRIPT" 14 | }, 15 | { 16 | "name": "sliderValue", 17 | "formula": "slider.data.value", 18 | "visibility": "INLINE" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data-old/_BHxrxC3nSY2cEBjWlaEFW.txt: -------------------------------------------------------------------------------- 1 | Geoffrey's feedback 2 | - hard to find docs because they're sorted alpha not chrono 3 | - hard to copy all the relevant sheets from one doc to another (eg, all the todos stuff.) Requires adding them by name 1 by 1. Also need to learn to use them. Should each highlighter come with directions / examples? Maybe it's time for "packs"? 4 | - bug: FindAll("highlightedTeammate") doesn't work in the meeting notes tracker? 5 | - would be nice to have cmd-b for markdown bold -------------------------------------------------------------------------------- /sample-data-old/bitcoin-price.1.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "bitcoin-price.1", 3 | "name": "test", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "${/[A-Z]+/:currency}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "price", 12 | "formula": "FetchJSON(`https://api.coindesk.com/v1/bpi/currentprice/${TextOfHighlight(currency)}.json`, 5)?.bpi[TextOfHighlight(currency)].rate_float", 13 | "visibility": "INLINE" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /sample-data-old/goodreads.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_gWD0cCuaOLXBxH0g5LLum", 5 | "configId": "goodreads.1", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_NfwjFmOEW90pWR4TAUYR5", 10 | "configId": "goodreads.2", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_gs5UdLFsi9yZ67aSBmbFI", 15 | "configId": "goodreads.3", 16 | "hideHighlightsInDocument": false 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /sample-data-old/pizza.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "NEUs942EyHg11EAbM0NAy", 5 | "configId": "food.ingredient", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "Lnj0VUjOlSZrK39rWUhuh", 10 | "configId": "common.number", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "RmXyk98yDMNoLFZeDwTt2", 15 | "configId": "food.quantity", 16 | "hideHighlightsInDocument": false 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /sample-data/todos.2.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "todos.2", 3 | "name": "incomplete", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "[ ]", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "task", 12 | "formula": "TextAfter($)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "duedate", 17 | "formula": "Filter(FindAll(\"dates\"), a => SameLine($, a))", 18 | "visibility": "HIDDEN" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data-old/todos.2.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "todos.2", 3 | "name": "incomplete", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "[ ]", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "task", 12 | "formula": "TextAfter($)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "duedate", 17 | "formula": "Filter(FindAll(\"dates\"), a => SameLine($, a))", 18 | "visibility": "HIDDEN" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data-old/workout.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_telDp1hoD0hM9mc47lhN8", 5 | "configId": "workout.exercise", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "34sdhiogfz1c6INpAO6Y7", 10 | "configId": "common.number", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "HACBcetciF14iiDndby8U", 15 | "configId": "common.date", 16 | "hideHighlightsInDocument": false 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /sample-data/pizza-dough.inputs.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "pizza-dough.inputs", 3 | "name": "dough inputs", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/[0-9][0-9\\.]*/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "value", 12 | "formula": "ParseFloat($)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "label", 17 | "formula": "TextBefore($)", 18 | "visibility": "HIDDEN" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data-old/pizza-dough.inputs.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "pizza-dough.inputs", 3 | "name": "dough inputs", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/[0-9][0-9\\.]*/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "value", 12 | "formula": "ParseFloat($)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "label", 17 | "formula": "TextBefore($)", 18 | "visibility": "HIDDEN" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data-old/plant-watering.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_j3k9Ygb6rLwbSyd5vinuv", 5 | "configId": "plant-watering.2", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_a8dvDDZ1nZurQsdKLnzjR", 10 | "configId": "plant-watering.1", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_FcKOW7acW3QAUd18PMGAt", 15 | "configId": "markdown", 16 | "hideHighlightsInDocument": true 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /sample-data-old/workshop.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_ELCuyeUPoC9ZD6qbloCCk", 5 | "configId": "workshop.time", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_V9L2URHFvfxK92PthY22T", 10 | "configId": "workshop.duration", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_L2fgDJxV9czPfbYn3PB3E", 15 | "configId": "common.number", 16 | "hideHighlightsInDocument": false 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /sample-data/math-pack.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_ZdLqzeZVmLp5mdwVJIoP7", 5 | "configId": "math-pack.formula", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_YPMob42qojduJMcd8ww5f", 10 | "configId": "math-pack.operator", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_4UesOgfjwlOg0KMa3G8Vu", 15 | "configId": "common.number", 16 | "hideHighlightsInDocument": false 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /sample-data/plant-watering.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_j3k9Ygb6rLwbSyd5vinuv", 5 | "configId": "plant-watering.2", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_a8dvDDZ1nZurQsdKLnzjR", 10 | "configId": "plant-watering.1", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_FcKOW7acW3QAUd18PMGAt", 15 | "configId": "markdown", 16 | "hideHighlightsInDocument": true 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /sample-data-old/math-pack.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_ZdLqzeZVmLp5mdwVJIoP7", 5 | "configId": "math-pack.formula", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_YPMob42qojduJMcd8ww5f", 10 | "configId": "math-pack.operator", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_4UesOgfjwlOg0KMa3G8Vu", 15 | "configId": "common.number", 16 | "hideHighlightsInDocument": false 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /sample-data/stonks-portfolio.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_HiRMeImbNYHY5LnCjALFd", 5 | "configId": "markdown", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_vYbP2uJkzwO2zAXyV9ZDY", 10 | "configId": "stonks-portfolio.1", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_ns9sZr9wgelKJDzzVezCe", 15 | "configId": "stonks-portfolio.2", 16 | "hideHighlightsInDocument": false 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /src/assets/play-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /sample-data-old/_AJ5yvielohvnk3xvY1ea5.txt: -------------------------------------------------------------------------------- 1 | 8/16 meeting 2 | - standup 10 minutes 3 | - writing check-in 10 minutes 4 | - i&s essay guide 5 | - block actions! 10 minutes 6 | - auto-run searches? 10 minutes 7 | 8 | next steps: 9 | 10 | - [ ] persistence improvements / pinned notes / chronological sort @shen 11 | - [ ] add more default highlighters @geoffrey 12 | - [ ] add a simple grouping mechanism (naming convention based) (display grouped in sidebar; add as a group) @shen 13 | - [ ] add readmes for searches 14 | 15 | -------------------------------------------------------------------------------- /sample-data/bar.total.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "bar.total", 3 | "name": "sheet2", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "Total: ", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "total", 12 | "formula": "FindAll(\"sales\").reduce((sum, sale) => sum + sale.data.sum, 0)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "col2", 17 | "formula": "`$ ${total}`", 18 | "visibility": "INLINE" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data/pizza-dough.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "cUDHSgTWKnDpZFr5T7lqY", 5 | "configId": "pizza-dough.inputs", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "FAAxgnKxU0RFIdA75whQf", 10 | "configId": "pizza-dough.number-of-balls", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "OPDhVR1ZH5zXQ51JOCO76", 15 | "configId": "pizza-dough.water", 16 | "hideHighlightsInDocument": false 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /sample-data-old/pizza-dough.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "cUDHSgTWKnDpZFr5T7lqY", 5 | "configId": "pizza-dough.inputs", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "FAAxgnKxU0RFIdA75whQf", 10 | "configId": "pizza-dough.number-of-balls", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "OPDhVR1ZH5zXQ51JOCO76", 15 | "configId": "pizza-dough.water", 16 | "hideHighlightsInDocument": false 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /sample-data-old/ice-cream.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "oOb1B0Bhe4d9xo7FITnNV", 5 | "configId": "food.ingredient", 6 | "highlightSearchRange": [ 7 | 12, 8 | 117 9 | ], 10 | "hideHighlightsInDocument": false 11 | }, 12 | { 13 | "id": "OJfD4K5OMu5qEHvZiRONd", 14 | "configId": "common.number", 15 | "hideHighlightsInDocument": false 16 | }, 17 | { 18 | "id": "dGc1oJZ2MSClD14RZNPe4", 19 | "configId": "food.quantity", 20 | "hideHighlightsInDocument": false 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /sample-data/recipe-pack.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_iKYWj3K4sDt13KilVZVMq", 5 | "configId": "food.scale", 6 | "highlightSearchRange": [ 7 | 217, 8 | 225 9 | ], 10 | "hideHighlightsInDocument": false 11 | }, 12 | { 13 | "id": "_AsFd1yr4rZzvExxiJopfi", 14 | "configId": "food.quantity", 15 | "hideHighlightsInDocument": false 16 | }, 17 | { 18 | "id": "_OMwt6fLRimCRHPoEniDbN", 19 | "configId": "common.duration", 20 | "hideHighlightsInDocument": false 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /sample-data/time-tracking.open-time.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "time-tracking.open-time", 3 | "name": "openTime", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{time2:time}{/-$/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "end", 12 | "formula": "DateTime.fromJSDate(NowDate()).toLocaleString(DateTime.TIME_24_SIMPLE)", 13 | "visibility": "INLINE" 14 | }, 15 | { 16 | "name": "col12", 17 | "formula": "TemplateButton($, \"stop\", ` ${end}`)", 18 | "visibility": "INLINE" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data-old/recipe-pack.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_iKYWj3K4sDt13KilVZVMq", 5 | "configId": "food.scale", 6 | "highlightSearchRange": [ 7 | 217, 8 | 225 9 | ], 10 | "hideHighlightsInDocument": false 11 | }, 12 | { 13 | "id": "_AsFd1yr4rZzvExxiJopfi", 14 | "configId": "food.quantity", 15 | "hideHighlightsInDocument": false 16 | }, 17 | { 18 | "id": "_OMwt6fLRimCRHPoEniDbN", 19 | "configId": "common.duration", 20 | "hideHighlightsInDocument": false 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /sample-data-old/time-tracking.open-time.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "time-tracking.open-time", 3 | "name": "openTime", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{time:time}{/-$/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "end", 12 | "formula": "DateTime.fromJSDate(NowDate()).toLocaleString(DateTime.TIME_24_SIMPLE)", 13 | "visibility": "INLINE" 14 | }, 15 | { 16 | "name": "col12", 17 | "formula": "TemplateButton($, \"stop\", ` ${end}`)", 18 | "visibility": "INLINE" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data/_nzNLA8UWxTsnQ01510U1e.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "_nzNLA8UWxTsnQ01510U1e", 3 | "name": "duration2", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{time2:start} - {time2:end}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "hours", 12 | "formula": "$.data.end.data.time\n.diff($.data.start.data.time).toMillis() / 1000 / 60 / 60", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "col4", 17 | "formula": "`${Round(hours, 2)} hours`", 18 | "visibility": "INLINE" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data/stonks-portfolio.1.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "stonks-portfolio.1", 3 | "name": "portfolio value", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "Portfolio value:", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "sum", 12 | "formula": "FindAll(\"holdings\").map(h => h.data.currentPrice * TextOfHighlight(h.data.shares)).reduce((a, b) => a + b, 0)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "col7", 17 | "formula": "`$${sum}`", 18 | "visibility": "INLINE" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data-old/stonks-portfolio.2.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "stonks-portfolio.2", 3 | "name": "holdings", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "${/\\\\w+/:ticker} {number:shares}@${/[\\\\d\\.]+/:cost}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "currentPrice", 12 | "formula": "TextOfHighlight(ticker) === \"GME\" ? 40 : 20", 13 | "visibility": "SUPERSCRIPT" 14 | }, 15 | { 16 | "name": "col10", 17 | "formula": "`$${currentPrice * ParseInt(shares)}`", 18 | "visibility": "INLINE" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data-old/tasktxt.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_2DSwSYTXo80t4t73tzSBP", 5 | "configId": "tasktxt.1", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_J6Co9yyWhKW8Z4OF3zA3x", 10 | "configId": "tasktxt.2", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_b4gF5DmbOcg95YEM7ffLt", 15 | "configId": "tasktxt.3", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_aM6LJPm8p0uhoyIKVH6sj", 20 | "configId": "tasktxt.4", 21 | "hideHighlightsInDocument": false 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /sample-data/splitwise.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_OJPBy5ppBRj9TYuDYPy4l", 5 | "configId": "splitwise.1", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_z1eraSVFW8v0jrntqmA9q", 10 | "configId": "splitwise.2", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_AH0O2WKt2k09K6xkjC2so", 15 | "configId": "splitwise.3", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_ErJjBAypL8RM4ojIYcHwb", 20 | "configId": "splitwise.4", 21 | "hideHighlightsInDocument": false 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /sample-data-old/movie-log.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_twysdJf5L2oWng1VP75uJ", 5 | "configId": "movie-log.1", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_jbTqffI9b2LRuqpgmsP9e", 10 | "configId": "movie-log.2", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_YrPYq5mwS7x7Xg6Wtjy0Z", 15 | "configId": "movie-log.3", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_xsbsEirMvVNcOGJc8bdfY", 20 | "configId": "common.date", 21 | "hideHighlightsInDocument": false 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /sample-data-old/splitwise.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_OJPBy5ppBRj9TYuDYPy4l", 5 | "configId": "splitwise.1", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_z1eraSVFW8v0jrntqmA9q", 10 | "configId": "splitwise.2", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_AH0O2WKt2k09K6xkjC2so", 15 | "configId": "splitwise.3", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_ErJjBAypL8RM4ojIYcHwb", 20 | "configId": "splitwise.4", 21 | "hideHighlightsInDocument": false 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /sample-data/workshop.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_97H0IvPoYbnh2SIWrSnDp", 5 | "configId": "markdown", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_ELCuyeUPoC9ZD6qbloCCk", 10 | "configId": "workshop.time", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_V9L2URHFvfxK92PthY22T", 15 | "configId": "workshop.duration", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_L2fgDJxV9czPfbYn3PB3E", 20 | "configId": "common.number", 21 | "hideHighlightsInDocument": false 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /sample-data-old/tasktxt.3.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "tasktxt.3", 3 | "name": "duration", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{number:number}s", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "startTime", 12 | "formula": "First(Filter(FindAll(\"startTime\"), SameLine))", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "button", 17 | "formula": "startTime === undefined ? TemplateButton($, \"start\", () => ` / [${DateTime.now().toLocaleString(DateTime.TIME_24_WITH_SECONDS)}]`) : undefined", 18 | "visibility": "INLINE" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data/welcome.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_u8JDE2Z4TtaNIrzgiaK23", 5 | "configId": "_obENYhG63Nd13x43qDRr0", 6 | "hideHighlightsInDocument": true 7 | }, 8 | { 9 | "id": "_POxhxKIcPl6vD6PhKXlqj", 10 | "configId": "_wqlqE65AnWhAK9dX8pm14", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_sDm7E0wtxnm19VrCbnfFh", 15 | "configId": "youtube", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_ww9wvgHRl83zAO8h9CtoG", 20 | "configId": "markdown", 21 | "hideHighlightsInDocument": true 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /sample-data-old/workout.txt: -------------------------------------------------------------------------------- 1 | workout 2 | Gym 7/20/22 3 | 4 | Bench 30 Squat 40, Maintain next time. 5 | 6 | Gym 7/18/22 7 | 8 | Bench 30kg 10x3 9 | Squat 35kg 10x3 (easy, could increase weights next) 10 | 11 | Gym 7/16/22 12 | 13 | Squat 30kg 10x3 14 | Bench 35kg 10x3 15 | 16 | Try to focus on form more 17 | 18 | Gym 7/13/22 19 | 20 | Squat 30 10x3 21 | Bench 35 10x3 22 | 23 | Gym 7/12/22 24 | 25 | run 10 km 26 | 27 | Squat 30 10x3 28 | Bench 35 10x3 29 | 30 | 7/7/22 gym: elliptical + bench 31 | 32 | 7/6/22 gym: squat 30, bench 30 33 | 34 | 7/4/22 gym: run + squat 35 | 36 | 7/1/22 gym: 37 | 38 | Squat 30 10x3 39 | Bench 35 10x3, felt a bit sore -------------------------------------------------------------------------------- /sample-data/workout.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_6O4u00zmQSuLKgN23bflN", 5 | "configId": "_obENYhG63Nd13x43qDRr0", 6 | "hideHighlightsInDocument": true 7 | }, 8 | { 9 | "id": "_telDp1hoD0hM9mc47lhN8", 10 | "configId": "workout.exercise", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "34sdhiogfz1c6INpAO6Y7", 15 | "configId": "common.number", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "HACBcetciF14iiDndby8U", 20 | "configId": "common.date", 21 | "hideHighlightsInDocument": false 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /sample-data-old/trip-plan.activity.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "trip-plan.activity", 3 | "name": "activity", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{markdown.h2}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "city", 12 | "formula": "PrevOfType($, \"city\")", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "openingHours", 17 | "formula": "NextOfType($, \"openingHours\")", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "isOpen", 22 | "formula": "openingHours.data.isOpen", 23 | "visibility": "HIDDEN" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /sample-data-old/blocks.example-ingredient.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "blocks.example-ingredient", 3 | "name": "exampleIngredient", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/eggs|milk|butter|tofu|tomatoes/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "aisle", 12 | "formula": "{\n \"milk\" : \"dairy\",\n \"butter\": \"dairy\",\n \"tomatoes\": \"produce\"\n}[$] || \"other\"", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "isVegan", 17 | "formula": "{\n \"milk\" : false,\n \"butter\": false\n}[$] !== false", 18 | "visibility": "HIDDEN" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /src/refactor/cleanup-highlighters.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | import { readDocumentSheets } from './utils.mjs' 4 | 5 | const watchPath = process.argv[2] 6 | const fullPath = path.resolve(process.cwd(), watchPath) 7 | 8 | const documentSheets = readDocumentSheets(watchPath) 9 | 10 | fs.readdirSync(fullPath).forEach(file => { 11 | if ( 12 | file.startsWith('_') && 13 | file.endsWith('.highlighter') 14 | ) { 15 | const configId = path.basename(file, '.highlighter') 16 | 17 | if (documentSheets.every((sheet) => sheet.configId !== configId)) { 18 | fs.unlinkSync(path.join(fullPath, file)) 19 | } 20 | } 21 | }) -------------------------------------------------------------------------------- /sample-data/plant-watering.1.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "plant-watering.1", 3 | "name": "interval", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "every {number:interval} days", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "intervalInt", 12 | "formula": "ParseInt(interval)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "daysSince", 17 | "formula": "Round((Date.now() - Date.parse(dates)) / (24 * 60 * 60 * 1000), 1)", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "needsWater", 22 | "formula": "daysSince > intervalInt", 23 | "visibility": "HIDDEN" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /sample-data-old/plant-watering.1.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "plant-watering.1", 3 | "name": "interval", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "every {number:interval} days", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "intervalInt", 12 | "formula": "ParseInt(interval)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "daysSince", 17 | "formula": "Round((Date.now() - Date.parse(dates)) / (24 * 60 * 60 * 1000), 1)", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "needsWater", 22 | "formula": "daysSince > intervalInt", 23 | "visibility": "HIDDEN" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /sample-data/splitwise.1.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "splitwise.1", 3 | "name": "output", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "total [{name:name}]", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "userItems", 12 | "formula": "$.allPrev(\"userMoney\")\n.filter((userMoney) => (\n userMoney.data.name.isEqualTo(name)\n))", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "sum", 17 | "formula": "userItems\n.sumOf(\"data.money.data.amount\")", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "sumText", 22 | "formula": "`$${sum}`", 23 | "visibility": "INLINE" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /sample-data-old/splitwise.1.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "splitwise.1", 3 | "name": "output", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "total [{name:name}]", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "userItems", 12 | "formula": "$.allPrev(\"userMoney\")\n.filter((userMoney) => (\n userMoney.data.name.isEqualTo(name)\n))", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "sum", 17 | "formula": "userItems\n.sumOf(\"data.money.data.amount\")", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "sumText", 22 | "formula": "`$${sum}`", 23 | "visibility": "INLINE" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /sample-data/food.quantity.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "food.quantity", 3 | "name": "quantity", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{number:amount} {/(cup|tablespoon|tbsp|teaspoon|tsp|pound|lb|gram|g|milliliter|ml)s?/:unit}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "scaleFactor", 12 | "formula": "Find(\"scale\")?.data.sliderValue", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "scaledAmount", 17 | "formula": "(scaleFactor && scaleFactor !== 1 && amount) ? `${scaleFactor * amount.data.value} ${unit}${amount.data.value === 1 ? 's' : ''}` : undefined", 18 | "visibility": "REPLACE" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data-old/food.quantity.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "food.quantity", 3 | "name": "quantity", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{number:amount} {/(cup|tablespoon|tbsp|teaspoon|tsp|pound|lb|gram|g|milliliter|ml)s?/:unit}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "scaleFactor", 12 | "formula": "Find(\"scale\")?.data.sliderValue", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "scaledAmount", 17 | "formula": "(scaleFactor && scaleFactor !== 1 && amount) ? `${scaleFactor * amount.data.value} ${unit}${amount.data.value === 1 ? 's' : ''}` : undefined", 18 | "visibility": "REPLACE" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /sample-data-old/trip-plan.openingHours.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "trip-plan.openingHours", 3 | "name": "openingHours", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "Open:", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "openingRange", 12 | "formula": "Filter(NextUntil($, HasType(\"markdown\")), HasType(\"openingRange\"))", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "isOpen", 17 | "formula": "openingRange.some(r => r.data.isActive)", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "color", 22 | "formula": "isOpen ? \"green\" : \"red\"", 23 | "visibility": "STYLE" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /sample-data/bar.sales.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "bar.sales", 3 | "name": "sales", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/^/}{emoji+:items}{/$/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "prices", 12 | "formula": "items.map(item => (\n item\n .allPrev(\"price\")\n .find(price => (\n price.data.item.isEqualTo(item)\n ))\n))", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "sum", 17 | "formula": "prices.sumOf(\"data.price\")", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "sumText", 22 | "formula": "`= $ ${sum}`", 23 | "visibility": "INLINE" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /sample-data/_p8Ot2ukI8aaITNa5bY5JU.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "_p8Ot2ukI8aaITNa5bY5JU", 3 | "name": "search2", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "Total:", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "durations", 12 | "formula": "Filter(\n PrevUntil($, HasType(\"markdown\")),\n HasType(\"duration2\")\n)\n", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "total", 17 | "formula": "durations.reduce((sum, duration) => (\n sum + duration.data.hours\n), 0)", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "col3", 22 | "formula": "`${Round(total, 2)} hours`", 23 | "visibility": "INLINE" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /sample-data/workout.txt: -------------------------------------------------------------------------------- 1 | Workout 2 | ℹ️ Click on the search icon in the top right corner of this window to see a calendar view of these workouts 3 | 4 | Gym 7/20/22 5 | 6 | Bench 30 Squat 40, Maintain next time. 7 | 8 | Gym 7/18/22 9 | 10 | Bench 30kg 10x3 11 | Squat 35kg 10x3 (easy, could increase weights next) 12 | 13 | Gym 7/16/22 14 | 15 | Squat 30kg 10x3 16 | Bench 35kg 10x3 17 | 18 | Try to focus on form more 19 | 20 | Gym 7/13/22 21 | 22 | Squat 30 10x3 23 | Bench 35 10x3 24 | 25 | Gym 7/12/22 26 | 27 | run 10 km 28 | 29 | Squat 30 10x3 30 | Bench 35 10x3 31 | 32 | 7/7/22 gym: elliptical + bench 33 | 34 | 7/6/22 gym: squat 30, bench 30 35 | 36 | 7/4/22 gym: run + squat 37 | 38 | 7/1/22 gym: 39 | 40 | Squat 30 10x3 41 | Bench 35 10x3, felt a bit sore -------------------------------------------------------------------------------- /src/embedded-main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from "react-dom/client"; 3 | import * as Tooltip from "@radix-ui/react-tooltip"; 4 | import * as Toast from "@radix-ui/react-toast"; 5 | import {EmbeddedDocument} from "./EmbeddedDocument"; 6 | 7 | const params = new URLSearchParams(location.search) 8 | const documentUrl = params.get('document') 9 | 10 | if (documentUrl) { 11 | ReactDOM.createRoot(document.getElementById("root")!).render( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | 21 | window.top?.postMessage({ type: "demoLoaded", name: documentUrl}) 22 | } -------------------------------------------------------------------------------- /src/refactor/utils.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | 4 | export function readDocumentSheets (basePath) { 5 | return JSON.parse(fs.readFileSync(path.join(basePath, "_documentsheets"), { encoding: 'utf8', flag: 'r' })) 6 | } 7 | 8 | export function writeDocumentSheets (basePath, documentsSheets) { 9 | fs.writeFileSync(path.join(basePath, "_documentsheets"), JSON.stringify(documentsSheets, null, 2)) 10 | } 11 | 12 | export function readHighlighter (basePath, id) { 13 | return JSON.parse(fs.readFileSync(path.join(basePath, `${id}.highlighter`))) 14 | } 15 | 16 | export function writeHighlighter (basePath, id, highlighter) { 17 | fs.writeFileSync(path.join(basePath, `${id}.highlighter`), JSON.stringify(highlighter, null, 2)) 18 | } 19 | 20 | 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | Potluck 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /sample-data-old/blocks.collapse.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "blocks.collapse", 3 | "name": "collapseBlock", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/➡️|⬇️/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "block", 12 | "formula": "$.prev(\"block\")", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "col14", 17 | "formula": "$ == \"⬇️\" ? \"...\" : undefined", 18 | "visibility": "REPLACE" 19 | }, 20 | { 21 | "name": "opposite", 22 | "formula": "$ == \"⬇️\" ? \"➡️\" : \"⬇️\"", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "button", 27 | "formula": "TemplateButton($, $, opposite, \"replace\")", 28 | "visibility": "INLINE" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | const openDocumentId = new URLSearchParams(location.search).get("openDocument") 2 | 3 | 4 | import React from "react"; 5 | import ReactDOM from "react-dom/client"; 6 | import App from "./App"; 7 | import * as Tooltip from "@radix-ui/react-tooltip"; 8 | import * as Toast from "@radix-ui/react-toast"; 9 | import "./index.css"; 10 | import { selectedTextDocumentIdBox, textDocumentsMobx } from "./primitives"; 11 | 12 | if (openDocumentId && textDocumentsMobx.get(openDocumentId)) { 13 | selectedTextDocumentIdBox.set(openDocumentId) 14 | } 15 | 16 | ReactDOM.createRoot(document.getElementById("root")!).render( 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | -------------------------------------------------------------------------------- /sample-data-old/ice-cream.txt: -------------------------------------------------------------------------------- 1 | ice cream 2 | Ingredients 3 | 1¾ cups heavy cream 4 | 2¼ cup whole milk 5 | 3¾ cup sugar 6 | 4⅛ teaspoon fine sea salt 7 | 5 tablespoon vanilla extract 8 | 9 | Instructions 10 | Pour 1 cup of the cream into a saucepan and add the sugar, salt. Scrape the seeds of the vanilla bean into the pot and then add the vanilla pod to the pot. Warm the mixture over medium heat, just until the sugar dissolves. Remove from the heat and add the remaining cream, milk, and vanilla extract (if using extract). Stir to combine and chill in the refrigerator. 11 | When ready to churn, remove the vanilla pod, whisk mixture again and pour into ice cream maker. Churn according to the manufacturer’s instructions. Transfer the finished ice cream to an airtight container and place in the freezer until ready to serve. Enjoy! -------------------------------------------------------------------------------- /sample-data-old/time-tracking.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_Wsf3s8IyOld9knDg1rdnC", 5 | "configId": "common.number", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_1Co3scDP2mNoUzFoYltQ5", 10 | "configId": "markdown", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_H7EWwGZc6qecFzGl0UTWc", 15 | "configId": "time-tracking.project", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_mMM1kgsMOjd4Jtwadj75F", 20 | "configId": "time-tracking.time", 21 | "hideHighlightsInDocument": false 22 | }, 23 | { 24 | "id": "_2gPgKJTGW7qDrcqP7ofMb", 25 | "configId": "time-tracking.open-time", 26 | "hideHighlightsInDocument": false 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /sample-data/markdown.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "markdown", 3 | "name": "markdown", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "=Markdown()", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "type", 12 | "formula": "$.data.type", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "font-weight", 17 | "formula": "type.startsWith(\"h\") || type === \"bold\" ? \"bold\" : \"normal\"", 18 | "visibility": "STYLE" 19 | }, 20 | { 21 | "name": "font-style", 22 | "formula": "type === \"italic\" ? \"italic\" : \"normal\"", 23 | "visibility": "STYLE" 24 | }, 25 | { 26 | "name": "font-size", 27 | "formula": "({\"h1\": \"1.3rem\",\n \"h2\": \"1rem\"}[type]) || \"normal\"", 28 | "visibility": "STYLE" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /src/assets/slider-track.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /sample-data-old/markdown.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "markdown", 3 | "name": "markdown", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "=Markdown()", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "type", 12 | "formula": "$.data.type", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "font-weight", 17 | "formula": "type.startsWith(\"h\") || type === \"bold\" ? \"bold\" : \"normal\"", 18 | "visibility": "STYLE" 19 | }, 20 | { 21 | "name": "font-style", 22 | "formula": "type === \"italic\" ? \"italic\" : \"normal\"", 23 | "visibility": "STYLE" 24 | }, 25 | { 26 | "name": "font-size", 27 | "formula": "({\"h1\": \"1.3rem\",\n \"h2\": \"1rem\"}[type]) || \"normal\"", 28 | "visibility": "STYLE" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /src/assets/asterisk-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sample-data-old/gochujang-pork.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "zUe6BSpguigbb8wqVJ8Jo", 5 | "configId": "food.ingredient", 6 | "highlightSearchRange": [ 7 | 662, 8 | 1430 9 | ], 10 | "hideHighlightsInDocument": false 11 | }, 12 | { 13 | "id": "69ETypGNV9MGLGuYDJVMT", 14 | "configId": "common.number", 15 | "hideHighlightsInDocument": false 16 | }, 17 | { 18 | "id": "rtmeGoGDzu10C1mxqo8rG", 19 | "configId": "food.quantity", 20 | "hideHighlightsInDocument": false 21 | }, 22 | { 23 | "id": "RAknoIAG0p2vDdGvW6Dvi", 24 | "configId": "food.scale", 25 | "hideHighlightsInDocument": false 26 | }, 27 | { 28 | "id": "DNQtQEvwr9jkN8cHFWgeE", 29 | "configId": "common.duration", 30 | "hideHighlightsInDocument": false 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /sample-data/gochujang-pork.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "zUe6BSpguigbb8wqVJ8Jo", 5 | "configId": "food.ingredient", 6 | "highlightSearchRange": [ 7 | 662, 8 | 1430 9 | ], 10 | "hideHighlightsInDocument": false 11 | }, 12 | { 13 | "id": "69ETypGNV9MGLGuYDJVMT", 14 | "configId": "common.number", 15 | "hideHighlightsInDocument": false 16 | }, 17 | { 18 | "id": "rtmeGoGDzu10C1mxqo8rG", 19 | "configId": "food.quantity", 20 | "hideHighlightsInDocument": false 21 | }, 22 | { 23 | "id": "RAknoIAG0p2vDdGvW6Dvi", 24 | "configId": "food.scale", 25 | "hideHighlightsInDocument": false 26 | }, 27 | { 28 | "id": "DNQtQEvwr9jkN8cHFWgeE", 29 | "configId": "common.duration", 30 | "hideHighlightsInDocument": false 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /sample-data/time-tracking.project.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "time-tracking.project", 3 | "name": "project", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{markdown.h1:project}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "time", 12 | "formula": "DateTime.now().toLocaleString(DateTime.TIME_24_SIMPLE)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "unfinishedTime", 17 | "formula": "NextOfType($, \"openTime\", 10)", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "inProgress", 22 | "formula": "unfinishedTime !== undefined", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "startButton", 27 | "formula": "!inProgress ? TemplateButton($, \"start\", `\\n${time} -`) : undefined", 28 | "visibility": "INLINE" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /sample-data-old/time-tracking.project.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "time-tracking.project", 3 | "name": "project", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{markdown.h1:project}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "time", 12 | "formula": "DateTime.now().toLocaleString(DateTime.TIME_24_SIMPLE)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "unfinishedTime", 17 | "formula": "NextOfType($, \"openTime\", 10)", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "inProgress", 22 | "formula": "unfinishedTime !== undefined", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "startButton", 27 | "formula": "!inProgress ? TemplateButton($, \"start\", `\\n${time} -`) : undefined", 28 | "visibility": "INLINE" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /sample-data-old/trip-plan.openingRange.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "trip-plan.openingRange", 3 | "name": "openingRange", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{day:start} - {day:end}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "days", 12 | "formula": "const days = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]\n\nreturn days.slice(\n days.indexOf(start.valueOf()),\n days.indexOf(end.valueOf()) + 1\n)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "timeRanges", 17 | "formula": "Filter(FindAll(\"timeSpan\"), SameLine($))", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "isActive", 22 | "formula": "days.includes(DateTime.now().weekdayShort) &&\ntimeRanges.some(r => r.data.isActive)", 23 | "visibility": "HIDDEN" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /sample-data-old/_EPSPtlS2leXEhRmSEoQZh.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "_EPSPtlS2leXEhRmSEoQZh", 3 | "name": "teammate", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/@shen|@sonnentag|@max|@geoffrey|@pvh/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "font-weight", 12 | "formula": "\"normal\"", 13 | "visibility": "STYLE" 14 | }, 15 | { 16 | "name": "background", 17 | "formula": "\"#eeeeee\"", 18 | "visibility": "STYLE" 19 | }, 20 | { 21 | "name": "padding", 22 | "formula": "\"2px\"", 23 | "visibility": "STYLE" 24 | }, 25 | { 26 | "name": "border-radius", 27 | "formula": "\"5px\"", 28 | "visibility": "STYLE" 29 | }, 30 | { 31 | "name": "highlighted", 32 | "formula": "FindAll(\"highlightedTeammate\")", 33 | "visibility": "HIDDEN" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /sample-data-old/blocks.shuffle.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "blocks.shuffle", 3 | "name": "shuffleBlock", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "🔀", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "block", 12 | "formula": "$.prev(\"block\")", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "lines", 17 | "formula": "SplitLines().filter(line => (\n line.span[0] > block.span[0] &&\n line.span[1] < block.span[1]\n))", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "shuffledLines", 22 | "formula": "lines.sortBy(line => Math.random())", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "button", 27 | "formula": "TemplateButton(block, \"shuffle\", `[\\n${shuffledLines.join(\"\\n\")}\\n]`, \"replace\")", 28 | "visibility": "INLINE" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /sample-data/_EPSPtlS2leXEhRmSEoQZh.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "_EPSPtlS2leXEhRmSEoQZh", 3 | "name": "teammate", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/@shen|@sonnentag|@max|@geoffrey|@pvh/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "font-weight", 12 | "formula": "\"normal\"", 13 | "visibility": "STYLE" 14 | }, 15 | { 16 | "name": "background", 17 | "formula": "\"#eeeeee\"", 18 | "visibility": "STYLE" 19 | }, 20 | { 21 | "name": "padding", 22 | "formula": "\"2px\"", 23 | "visibility": "STYLE" 24 | }, 25 | { 26 | "name": "border-radius", 27 | "formula": "\"5px\"", 28 | "visibility": "STYLE" 29 | }, 30 | { 31 | "name": "highlighted", 32 | "formula": "FindAll(\"highlightedTeammate\")", 33 | "visibility": "HIDDEN" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /sample-data/bar.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_2TRyK9cYdyhE5k2QGQNrX", 5 | "configId": "markdown", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_csZJCgJ3S78PObHxaQCt6", 10 | "configId": "common.emoji", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_M4kQAPANPacHMzSlMrxGj", 15 | "configId": "common.number", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_XnudOhOYOAwZ0O41RC71M", 20 | "configId": "bar.price", 21 | "hideHighlightsInDocument": false 22 | }, 23 | { 24 | "id": "_FByk6n1gPfsxtVnlmbw9g", 25 | "configId": "bar.sales", 26 | "hideHighlightsInDocument": false 27 | }, 28 | { 29 | "id": "_76ua3Ul5NMlnigIHzspSm", 30 | "configId": "bar.total", 31 | "hideHighlightsInDocument": false 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /sample-data-old/bar.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_2TRyK9cYdyhE5k2QGQNrX", 5 | "configId": "markdown", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_csZJCgJ3S78PObHxaQCt6", 10 | "configId": "common.emoji", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_M4kQAPANPacHMzSlMrxGj", 15 | "configId": "common.number", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_XnudOhOYOAwZ0O41RC71M", 20 | "configId": "bar.price", 21 | "hideHighlightsInDocument": false 22 | }, 23 | { 24 | "id": "_FByk6n1gPfsxtVnlmbw9g", 25 | "configId": "bar.sales", 26 | "hideHighlightsInDocument": false 27 | }, 28 | { 29 | "id": "_76ua3Ul5NMlnigIHzspSm", 30 | "configId": "bar.total", 31 | "hideHighlightsInDocument": false 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /sample-data-old/plant-watering.2.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "plant-watering.2", 3 | "name": "schedule", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{dates:lastWatered}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "daysSince", 12 | "formula": "Round((Date.now() - Date.parse(lastWatered)) / (24 * 60 * 60 * 1000), 1)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "needsWater", 17 | "formula": "daysSince > PrevOfType($, \"interval\").data.intervalInt", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "color", 22 | "formula": "needsWater ? \"red\": \"green\"", 23 | "visibility": "STYLE" 24 | }, 25 | { 26 | "name": "button", 27 | "formula": "TemplateButton($, \"🚿\", DateTime.now().toLocaleString(), \"replace\")", 28 | "visibility": "INLINE" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /sample-data/aeropress.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "eiIXzGv8Zq1zybgMZdd1z", 5 | "configId": "common.number", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "X8bBiHSTseJNK4lyNkl2D", 10 | "configId": "food.quantity", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "JdoUlaXUw0gg15KLpYHp6", 15 | "configId": "common.duration", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "fUpyOI1wmdDbCnuq6dNfU", 20 | "configId": "food.scale", 21 | "hideHighlightsInDocument": false 22 | }, 23 | { 24 | "id": "PL4sKTsqJcM18ppBJIviZ", 25 | "configId": "common.date", 26 | "hideHighlightsInDocument": false 27 | }, 28 | { 29 | "id": "Htzihyo06gMLB1lCrEl24", 30 | "configId": "markdown", 31 | "hideHighlightsInDocument": false 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /sample-data/workout.exercise.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "workout.exercise", 3 | "name": "exercise", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/bench|squat|run|elliptical/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "date", 12 | "formula": "PrevOfType($, \"dates\")", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "numbers", 17 | "formula": "Filter(NextUntil($, HasType(\"exercise\")), SameLine($))", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "weight", 22 | "formula": "First(numbers)", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "sets", 27 | "formula": "Second(numbers)", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "reps", 32 | "formula": "Third(numbers)", 33 | "visibility": "HIDDEN" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /sample-data-old/aeropress.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "eiIXzGv8Zq1zybgMZdd1z", 5 | "configId": "common.number", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "X8bBiHSTseJNK4lyNkl2D", 10 | "configId": "food.quantity", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "JdoUlaXUw0gg15KLpYHp6", 15 | "configId": "common.duration", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "fUpyOI1wmdDbCnuq6dNfU", 20 | "configId": "food.scale", 21 | "hideHighlightsInDocument": false 22 | }, 23 | { 24 | "id": "PL4sKTsqJcM18ppBJIviZ", 25 | "configId": "common.date", 26 | "hideHighlightsInDocument": false 27 | }, 28 | { 29 | "id": "Htzihyo06gMLB1lCrEl24", 30 | "configId": "markdown", 31 | "hideHighlightsInDocument": false 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /sample-data-old/blocks.sort.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "blocks.sort", 3 | "name": "sortBlock", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "🔤", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "block", 12 | "formula": "$.prev(\"block\")", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "lines", 17 | "formula": "SplitLines().filter(line => (\n line.span[0] > block.span[0] &&\n line.span[1] < block.span[1]\n))", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "sortedLines", 22 | "formula": "lines.sortBy(line => line.text())", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "button", 27 | "formula": "const text = `[\\n${sortedLines.join(\"\\n\")}\\n]`\n\nreturn TemplateButton(\n block, \"sort\", text, \"replace\"\n)", 28 | "visibility": "INLINE" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /sample-data-old/workout.exercise.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "workout.exercise", 3 | "name": "exercise", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/bench|squat|run|elliptical/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "date", 12 | "formula": "PrevOfType($, \"dates\")", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "numbers", 17 | "formula": "Filter(NextUntil($, HasType(\"exercise\")), SameLine($))", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "weight", 22 | "formula": "First(numbers)", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "sets", 27 | "formula": "Second(numbers)", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "reps", 32 | "formula": "Third(numbers)", 33 | "visibility": "HIDDEN" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /sample-data/plant-watering.2.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "plant-watering.2", 3 | "name": "schedule", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{dates:lastWatered}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "daysSince", 12 | "formula": "Round((Date.now() - Date.parse(lastWatered)) / (24 * 60 * 60 * 1000), 1)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "needsWater", 17 | "formula": "daysSince > PrevOfType($, \"interval\").data.intervalInt", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "color", 22 | "formula": "needsWater ? \"red\": \"green\"", 23 | "visibility": "STYLE" 24 | }, 25 | { 26 | "name": "button", 27 | "formula": "TemplateButton($, \"🚿\", DateTime.now().setLocale('en-US').toLocaleString(), \"replace\")", 28 | "visibility": "INLINE" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /sample-data/default-searches.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_MqeMnBOqDM4Pdej2Xtrpf", 5 | "configId": "_wqlqE65AnWhAK9dX8pm14", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_S9hDqoAlsHf0KIdGkXcsq", 10 | "configId": "youtube", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_WX9HAV8CpFFbJdYW3JijD", 15 | "configId": "common.date", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_HH6NzuEldBXPsvRUWJx7b", 20 | "configId": "markdown", 21 | "hideHighlightsInDocument": false 22 | }, 23 | { 24 | "id": "_gz2GcSLR4lTZudJAKquYN", 25 | "configId": "common.duration", 26 | "hideHighlightsInDocument": false 27 | }, 28 | { 29 | "id": "_sMSYj7m9SZQPciwkYsK40", 30 | "configId": "common.number", 31 | "hideHighlightsInDocument": false 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /sample-data-old/blocks.bla.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "blocks.bla", 3 | "name": "remove", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "⬆️", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "block", 12 | "formula": "$.prev(\"block\")", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "lines", 17 | "formula": "SplitLines().filter(line => (\n line.span[0] > block.span[0] &&\n line.span[1] < block.span[1]\n))", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "first", 22 | "formula": "lines[0]", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "rest", 27 | "formula": "lines.splice(1)", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "button", 32 | "formula": "TemplateButton(block, \"remove first\", `${first}\\n[\\n${rest.join(\"\\n\")}\\n]`, \"replace\")", 33 | "visibility": "INLINE" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /sample-data-old/blocks.remove.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "blocks.remove", 3 | "name": "removeFirstBlock", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "⬆️", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "block", 12 | "formula": "$.prev(\"block\")", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "lines", 17 | "formula": "SplitLines().filter(line => (\n line.span[0] > block.span[0] &&\n line.span[1] < block.span[1]\n))", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "first", 22 | "formula": "lines[0]", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "rest", 27 | "formula": "lines.splice(1)", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "button", 32 | "formula": "TemplateButton(block, \"remove first\", `${first}\\n[\\n${rest.join(\"\\n\")}\\n]`, \"replace\")", 33 | "visibility": "INLINE" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /src/data/officialFoods.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | // import officialFoods from "./officialFoodIdsFiltered.csv"; 3 | // @ts-ignore 4 | // import nutrients from "./nutrient.csv"; 5 | // @ts-ignore 6 | // import measureUnits from "./measure_unit.csv"; 7 | // @ts-ignore 8 | import foodNutrients from "./food_nutrient_filtered.csv"; 9 | 10 | export type OfficialFood = { 11 | fdc_id: string; 12 | data_type: string; 13 | description: string; 14 | food_category_id: string; 15 | publication_date: string; 16 | }; 17 | 18 | export type FoodNutrient = { 19 | id: string; 20 | fdc_id: string; 21 | nutrient_id: string; 22 | amount: string; 23 | data_points: string; 24 | derivation_id: string; 25 | }; 26 | 27 | export const OFFICIAL_FOODS = [] /* officialFoods */ as OfficialFood[]; 28 | export const NUTRIENTS = [] /* nutrients*/ as { [key: string]: string }[]; 29 | export const MEASURE_UNITS = [] /* measureUnits */ as { [key: string]: string }[]; 30 | export const FOOD_NUTRIENTS = [] /* foodNutrients */ as FoodNutrient[]; 31 | -------------------------------------------------------------------------------- /sample-data/stonks-portfolio.2.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "stonks-portfolio.2", 3 | "name": "holdings", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "${/\\\\w+/:ticker} {number:shares}@${/[\\\\d\\.]+/:cost}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "currentPrice", 12 | "formula": "TextOfHighlight(ticker) === \"GME\" ? 40 : 20", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "total", 17 | "formula": "currentPrice * ParseInt(shares)", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "change", 22 | "formula": "Round((currentPrice - cost) / cost, 2)", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "changePercent", 27 | "formula": "change >= 0 ? `+${change * 100}%`\n: `${change * 100}%`", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "col3", 32 | "formula": "`-> $${currentPrice} (${changePercent}) = ${total}`", 33 | "visibility": "INLINE" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /sample-data-old/pizza-dough.water.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "pizza-dough.water", 3 | "name": "water", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "Water:", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "numberOfBalls", 12 | "formula": "FindAll(\"dough inputs\").find(h => TextOfHighlight(h.data.label).includes(\"dough balls\")).data.value", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "water", 17 | "formula": "FindAll(\"dough inputs\").find(h => TextOfHighlight(h.data.label).includes(\"water %\")).data.value / 100", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "gramsPerBall", 22 | "formula": "450", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "total", 27 | "formula": "numberOfBalls * gramsPerBall * water", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "display", 32 | "formula": "`${Math.round(total)} grams`", 33 | "visibility": "INLINE" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /sample-data/pizza-dough.water.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "pizza-dough.water", 3 | "name": "water", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "Water:", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "numberOfBalls", 12 | "formula": "FindAll(\"dough inputs\").find(h => TextOfHighlight(h.data.label).includes(\"dough balls\")).data.value", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "water", 17 | "formula": "FindAll(\"dough inputs\").find(h => TextOfHighlight(h.data.label).includes(\"water %\")).data.value / 100", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "gramsPerBall", 22 | "formula": "450", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "total", 27 | "formula": "numberOfBalls * gramsPerBall * water", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "display", 32 | "formula": "`${Math.round(total)} grams`", 33 | "visibility": "INLINE" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /sample-data-old/default-searches.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_S9hDqoAlsHf0KIdGkXcsq", 5 | "configId": "youtube", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_WX9HAV8CpFFbJdYW3JijD", 10 | "configId": "common.date", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_mEAGvWWAof2XNyFwmgUe0", 15 | "configId": "crm.1", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_HH6NzuEldBXPsvRUWJx7b", 20 | "configId": "markdown", 21 | "hideHighlightsInDocument": false 22 | }, 23 | { 24 | "id": "_r97dmT32ybw8jF0kk7bRw", 25 | "configId": "goodreads.1", 26 | "hideHighlightsInDocument": false 27 | }, 28 | { 29 | "id": "_gz2GcSLR4lTZudJAKquYN", 30 | "configId": "common.duration", 31 | "hideHighlightsInDocument": false 32 | }, 33 | { 34 | "id": "_sMSYj7m9SZQPciwkYsK40", 35 | "configId": "common.number", 36 | "hideHighlightsInDocument": false 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /sample-data-old/blocks.txt: -------------------------------------------------------------------------------- 1 | block actions 2 | # Readme 3 | 4 | You can mark a section with "[" and "]" to turn them into a block. Then you can use different emojis to add actions to that block 5 | 6 | [ 7 | - E 8 | - C 9 | - B 10 | - D 11 | - A 12 | ] 13 | ➡️ collapse the block 14 | 🔤 sort the lines in the block alphabetically 15 | 🔀 shuffles the lines in the block 16 | ⬆️ remove the first item from the block 17 | 🎩 of "type" by "attribute" 18 | - sorting hat will group highlights of *type* by *attribute* 19 | 20 | # Examples 21 | 22 | ## Pick a winner for a raffle 23 | 24 | Collapse the block, then shuffle it and remove the first item to pick a random winner 25 | 26 | [ 27 | - Alex 28 | - Sandra 29 | - Bob 30 | ] 31 | ➡️ 🔀 ⬆️ 32 | 33 | ## Group a shopping list by category 34 | 35 | - tofu 36 | - tomatoes 37 | - 10 eggs 38 | - milk 39 | - butter 40 | 41 | [ 42 | - **other** 43 | - 10 eggs 44 | - tofu 45 | - **produce** 46 | - tomatoes 47 | - **dairy** 48 | - milk 49 | - butter 50 | ] 51 | 🎩 of "exampleIngredient" by "aisle" 52 | 🎩 of "exampleIngredient" by "isVegan" 53 | -------------------------------------------------------------------------------- /sample-data/pizza-dough.number-of-balls.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "pizza-dough.number-of-balls", 3 | "name": "flour", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "Flour:", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "numberOfBalls", 12 | "formula": "FindAll(\"dough inputs\").find(h => TextOfHighlight(h.data.label).includes(\"dough balls\")).data.value", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "water", 17 | "formula": "FindAll(\"dough inputs\").find(h => TextOfHighlight(h.data.label).includes(\"water %\")).data.value / 100", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "gramsPerBall", 22 | "formula": "450", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "total", 27 | "formula": "numberOfBalls * gramsPerBall * (1 - water)", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "display", 32 | "formula": "`${Math.round(total)} grams`", 33 | "visibility": "INLINE" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /sample-data-old/pizza-dough.number-of-balls.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "pizza-dough.number-of-balls", 3 | "name": "flour", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "Flour:", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "numberOfBalls", 12 | "formula": "FindAll(\"dough inputs\").find(h => TextOfHighlight(h.data.label).includes(\"dough balls\")).data.value", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "water", 17 | "formula": "FindAll(\"dough inputs\").find(h => TextOfHighlight(h.data.label).includes(\"water %\")).data.value / 100", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "gramsPerBall", 22 | "formula": "450", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "total", 27 | "formula": "numberOfBalls * gramsPerBall * (1 - water)", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "display", 32 | "formula": "`${Math.round(total)} grams`", 33 | "visibility": "INLINE" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /sample-data/meeting.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_YcmEbl94YLaKvCBmx4EhF", 5 | "configId": "common.duration", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_9t8ilvJ7XPrDn5LM9rcou", 10 | "configId": "time-tracking.time", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_JIXZaoTWFccSUrT7JkXCx", 15 | "configId": "todos.1", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_vAlraHxh3rPGMefQn1BNn", 20 | "configId": "todos.2", 21 | "hideHighlightsInDocument": false 22 | }, 23 | { 24 | "id": "_Pi6GccwquTRbC1uqyfAw5", 25 | "configId": "_1F0eRt4v6Onwr8fDfaXQU", 26 | "hideHighlightsInDocument": false 27 | }, 28 | { 29 | "id": "_9guUeM0PS7Q7Uj5Rmtltt", 30 | "configId": "markdown", 31 | "hideHighlightsInDocument": false 32 | }, 33 | { 34 | "id": "_vr6DjALacbU2J62wnuczP", 35 | "configId": "_EPSPtlS2leXEhRmSEoQZh", 36 | "hideHighlightsInDocument": false 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /sample-data-old/_AJ5yvielohvnk3xvY1ea5.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_VU6isfrUenQk3Wdg05qGc", 5 | "configId": "common.number", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_YDKITIckz6nDms4vHUM8x", 10 | "configId": "common.duration", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_fDC1BebE8R09VEltAN6ld", 15 | "configId": "goodreads.1", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_wRX26NlxkoH38ioBxXncv", 20 | "configId": "markdown", 21 | "hideHighlightsInDocument": false 22 | }, 23 | { 24 | "id": "_RwIa8qsVEEBI1yN9N9JV2", 25 | "configId": "crm.1", 26 | "hideHighlightsInDocument": false 27 | }, 28 | { 29 | "id": "_eTtQylRi3bH4xBKfuZvXk", 30 | "configId": "common.date", 31 | "hideHighlightsInDocument": false 32 | }, 33 | { 34 | "id": "_VZNZA4WGboyKJQgWTXZAI", 35 | "configId": "_EPSPtlS2leXEhRmSEoQZh", 36 | "hideHighlightsInDocument": false 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /sample-data/food.ingredient.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "food.ingredient", 3 | "name": "ingredients", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "=MatchHighlight(DataFromDoc(\"all ingredients\", \"allIngredients\", \"$\"))", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "matched", 12 | "formula": "$.data.matchedHighlight", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "USDA Name", 17 | "formula": "USDAFoodName($)", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "quantity", 22 | "formula": "PrevOfType($, [\"quantity\", \"number\"], 20)", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "scaleFactor", 27 | "formula": "Find(\"scale\")?.data.sliderValue", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "scaledQuantity", 32 | "formula": "(scaleFactor && scaleFactor !== 1 && IsNumber(quantity.valueOf())) ? `${quantity * Round(scaleFactor, 2)} ${$}` : undefined", 33 | "visibility": "REPLACE" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /sample-data-old/_oHItjl0ecKP4fV4fh15L6.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_YcmEbl94YLaKvCBmx4EhF", 5 | "configId": "common.duration", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_9t8ilvJ7XPrDn5LM9rcou", 10 | "configId": "time-tracking.time", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_JIXZaoTWFccSUrT7JkXCx", 15 | "configId": "todos.1", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_vAlraHxh3rPGMefQn1BNn", 20 | "configId": "todos.2", 21 | "hideHighlightsInDocument": false 22 | }, 23 | { 24 | "id": "_Pi6GccwquTRbC1uqyfAw5", 25 | "configId": "_1F0eRt4v6Onwr8fDfaXQU", 26 | "hideHighlightsInDocument": false 27 | }, 28 | { 29 | "id": "_9guUeM0PS7Q7Uj5Rmtltt", 30 | "configId": "markdown", 31 | "hideHighlightsInDocument": false 32 | }, 33 | { 34 | "id": "_vr6DjALacbU2J62wnuczP", 35 | "configId": "_EPSPtlS2leXEhRmSEoQZh", 36 | "hideHighlightsInDocument": false 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /sample-data-old/food.ingredient.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "food.ingredient", 3 | "name": "ingredients", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "=MatchHighlight(DataFromDoc(\"all ingredients\", \"allIngredients\", \"$\"))", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "matched", 12 | "formula": "$.data.matchedHighlight", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "USDA Name", 17 | "formula": "USDAFoodName($)", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "quantity", 22 | "formula": "PrevOfType($, [\"quantity\", \"number\"], 20)", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "scaleFactor", 27 | "formula": "Find(\"scale\")?.data.sliderValue", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "scaledQuantity", 32 | "formula": "(scaleFactor && scaleFactor !== 1 && IsNumber(quantity.valueOf())) ? `${quantity * Round(scaleFactor, 2)} ${$}` : undefined", 33 | "visibility": "REPLACE" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /sample-data/time-tracking.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_EB3s0twrdgiD4cKCOxxfI", 5 | "configId": "_p8Ot2ukI8aaITNa5bY5JU", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_B9uvfnlj7FdGpKkSVm1P9", 10 | "configId": "_nzNLA8UWxTsnQ01510U1e", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_Wsf3s8IyOld9knDg1rdnC", 15 | "configId": "common.number", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_1Co3scDP2mNoUzFoYltQ5", 20 | "configId": "markdown", 21 | "hideHighlightsInDocument": false 22 | }, 23 | { 24 | "id": "_H7EWwGZc6qecFzGl0UTWc", 25 | "configId": "time-tracking.project", 26 | "hideHighlightsInDocument": false 27 | }, 28 | { 29 | "id": "_mMM1kgsMOjd4Jtwadj75F", 30 | "configId": "time-tracking.time", 31 | "hideHighlightsInDocument": false 32 | }, 33 | { 34 | "id": "_2gPgKJTGW7qDrcqP7ofMb", 35 | "configId": "time-tracking.open-time", 36 | "hideHighlightsInDocument": false 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /sample-data-old/bar.sales.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "bar.sales", 3 | "name": "sales", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/^/}{emoji+:items}{/$/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "prices", 12 | "formula": "items.map(item => (\n item\n .allPrev(\"price\")\n .find(price => (\n price.data.item.isEqualTo(item)\n ))\n))", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "sum", 17 | "formula": "prices.sumOf(\"data.price\")", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "sumText", 22 | "formula": "`= ${sum} Euro`", 23 | "visibility": "INLINE" 24 | }, 25 | { 26 | "name": "change", 27 | "formula": "let result = []\n\nif (sum < 5) {\n result.push(` 5 → ${5 - sum}`)\n}\n\nif (sum < 10) {\n result.push(` 10 → ${10 - sum}`)\n}\n\nreturn result.join(', ')", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "col5", 32 | "formula": "HasCursorFocus($) ? change : undefined", 33 | "visibility": "HIDDEN" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /sample-data/workout.workout.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "workout.workout", 3 | "name": "workouts", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/squat|bench|rowing|triceps|elliptical/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "number", 12 | "formula": "Filter(NextUntil($, HasType(\"workouts\")), SameLine($))", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "weight", 17 | "formula": "First(number)", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "reps", 22 | "formula": "Second(number)", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "sets", 27 | "formula": "Third(number)", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "date", 32 | "formula": "PrevOfType($, \"dates\")", 33 | "visibility": "HIDDEN" 34 | }, 35 | { 36 | "name": "total", 37 | "formula": "reps * sets", 38 | "visibility": "HIDDEN" 39 | }, 40 | { 41 | "name": "nextWeight", 42 | "formula": "weight + 5", 43 | "visibility": "HIDDEN" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /sample-data-old/workout.workout.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "workout.workout", 3 | "name": "workouts", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{/squat|bench|rowing|triceps|elliptical/}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "number", 12 | "formula": "Filter(NextUntil($, HasType(\"workouts\")), SameLine($))", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "weight", 17 | "formula": "First(number)", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "reps", 22 | "formula": "Second(number)", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "sets", 27 | "formula": "Third(number)", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "date", 32 | "formula": "PrevOfType($, \"dates\")", 33 | "visibility": "HIDDEN" 34 | }, 35 | { 36 | "name": "total", 37 | "formula": "reps * sets", 38 | "visibility": "HIDDEN" 39 | }, 40 | { 41 | "name": "nextWeight", 42 | "formula": "weight + 5", 43 | "visibility": "HIDDEN" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /sample-data/math-pack.formula.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "math-pack.formula", 3 | "name": "formula", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "=SplitLines()", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "numbers", 12 | "formula": "Filter(FindAll(\"number\"), SameLine($))", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "operators", 17 | "formula": "Filter(FindAll(\"operator\"), SameLine($))", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "formula", 22 | "formula": "numbers.concat(operators)\n .sortBy(h => h.span[0])\n .map(h => h.text())\n .join(\" \")", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "result", 27 | "formula": "eval(formula)", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "showResult", 32 | "formula": "operators.length >= 1 && numbers.length >= 2 &&\nresult !== undefined", 33 | "visibility": "HIDDEN" 34 | }, 35 | { 36 | "name": "sticker", 37 | "formula": "showResult ? `= ${result}` : \"\"", 38 | "visibility": "INLINE" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /sample-data-old/math-pack.formula.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "math-pack.formula", 3 | "name": "formula", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "=SplitLines()", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "numbers", 12 | "formula": "Filter(FindAll(\"number\"), SameLine($))", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "operators", 17 | "formula": "Filter(FindAll(\"operator\"), SameLine($))", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "formula", 22 | "formula": "numbers.concat(operators)\n .sortBy(h => h.span[0])\n .map(h => h.text())\n .join(\" \")", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "result", 27 | "formula": "eval(formula)", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "showResult", 32 | "formula": "operators.length >= 1 && numbers.length >= 2 &&\nresult !== undefined", 33 | "visibility": "HIDDEN" 34 | }, 35 | { 36 | "name": "sticker", 37 | "formula": "showResult ? `= ${result}` : \"\"", 38 | "visibility": "INLINE" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /sample-data/chilli.txt: -------------------------------------------------------------------------------- 1 | Todd's Chili 2 | ## Ingredients 3 | 4 | ## Recipe 5 | Bring 2 pounds low fat (~90/10) ground chuck beef to room temperature, and season with 1 Tbsp onion powder, 2 tsp salt, and 3/8 tsp garlic powder. 6 | 7 | Warm bacon fat or cooking oil in large pot over high heat, and add seasoned meat. Break meat into small pieces, and stir until meat is browned and liquid becomes gravy-like. 8 | 9 | Add 12oz ipa beer or hop water, 8oz can tomato sauce/puree, 3 Tbl ground ancho chili powder, 1 tsp ground cumin, 1 tsp paprika, 1 tsp unsweetened cocoa powder, 1/4 tsp dried oregano, 1/4 tsp ground cayenne pepper, and 1/8 tsp ground cinnamon to meat mixture, and simmer over low heat for 2-3 hours, stirring regularly. 10 | 11 | Add 1/8 Cup diced poblano peppers to mixture, and continue to simmer for 2 hours, stirring regularly. 12 | 13 | Optionally rinse 1 can red kidney beans and 1 can black beans with water and drain. Gently stir beans into mixture, keeping the beans intact. 14 | 15 | Simmer on low until liquid as evaporated. Chili is ready once flavors are blended and texture is to your liking. 16 | 17 | Serve in bowl and garnish to taste with grated cheddar, avocado, sour cream, jalapeño, salsa, tortilla chips, Fritos, or corn bread. -------------------------------------------------------------------------------- /sample-data-old/blocks.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_YR7856hxyyG2NKZsJAzws", 5 | "configId": "blocks.remove", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_saVunZsu1B5sKwfqSIkGb", 10 | "configId": "blocks.block", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_I9PHjLNehuiyq4vYgEYdm", 15 | "configId": "blocks.collapse", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_AP3bdNza4OSrHgfB8V2wz", 20 | "configId": "blocks.shuffle", 21 | "hideHighlightsInDocument": false 22 | }, 23 | { 24 | "id": "_oqy9W8cxf0i1NQD6xhUdb", 25 | "configId": "blocks.sort", 26 | "hideHighlightsInDocument": false 27 | }, 28 | { 29 | "id": "_ZJYg11klR2KoVhlWmeL2W", 30 | "configId": "blocks.sorting-hat", 31 | "hideHighlightsInDocument": false 32 | }, 33 | { 34 | "id": "_CPg91EyQ0EVBMmTCFAGt0", 35 | "configId": "blocks.example-ingredient", 36 | "hideHighlightsInDocument": false 37 | }, 38 | { 39 | "id": "_O2LJ8Q1oZ5OV271idEx3m", 40 | "configId": "markdown", 41 | "hideHighlightsInDocument": false 42 | } 43 | ] 44 | } -------------------------------------------------------------------------------- /sample-data-old/_iiy0UQcJ4SleIwm7XOkYg.txt: -------------------------------------------------------------------------------- 1 | Todd's Chili 2 | ## Ingredients 3 | 4 | ## Recipe 5 | Bring 2 pounds low fat (~90/10) ground chuck beef to room temperature, and season with 1 Tbsp onion powder, 2 tsp salt, and 3/8 tsp garlic powder. 6 | 7 | Warm bacon fat or cooking oil in large pot over high heat, and add seasoned meat. Break meat into small pieces, and stir until meat is browned and liquid becomes gravy-like. 8 | 9 | Add 12oz ipa beer or hop water, 8oz can tomato sauce/puree, 3 Tbl ground ancho chili powder, 1 tsp ground cumin, 1 tsp paprika, 1 tsp unsweetened cocoa powder, 1/4 tsp dried oregano, 1/4 tsp ground cayenne pepper, and 1/8 tsp ground cinnamon to meat mixture, and simmer over low heat for 2-3 hours, stirring regularly. 10 | 11 | Add 1/8 Cup diced poblano peppers to mixture, and continue to simmer for 2 hours, stirring regularly. 12 | 13 | Optionally rinse 1 can red kidney beans and 1 can black beans with water and drain. Gently stir beans into mixture, keeping the beans intact. 14 | 15 | Simmer on low until liquid as evaporated. Chili is ready once flavors are blended and texture is to your liking. 16 | 17 | Serve in bowl and garnish to taste with grated cheddar, avocado, sour cream, jalapeño, salsa, tortilla chips, Fritos, or corn bread. -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, ViteDevServer } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import dsv from "@rollup/plugin-dsv"; 4 | import { resolve } from 'path'; 5 | import analyze from 'rollup-plugin-analyzer' 6 | 7 | // The Vite Chokidar overrides don't actually work unless you write a plugin. 8 | // This plugin overwrites the `ignored` directories for live-reloading. We do 9 | // this to avoid reloads on changes to `sample-data/`. 10 | // 11 | // https://github.com/vitejs/vite/issues/8341 12 | const ignored = () => { 13 | return { 14 | name: "ignored-overrides", 15 | configureServer: (server: ViteDevServer): void => { 16 | server.watcher.options = { 17 | ...server.watcher.options, 18 | ignored: ["**/.git/**", "**/node_modules/**", "**/sample-data/**"], 19 | }; 20 | }, 21 | }; 22 | }; 23 | 24 | // https://vitejs.dev/config/ 25 | export default defineConfig({ 26 | plugins: [react(), dsv(), ignored(), {...analyze(), apply: "build"}], 27 | base: '', 28 | server: { 29 | host: true 30 | }, 31 | build: { 32 | rollupOptions: { 33 | input: { 34 | main: resolve(__dirname, "index.html"), 35 | embedded: resolve(__dirname, "embedded.html") 36 | } 37 | } 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /sample-data-old/blocks.sorting-hat.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "blocks.sorting-hat", 3 | "name": "sortingHat", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "🎩 of {/\"[^\"]*\"/:a} by {/\"[^\"]*\"/:b}", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "type", 12 | "formula": "a.text().slice(1, -1)", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "attr", 17 | "formula": "b.text().slice(1, -1)", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "block", 22 | "formula": "$.prev(\"block\")", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "groupedItems", 27 | "formula": "return FindAll(type).filter(h => (\n h.span[0] > block.span[0] &&\n h.span[1] < block.span[1]\n ))\n .groupBy(h => h.data[attr])\n .map(({group, items}) => {\n const heading = `- **${group}**`\n\n return(\n `${heading}\\n${items.map((item) => (\n ` ${item.wholeLine().text().trimStart()}`\n )).join('\\n')}`\n )\n })\n.join(\"\\n\")\n", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "button", 32 | "formula": "TemplateButton(block, \"group\", `[\\n${groupedItems}\\n]`, \"replace\")", 33 | "visibility": "INLINE" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /sample-data/meeting.txt: -------------------------------------------------------------------------------- 1 | Potluck Weekly Meeting 8/15 2 | ## agenda 3 | Start at SOC 2:00 4 | 5 | 15 minutes chit chat 6 | 60 minutes minutes goals for the week 7 | - Start writing! 8 | - Get more usage: more user tests + larger group session 9 | - Polish + bugfixes 10 | - [ ] @max make the formula + pattern bars look more different 11 | 12 | other topics: 13 | - Related Work 14 | - Potluck + AI 15 | 16 | 15 minutes sync 17 | 18 | ## coffee demo 19 | - it has computed properties + widgets, doesn't have formatting / buttons 20 | - it's pretty involved. make a simpler first step? unit conversion? 21 | - maybe hardcoded scaling is simple enough. 22 | 23 | ## javascript?? 24 | - is it just incidental? or do we lean into the familiarity? 25 | 26 | ## next steps 27 | 28 | - intro: @shen 29 | - design principles: @geoffrey 30 | - demo @max 31 | - findings @sonnentag: 4-6 findings, one sentence per 32 | - related work: @geoffrey (maybe @shen helps) 33 | 34 | Goal: legible artifact by Friday 35 | Non-goal: uniformity of prose 36 | 37 | ## other notes 38 | 39 | - Shen is out Thurs afternoon ~ this week 40 | - chatting w/ Nathan on Tues 41 | 42 | ## instructions for using this doc 43 | Team member names get highlighted: @shen @sonnentag @max @geoffrey 44 | Put your own name here to extra-highlight just your name specifically: @geoffrey (doesn't work, some bug in formulas) -------------------------------------------------------------------------------- /sample-data-old/_oHItjl0ecKP4fV4fh15L6.txt: -------------------------------------------------------------------------------- 1 | Potluck Weekly Meeting 8/15 2 | ## agenda 3 | Start at SOC 2:00 4 | 5 | 15 minutes chit chat 6 | 60 minutes minutes goals for the week 7 | - Start writing! 8 | - Get more usage: more user tests + larger group session 9 | - Polish + bugfixes 10 | - [ ] @max make the formula + pattern bars look more different 11 | 12 | other topics: 13 | - Related Work 14 | - Potluck + AI 15 | 16 | 15 minutes sync 17 | 18 | ## coffee demo 19 | - it has computed properties + widgets, doesn't have formatting / buttons 20 | - it's pretty involved. make a simpler first step? unit conversion? 21 | - maybe hardcoded scaling is simple enough. 22 | 23 | ## javascript?? 24 | - is it just incidental? or do we lean into the familiarity? 25 | 26 | ## next steps 27 | 28 | - intro: @shen 29 | - design principles: @geoffrey 30 | - demo @max 31 | - findings @sonnentag: 4-6 findings, one sentence per 32 | - related work: @geoffrey (maybe @shen helps) 33 | 34 | Goal: legible artifact by Friday 35 | Non-goal: uniformity of prose 36 | 37 | ## other notes 38 | 39 | - Shen is out Thurs afternoon ~ this week 40 | - chatting w/ Nathan on Tues 41 | 42 | ## instructions for using this doc 43 | Team member names get highlighted: @shen @sonnentag @max @geoffrey 44 | Put your own name here to extra-highlight just your name specifically: @geoffrey (doesn't work, some bug in formulas) -------------------------------------------------------------------------------- /sample-data/chilli.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_rBNrhouCsIKScVW4rzoUP", 5 | "configId": "_qvqxfvCRfMf4jMbieqxcv", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_ei1bdWIXkCgYHQLqEU8Pp", 10 | "configId": "food.ingredient", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_OMjCFGo4XlMo8EgXzJbqQ", 15 | "configId": "common.duration", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_8l4M46VNQLL2fS424kG6E", 20 | "configId": "food.quantity", 21 | "hideHighlightsInDocument": false 22 | }, 23 | { 24 | "id": "_QeVX2HaMkKRUOIpwLEER5", 25 | "configId": "food.scale", 26 | "hideHighlightsInDocument": false 27 | }, 28 | { 29 | "id": "_9ZHb9D3b2xcrg4ZPoZ7xX", 30 | "configId": "common.number", 31 | "hideHighlightsInDocument": false 32 | }, 33 | { 34 | "id": "_9yOMzkh3EZ2E1dHYo3yg8", 35 | "configId": "common.duration", 36 | "hideHighlightsInDocument": false 37 | }, 38 | { 39 | "id": "_KHJTAm55FCUQOnhGIpcHz", 40 | "configId": "markdown", 41 | "hideHighlightsInDocument": false 42 | }, 43 | { 44 | "id": "_e7JegGTX8hQPnwfM8HeAU", 45 | "configId": "common.date", 46 | "hideHighlightsInDocument": false 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /sample-data-old/tasktxt.4.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "tasktxt.4", 3 | "name": "task", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "[ ]", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "duration", 12 | "formula": "First(Filter(FindAll(\"duration\"), SameLine))", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "startTime", 17 | "formula": "duration?.data.startTime", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "secondsElapsed", 22 | "formula": "startTime !== undefined\n ? DateTime.fromJSDate(NowDate()).diff(DateTime.fromISO(TextOfHighlight(startTime).substring(1, TextOfHighlight(startTime).length - 1))).as(\"seconds\")\n : undefined", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "totalSeconds", 27 | "formula": "duration !== undefined ? ParseInt(duration.data.number) : undefined", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "col9", 32 | "formula": "secondsElapsed !== undefined ? `${Round(totalSeconds - secondsElapsed)}s left` : undefined", 33 | "visibility": "SUPERSCRIPT" 34 | }, 35 | { 36 | "name": "col12", 37 | "formula": "startTime !== undefined ? TemplateButton($, \"done\", \"[x]\", \"replace\") : undefined", 38 | "visibility": "INLINE" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /src/EmbeddedDocument.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {useState, useEffect} from "react"; 3 | import {observer} from "mobx-react-lite"; 4 | import "./index.css"; 5 | import classNames from "classnames"; 6 | import {ToastViewport} from "@radix-ui/react-toast"; 7 | import {selectedTextDocumentIdBox} from "./primitives"; 8 | import {Editor} from "./Editor" 9 | import {loadDocumentExport} from "./App"; 10 | 11 | export const EmbeddedDocument = observer(({url}: { url: string }) => { 12 | const [isLoading, setIsLoading] = useState(true) 13 | 14 | useEffect(() => { 15 | setIsLoading(true) 16 | 17 | fetch(url) 18 | .then((res) => res.text()) 19 | .then((documentExport) => { 20 | loadDocumentExport(eval(`(() => { return ${documentExport} })()`), true) 21 | setIsLoading(false) 22 | }) 23 | 24 | }, [url]) 25 | 26 | if (isLoading) { 27 | return
28 | } 29 | 30 | const documentId = selectedTextDocumentIdBox.get() 31 | 32 | return ( 33 | <> 34 |
40 | 41 |
42 | 43 | 44 | ) 45 | }) 46 | -------------------------------------------------------------------------------- /sample-data-old/trip-plan.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_InLcuS3AkMLP7bsKI5v6N", 5 | "configId": "trip-plan.activity", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_QgP1hr72eFZyXOKb6GBwM", 10 | "configId": "trip-plan.city", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_rHwaFAMcGPKOYFHMzGvaJ", 15 | "configId": "common.number", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_nAjrSj3fnSHKYYvocIVGd", 20 | "configId": "markdown", 21 | "hideHighlightsInDocument": false 22 | }, 23 | { 24 | "id": "_VDr76rWqTSaAMmFNLpru5", 25 | "configId": "trip-plan.day", 26 | "hideHighlightsInDocument": false 27 | }, 28 | { 29 | "id": "_NKhtuqG94fi030KZVPz1J", 30 | "configId": "trip-plan.openingRange", 31 | "hideHighlightsInDocument": false 32 | }, 33 | { 34 | "id": "_Y179IOu73uTbYWrdxv9bt", 35 | "configId": "trip-plan.time", 36 | "hideHighlightsInDocument": false 37 | }, 38 | { 39 | "id": "_yqGX28lejuaacUHpNFDBh", 40 | "configId": "trip-plan.timeSpan", 41 | "hideHighlightsInDocument": false 42 | }, 43 | { 44 | "id": "_WqCbdCTLROphJfThUc0r9", 45 | "configId": "trip-plan.openingHours", 46 | "hideHighlightsInDocument": false 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Potluck: Dynamic Documents as Personal Software 2 | 3 | ![image](https://github.com/inkandswitch/potluck/assets/934016/d612467b-7c36-4341-bcc5-f4e263e59c3e) 4 | 5 | Today, personal computing is organized around apps: large prefabricated units of software developed by professionals for the masses, with few opportunities for customization. How might we reorient computing so that people can deeply tailor software to meet their unique needs? 6 | 7 | We think a promising workflow is gradual enrichment from docs to apps: starting with regular text documents and incrementally evolving them into interactive software. Potluck is a research prototype that supports this workflow. Users can create live searches that extract structured information from freeform text, write formulas that compute with that information, and then display the results as dynamic annotations in the original document. 8 | 9 | We’ve found that Potluck is versatile enough to build personal tools for managing recipes, workouts, household chores, and more. We share some findings from building these tools, and envision a computational environment where these primitives help people grow documents into personal tools. 10 | 11 | - [**Live demo**](https://www.inkandswitch.com/potluck/demo/) 12 | - [**Research essay**](https://www.inkandswitch.com/potluck/) 13 | 14 | This repo has the code for the Potluck prototype. It's quite usable for a prototype but it's not a polished product or actively maintained. 15 | -------------------------------------------------------------------------------- /sample-data-old/workshop.duration.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "workshop.duration", 3 | "name": "agendaDuration", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{number:minutes} minutes", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "prevDurations", 12 | "formula": "AllPrevOfType($, \"agendaDuration\")", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "prevDateTime", 17 | "formula": "DateTime.fromObject({\n hours: ParseInt($.prev(\"time\").data.hours),\n minutes: ParseInt($.prev(\"time\").data.minutes)\n})", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "startTime", 22 | "formula": "prevDurations\n .reduce((dateTime, duration) => (\n dateTime.plus({\n minutes: ParseInt(duration.data.minutes)\n })\n ),\n prevDateTime)", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "endTime", 27 | "formula": "startTime.plus({ \n minutes: ParseInt(minutes)\n})", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "startTimeText", 32 | "formula": "startTime.toLocaleString(DateTime.TIME_SIMPLE)", 33 | "visibility": "HIDDEN" 34 | }, 35 | { 36 | "name": "endTimeText", 37 | "formula": "endTime.toLocaleString(DateTime.TIME_SIMPLE)", 38 | "visibility": "HIDDEN" 39 | }, 40 | { 41 | "name": "durationText", 42 | "formula": "`${startTimeText} - ${endTimeText}`", 43 | "visibility": "REPLACE" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /sample-data/workshop.duration.highlighter: -------------------------------------------------------------------------------- 1 | { 2 | "id": "workshop.duration", 3 | "name": "agendaDuration", 4 | "properties": [ 5 | { 6 | "name": "$", 7 | "formula": "{number:minutes} minutes", 8 | "visibility": "HIDDEN" 9 | }, 10 | { 11 | "name": "prevDurations", 12 | "formula": "AllPrevOfType($, \"agendaDuration\")", 13 | "visibility": "HIDDEN" 14 | }, 15 | { 16 | "name": "prevDateTime", 17 | "formula": "DateTime.fromObject({\n hours: ParseInt($.prev(\"time\").data.hours),\n minutes: ParseInt($.prev(\"time\").data.minutes)\n})", 18 | "visibility": "HIDDEN" 19 | }, 20 | { 21 | "name": "startTime", 22 | "formula": "prevDurations\n .reduce((dateTime, duration) => (\n dateTime.plus({\n minutes: ParseInt(duration.data.minutes)\n })\n ),\n prevDateTime)", 23 | "visibility": "HIDDEN" 24 | }, 25 | { 26 | "name": "endTime", 27 | "formula": "startTime.plus({ \n minutes: ParseInt(minutes)\n})", 28 | "visibility": "HIDDEN" 29 | }, 30 | { 31 | "name": "startTimeText", 32 | "formula": "startTime.toLocaleString(DateTime.TIME_SIMPLE)", 33 | "visibility": "HIDDEN" 34 | }, 35 | { 36 | "name": "endTimeText", 37 | "formula": "endTime.toLocaleString(DateTime.TIME_SIMPLE)", 38 | "visibility": "HIDDEN" 39 | }, 40 | { 41 | "name": "durationText", 42 | "formula": "`${startTimeText} - ${endTimeText}`", 43 | "visibility": "REPLACE" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /embedded-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | Potluck 14 | 15 | 16 | 17 |
18 | 19 | 21 |
22 | 23 | 25 |
26 | 27 | 29 | 30 |
31 | 32 | 34 | 35 |
36 | 37 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /sample-data-old/_iiy0UQcJ4SleIwm7XOkYg.metadata: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "id": "_rBNrhouCsIKScVW4rzoUP", 5 | "configId": "_qvqxfvCRfMf4jMbieqxcv", 6 | "hideHighlightsInDocument": false 7 | }, 8 | { 9 | "id": "_ei1bdWIXkCgYHQLqEU8Pp", 10 | "configId": "food.ingredient", 11 | "hideHighlightsInDocument": false 12 | }, 13 | { 14 | "id": "_OMjCFGo4XlMo8EgXzJbqQ", 15 | "configId": "common.duration", 16 | "hideHighlightsInDocument": false 17 | }, 18 | { 19 | "id": "_8l4M46VNQLL2fS424kG6E", 20 | "configId": "food.quantity", 21 | "hideHighlightsInDocument": false 22 | }, 23 | { 24 | "id": "_QeVX2HaMkKRUOIpwLEER5", 25 | "configId": "food.scale", 26 | "hideHighlightsInDocument": false 27 | }, 28 | { 29 | "id": "_9ZHb9D3b2xcrg4ZPoZ7xX", 30 | "configId": "common.number", 31 | "hideHighlightsInDocument": false 32 | }, 33 | { 34 | "id": "_9yOMzkh3EZ2E1dHYo3yg8", 35 | "configId": "common.duration", 36 | "hideHighlightsInDocument": false 37 | }, 38 | { 39 | "id": "_9I4ok3wsBr37d5mtoyLwZ", 40 | "configId": "goodreads.1", 41 | "hideHighlightsInDocument": false 42 | }, 43 | { 44 | "id": "_KHJTAm55FCUQOnhGIpcHz", 45 | "configId": "markdown", 46 | "hideHighlightsInDocument": false 47 | }, 48 | { 49 | "id": "_vguDUU4gpZbEVkeyoGDsN", 50 | "configId": "crm.1", 51 | "hideHighlightsInDocument": false 52 | }, 53 | { 54 | "id": "_e7JegGTX8hQPnwfM8HeAU", 55 | "configId": "common.date", 56 | "hideHighlightsInDocument": false 57 | } 58 | ] 59 | } -------------------------------------------------------------------------------- /src/assets/whiteout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /documents/todos.json: -------------------------------------------------------------------------------- 1 | { 2 | "textDocument": { 3 | "id": "todos", 4 | "name": "natto todos", 5 | "text": [ 6 | "simple todo list with conditional formatting", 7 | "", 8 | "[ ] make a gallery view", 9 | "[ ] announce multiplayer 08/15/22", 10 | "[ ] add keyboard shortcuts", 11 | "[x] add zoom ability" 12 | ], 13 | "sheets": [ 14 | { 15 | "id": "_Do0kWwRB2norSG0x4paId", 16 | "configId": "todos.1", 17 | "hideHighlightsInDocument": false 18 | }, 19 | { 20 | "id": "_LyD5a1lA4LFoMpVDuEz2g", 21 | "configId": "todos.2", 22 | "hideHighlightsInDocument": false 23 | } 24 | ] 25 | }, 26 | "sheetConfigs": [ 27 | { 28 | "id": "todos.1", 29 | "name": "completed", 30 | "properties": [ 31 | { 32 | "name": "$", 33 | "formula": "[x]", 34 | "visibility": "HIDDEN" 35 | }, 36 | { 37 | "name": "task", 38 | "formula": "TextAfter($)", 39 | "visibility": "HIDDEN" 40 | }, 41 | { 42 | "name": "text-decoration", 43 | "formula": "\"line-through\"", 44 | "visibility": "STYLE" 45 | } 46 | ] 47 | }, 48 | { 49 | "id": "todos.2", 50 | "name": "incomplete", 51 | "properties": [ 52 | { 53 | "name": "$", 54 | "formula": "[ ]", 55 | "visibility": "HIDDEN" 56 | }, 57 | { 58 | "name": "task", 59 | "formula": "TextAfter($)", 60 | "visibility": "HIDDEN" 61 | }, 62 | { 63 | "name": "duedate", 64 | "formula": "Filter(FindAll(\"dates\"), a => SameLine($, a))", 65 | "visibility": "HIDDEN" 66 | } 67 | ] 68 | } 69 | ] 70 | } -------------------------------------------------------------------------------- /src/NumberSliderComponent.tsx: -------------------------------------------------------------------------------- 1 | import { action, computed, makeObservable, observable } from "mobx"; 2 | import { observer } from "mobx-react-lite"; 3 | import { generateNanoid } from "./utils"; 4 | 5 | type NumberSliderState = { 6 | value: number; 7 | }; 8 | 9 | const NumberSlider = observer( 10 | ({ 11 | state, 12 | range, 13 | }: { 14 | state: NumberSliderState; 15 | range: [from: number, to: number]; 16 | }) => { 17 | return ( 18 |
19 | { 25 | state.value = parseFloat(e.target.value); 26 | })} 27 | className="h-1" 28 | /> 29 |
30 | ); 31 | } 32 | ); 33 | 34 | class NumberSliderComponentData { 35 | constructor(readonly state: NumberSliderState) { 36 | makeObservable(this, { 37 | value: computed, 38 | }); 39 | } 40 | 41 | get value() { 42 | return this.state.value; 43 | } 44 | } 45 | 46 | export class NumberSliderComponent { 47 | id = generateNanoid(); 48 | state: NumberSliderState; 49 | data: NumberSliderComponentData; 50 | 51 | constructor(initialValue: number, readonly range: [number, number]) { 52 | this.state = observable({ 53 | value: initialValue, 54 | }); 55 | this.data = new NumberSliderComponentData(this.state); 56 | } 57 | 58 | render() { 59 | return ; 60 | } 61 | 62 | destroy() {} 63 | } 64 | 65 | export function createNumberSliderComponent( 66 | initialValue: number 67 | ): NumberSliderComponent { 68 | return new NumberSliderComponent(initialValue, [0, 10]); 69 | } 70 | -------------------------------------------------------------------------------- /documents/stonks.json: -------------------------------------------------------------------------------- 1 | { 2 | "textDocument": { 3 | "id": "stonks-portfolio", 4 | "name": "stonks portfolio", 5 | "text": [ 6 | "would like to import outside data", 7 | "doing row-wise computation is pretty straightforward in potluck", 8 | "", 9 | "$GME 250@$5.00", 10 | "$AMC 100@$1.00", 11 | "", 12 | "portfolio value" 13 | ], 14 | "sheets": [ 15 | { 16 | "id": "_vYbP2uJkzwO2zAXyV9ZDY", 17 | "configId": "stonks-portfolio.1", 18 | "hideHighlightsInDocument": false 19 | }, 20 | { 21 | "id": "_ns9sZr9wgelKJDzzVezCe", 22 | "configId": "stonks-portfolio.2", 23 | "hideHighlightsInDocument": false 24 | } 25 | ] 26 | }, 27 | "sheetConfigs": [ 28 | { 29 | "id": "stonks-portfolio.1", 30 | "name": "portfolio value", 31 | "properties": [ 32 | { 33 | "name": "$", 34 | "formula": "portfolio value", 35 | "visibility": "HIDDEN" 36 | }, 37 | { 38 | "name": "col8", 39 | "formula": "FindAll(\"holdings\").map(h => h.data.currentPrice * TextOfHighlight(h.data.shares)).reduce((a, b) => a + b, 0)", 40 | "visibility": "INLINE" 41 | } 42 | ] 43 | }, 44 | { 45 | "id": "stonks-portfolio.2", 46 | "name": "holdings", 47 | "properties": [ 48 | { 49 | "name": "$", 50 | "formula": "${/\\\\w+/:ticker} {number:shares}@${/[\\\\d\\.]+/:cost}", 51 | "visibility": "HIDDEN" 52 | }, 53 | { 54 | "name": "currentPrice", 55 | "formula": "TextOfHighlight(ticker) === \"GME\" ? 40 : 20", 56 | "visibility": "SUPERSCRIPT" 57 | }, 58 | { 59 | "name": "col10", 60 | "formula": "`$${currentPrice * ParseInt(shares)}`", 61 | "visibility": "INLINE" 62 | } 63 | ] 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /src/markdown.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | // @ts-ignore 3 | Prism.languages.markdown = Prism.languages.extend("markup", {}), Prism.languages.insertBefore("markdown", "prolog", { 4 | blockquote: {pattern: /^>(?:[\t ]*>)*/m, alias: "punctuation"}, 5 | code: [{pattern: /^(?: {4}|\t).+/m, alias: "keyword"}, {pattern: /``.+?``|`[^`\n]+`/, alias: "keyword"}], 6 | title: [{ 7 | pattern: /\w+.*(?:\r?\n|\r)(?:==+|--+)/, 8 | alias: "important", 9 | inside: {punctuation: /==+$|--+$/} 10 | }, {pattern: /(^\s*)#+.+/m, lookbehind: !0, alias: "important", inside: {punctuation: /^#+|#+$/}}], 11 | hr: {pattern: /(^\s*)([*-])([\t ]*\2){2,}(?=\s*$)/m, lookbehind: !0, alias: "punctuation"}, 12 | list: {pattern: /(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m, lookbehind: !0, alias: "punctuation"}, 13 | "url-reference": { 14 | pattern: /!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/, 15 | inside: { 16 | variable: {pattern: /^(!?\[)[^\]]+/, lookbehind: !0}, 17 | string: /(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/, 18 | punctuation: /^[\[\]!:]|[<>]/ 19 | }, 20 | alias: "url" 21 | }, 22 | bold: { 23 | pattern: /(^|[^\\])(\*\*|__)(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/, 24 | lookbehind: !0, 25 | inside: {punctuation: /^\*\*|^__|\*\*$|__$/} 26 | }, 27 | italic: { 28 | pattern: /(^|[^\\])([*_])(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/, 29 | lookbehind: !0, 30 | inside: {punctuation: /^[*_]|[*_]$/} 31 | }, 32 | url: { 33 | pattern: /!?\[[^\]]+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)| ?\[[^\]\n]*\])/, 34 | inside: {variable: {pattern: /(!?\[)[^\]]+(?=\]$)/, lookbehind: !0}, string: {pattern: /"(?:\\.|[^"\\])*"(?=\)$)/}} 35 | } 36 | //@ts-ignore 37 | }), Prism.languages.markdown.bold.inside.url = Prism.util.clone(Prism.languages.markdown.url), Prism.languages.markdown.italic.inside.url = Prism.util.clone(Prism.languages.markdown.url), Prism.languages.markdown.bold.inside.italic = Prism.util.clone(Prism.languages.markdown.italic), Prism.languages.markdown.italic.inside.bold = Prism.util.clone(Prism.languages.markdown.bold); // prettier-ignore 38 | -------------------------------------------------------------------------------- /sample-data-old/pizza.txt: -------------------------------------------------------------------------------- 1 | sheet pan corn kimchi pizza 2 | Sheet Pan Corn Pizza With Kimchi and Hot Dogs 3 | 4 | 5 Tbsp. extra-virgin olive oil, divided 5 | 1 lb. store-bought pizza dough, room temperature 6 | 1 14.5-oz. can crushed tomatoes 7 | 2 tsp. sugar 8 | 1 cup coarsely chopped drained kimchi, plus juice from jar (optional) 9 | Kosher salt 10 | 8 oz. low-moisture mozzarella, grated 11 | 1 medium green bell pepper, cut into ¼" pieces 12 | 4 all-beef or other hot dogs, sliced into ½" coins 13 | 2 cups corn (from about 2 ears) 14 | 3 scallions, thinly sliced 15 | 16 | Step 1 17 | Coat a large rimmed baking sheet with 4 Tbsp. extra-virgin olive oil. Place 1 lb. store-bought pizza dough, room temperature, in center of baking sheet; using your fingers, gradually stretch dough outward from center until it reaches to edges and into corners of baking sheet. (If dough is too stiff or springs back, cover with an inverted baking sheet or plastic wrap and let rest 10 minutes before trying again. You may need to let dough rest 2 or 3 times.) Cover and let rise in a warm spot until slightly puffy, about 30 minutes. 18 | 19 | Step 2 20 | While the dough is rising, place a rack in lowest position of oven; preheat to 475°. Combine one 14.5-oz. can crushed tomatoes, 2 tsp. sugar, remaining 1 Tbsp. extra-virgin olive oil, and up to ¼ cup kimchi juice (if using) in a small saucepan. Bring to a simmer over medium heat and cook, stirring occasionally, until sauce is slightly reduced, 7–10 minutes. Remove from heat; season with salt. 21 | 22 | Step 3 23 | Uncover dough and scatter 8 oz. low-moisture mozzarella, grated, over, going all the way to the edges. Dollop sauce over (do not spread), then evenly top with 1 medium green bell pepper, cut into ¼" pieces, 4 all-beef or other hot dogs, sliced into ½" coins, 2 cups corn kernels (from about 2 ears), and 1 cup coarsely chopped drained kimchi. 24 | 25 | Step 4 26 | Bake pizza until cheese is melted and crust is golden brown on bottom and sides (lift an edge with a heatproof spatula to check), 22–28 minutes. If crust feels soft or bendy in center, loosely cover pizza with foil and continue to bake 8–10 minutes longer. 27 | 28 | Step 5 29 | To serve, top pizza with 3 scallions, thinly sliced; cut into squares. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "potluck-parser", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview", 9 | "refactor-watcher": "node src/refactor/watcher.mjs sample-data", 10 | "refactor-cleanup": "node src/refactor/cleanup-highlighters.mjs sample-data" 11 | }, 12 | "dependencies": { 13 | "@codemirror/autocomplete": "^6.1.0", 14 | "@codemirror/commands": "^6.0.1", 15 | "@codemirror/lang-javascript": "^6.0.2", 16 | "@codemirror/language": "^6.2.1", 17 | "@codemirror/state": "^6.1.0", 18 | "@codemirror/view": "^6.0.3", 19 | "@radix-ui/react-hover-card": "^0.1.5", 20 | "@radix-ui/react-icons": "^1.1.1", 21 | "@radix-ui/react-popover": "^1.0.0", 22 | "@radix-ui/react-toast": "^1.0.0", 23 | "@radix-ui/react-tooltip": "^1.0.0", 24 | "@rollup/plugin-dsv": "^2.0.3", 25 | "@types/memoizee": "^0.4.8", 26 | "@types/nanoid-dictionary": "^4.2.0", 27 | "browser-fs-access": "^0.31.0", 28 | "classnames": "^2.3.1", 29 | "codemirror": "^6.0.1", 30 | "date-fns": "^2.28.0", 31 | "eventemitter3": "^4.0.7", 32 | "file-dialog": "^0.0.8", 33 | "fuzzyset": "^1.0.7", 34 | "idb-keyval": "^6.2.0", 35 | "lodash": "^4.17.21", 36 | "luxon": "^3.0.1", 37 | "memoizee": "^0.4.15", 38 | "mobx": "^6.6.1", 39 | "mobx-react-lite": "^3.4.0", 40 | "nanoid": "^4.0.0", 41 | "nanoid-dictionary": "^4.3.0", 42 | "ohm-js": "^16.4.0", 43 | "prismjs": "^1.28.0", 44 | "react": "^18.0.0", 45 | "react-big-calendar": "^1.5.0", 46 | "react-dom": "^18.0.0" 47 | }, 48 | "devDependencies": { 49 | "@types/lodash": "^4.14.182", 50 | "@types/luxon": "^3.0.0", 51 | "@types/prismjs": "^1.26.0", 52 | "@types/react": "^18.0.0", 53 | "@types/react-big-calendar": "^0.38.1", 54 | "@types/react-dom": "^18.0.0", 55 | "@vitejs/plugin-react": "^1.3.0", 56 | "autoprefixer": "^10.4.7", 57 | "outdent": "^0.8.0", 58 | "postcss": "^8.4.14", 59 | "prettier": "^2.7.1", 60 | "rollup-plugin-analyzer": "^4.0.0", 61 | "tailwindcss": "^3.1.6", 62 | "typescript": "^4.6.3", 63 | "vite": "^2.9.9", 64 | "watcher": "^1.2.0" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .icon { 6 | display: inline-block; 7 | mask-position: center; 8 | mask-size: contain; 9 | mask-repeat: no-repeat; 10 | width: 1em; 11 | height: 1em; 12 | 13 | -webkit-mask-position: center; 14 | -webkit-mask-size: contain; 15 | -webkit-mask-repeat: no-repeat; 16 | } 17 | 18 | .icon-plus { 19 | mask-image: url("assets/plus-icon.svg"); 20 | -webkit-mask-image: url("assets/plus-icon.svg"); 21 | } 22 | 23 | .icon-asterisk { 24 | mask-image: url('assets/asterisk-icon.svg'); 25 | -webkit-mask-image: url('assets/asterisk-icon.svg'); 26 | } 27 | 28 | .icon-expandable { 29 | transition: transform 100ms ease-in-out; 30 | mask-image: url('assets/chevron-forward.svg'); 31 | -webkit-mask-image: url('assets/chevron-forward.svg'); 32 | } 33 | 34 | .icon-expandable.is-expanded { 35 | transform: rotate(90deg); 36 | } 37 | 38 | .button { 39 | @apply px-3 py-1 bg-blue-500 text-white rounded; 40 | } 41 | 42 | .cm-focused .cm-highlight-selection, 43 | .cm-highlight-hover { 44 | @apply bg-yellow-200 45 | } 46 | 47 | .cm-highlight-replace { 48 | @apply hidden; 49 | } 50 | 51 | .rbc-event:hover { 52 | @apply bg-blue-500; 53 | } 54 | 55 | .cm-highlight { 56 | padding-bottom: 2px; 57 | background-image: url(./assets/splat-underline-2-default.svg); 58 | background-size: 100% 4px; 59 | background-position: bottom; 60 | background-repeat: no-repeat; 61 | } 62 | 63 | input[type="range"] { 64 | @apply h-1 w-14; 65 | background-image: url(./assets/slider-track.svg); 66 | background-repeat: no-repeat; 67 | background-size: contain; 68 | background-position: center center; 69 | -webkit-appearance: none; 70 | } 71 | 72 | input[type="range"]::-webkit-slider-thumb { 73 | @apply h-1.5 w-1.5 rounded-full cursor-ew-resize; 74 | background-image: url(./assets/slider-handle.svg); 75 | background-repeat: no-repeat; 76 | background-size: contain; 77 | -webkit-appearance: none; 78 | } 79 | 80 | .annotation-token { 81 | @apply font-mono text-sm; 82 | color: #1355ff; 83 | background-image: url(./assets/whiteout.svg); 84 | background-size: auto 100%; 85 | background-repeat: repeat-x; 86 | font-family: "Schoolbell", sans-serif; 87 | font-size: 16px; 88 | } 89 | 90 | /* z-index hack */ 91 | 92 | div[data-radix-popper-content-wrapper] { 93 | z-index: 9999 !important; 94 | } -------------------------------------------------------------------------------- /src/assets/splat-underline-2-default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { customAlphabet } from "nanoid"; 2 | import { 3 | HighlightComponent, 4 | Span, 5 | textDocumentsMobx, 6 | } from "./primitives"; 7 | import { alphanumeric } from "nanoid-dictionary"; 8 | import { getComputedSheetValue } from "./compute"; 9 | import { Highlight } from "./highlight"; 10 | 11 | export function doSpansOverlap(a: Span, b: Span) { 12 | return a[0] <= b[1] && b[0] <= a[1]; 13 | } 14 | 15 | export function doesSpanContainOtherSpan(parent: Span, child: Span) { 16 | return parent[0] <= child[0] && parent[1] >= child[1]; 17 | } 18 | 19 | export function doesSpanContainsPosition(span: Span, position: number) { 20 | return span[0] <= position && position < span[1]; 21 | } 22 | 23 | export function getTextForHighlight(highlight: Highlight) { 24 | const textDocument = textDocumentsMobx.get(highlight.documentId); 25 | return textDocument?.text.sliceString(highlight.span[0], highlight.span[1]); 26 | } 27 | 28 | export function getIntValue(value: any) { 29 | if (isValueRowHighlight(value)) { 30 | value = getTextForHighlight(value); 31 | } 32 | 33 | return parseInt(value, 10); 34 | } 35 | 36 | export function isValueRowHighlight(valueRow: any): valueRow is Highlight { 37 | return ( 38 | typeof valueRow === "object" && 39 | "span" in valueRow && 40 | valueRow.span !== undefined 41 | ); 42 | } 43 | 44 | export function isHighlightComponent(value: any): value is HighlightComponent { 45 | return ( 46 | value !== undefined && 47 | value !== null && 48 | typeof value === "object" && 49 | typeof value.render === "function" 50 | ); 51 | } 52 | 53 | export function isNumericish(value: any): boolean { 54 | return ( 55 | typeof value === "number" || 56 | (typeof value === "string" && /^([\d\.])+$/.test(value)) 57 | ); 58 | } 59 | 60 | export function coerceValueToNumber (value: any) : number | typeof NaN { 61 | if (isValueRowHighlight(value)) { 62 | return coerceValueToNumber(getTextForHighlight(value)) 63 | } 64 | 65 | if (typeof value === "number") { 66 | return value 67 | } 68 | 69 | if (isNumericish(value)) { 70 | return parseFloat(value) 71 | } 72 | 73 | return NaN 74 | } 75 | 76 | const _generateNanoid = customAlphabet(alphanumeric) 77 | 78 | export const generateNanoid = () => `_${_generateNanoid()}`; 79 | 80 | export function transformColumnFormula( 81 | formula: string, 82 | isFirstColumn: boolean 83 | ) { 84 | return isFirstColumn 85 | ? formula.startsWith("=") 86 | ? formula.substring(1) 87 | : `MatchPattern("${formula.replaceAll(/"/g, '\\"')}")` 88 | : formula; 89 | } 90 | -------------------------------------------------------------------------------- /src/data/measure_unit.csv: -------------------------------------------------------------------------------- 1 | "id","name" 2 | "1000","cup" 3 | "1001","tablespoon" 4 | "1002","teaspoon" 5 | "1003","liter" 6 | "1004","milliliter" 7 | "1005","cubic inch" 8 | "1006","cubic centimeter" 9 | "1007","gallon" 10 | "1008","pint" 11 | "1009","fl oz" 12 | "1010","paired cooked w" 13 | "1011","paired raw w" 14 | "1012","dripping w" 15 | "1013","bar" 16 | "1014","bird" 17 | "1015","biscuit" 18 | "1016","bottle" 19 | "1017","box" 20 | "1018","breast" 21 | "1019","can" 22 | "1020","chicken" 23 | "1021","chop" 24 | "1022","cookie" 25 | "1023","container" 26 | "1024","cracker" 27 | "1025","drink" 28 | "1026","drumstick" 29 | "1027","fillet" 30 | "1028","fruit" 31 | "1029","large" 32 | "1030","lb" 33 | "1031","leaf" 34 | "1032","leg" 35 | "1033","link" 36 | "1034","links" 37 | "1035","loaf" 38 | "1036","medium" 39 | "1037","muffin" 40 | "1038","oz" 41 | "1039","package" 42 | "1040","packet" 43 | "1041","patty" 44 | "1042","patties" 45 | "1043","piece" 46 | "1044","pieces" 47 | "1045","quart" 48 | "1046","roast" 49 | "1047","sausage" 50 | "1048","scoop" 51 | "1049","serving" 52 | "1050","slice" 53 | "1051","slices" 54 | "1052","small" 55 | "1053","stalk" 56 | "1054","steak" 57 | "1055","stick" 58 | "1056","strip" 59 | "1057","tablet" 60 | "1058","thigh" 61 | "1059","unit" 62 | "1060","wedge" 63 | "1061","orig ckd g" 64 | "1062","orig rw g" 65 | "1063","medallion" 66 | "1064","pie" 67 | "1065","wing" 68 | "1066","back" 69 | "1067","olive" 70 | "1068","pocket" 71 | "1069","order" 72 | "1070","shrimp" 73 | "1071","each" 74 | "1072","filet" 75 | "1073","plantain" 76 | "1074","nugget" 77 | "1075","pretzel" 78 | "1076","corndog" 79 | "1077","spear" 80 | "1078","sandwich" 81 | "1079","tortilla" 82 | "1080","burrito" 83 | "1081","taco" 84 | "1082","tomatoes" 85 | "1083","chips" 86 | "1084","shell" 87 | "1085","bun" 88 | "1086","crust" 89 | "1087","sheet" 90 | "1088","bag" 91 | "1089","bagel" 92 | "1090","bowl" 93 | "1091","breadstick" 94 | "1092","bulb" 95 | "1093","cake" 96 | "1094","carton" 97 | "1095","chunk" 98 | "1096","contents" 99 | "1097","cutlet" 100 | "1098","doughnut" 101 | "1099","egg" 102 | "1100","fish" 103 | "1101","foreshank" 104 | "1102","frankfurter" 105 | "1103","fries" 106 | "1104","head" 107 | "1105","jar" 108 | "1106","loin" 109 | "1107","pancake" 110 | "1108","pizza" 111 | "1109","rack" 112 | "1110","ribs" 113 | "1111","roll" 114 | "1112","shank" 115 | "1113","shoulder" 116 | "1114","skin" 117 | "1115","wafers" 118 | "1116","wrap" 119 | "1117","bunch" 120 | "1118","Tablespoons" 121 | "1119","Banana" 122 | "1120","Onion" 123 | "9999","undetermined" 124 | -------------------------------------------------------------------------------- /sample-data/gochujang-pork.txt: -------------------------------------------------------------------------------- 1 | Gochujang pork 2 | Grilled Gochujang Pork With Fresh Sesame Kimchi 3 | 4 | Pork shoulder is often prepared as a large roast, requiring hours of cooking until it’s tender. But if you slice it thinly and pound it, the meat quickly absorbs this savory gochujang marinade and cooks up in no time. The spicy pork is balanced by a cool and crisp sesame kimchi, eaten fresh like a salad rather than fermented like traditional preparations. Baby bok choy stands in for the usual napa cabbage, and it’s coated in a vibrant sauce of garlic, ginger, gochugaru, fish sauce and nutty sesame oil. Tuck any leftover pork and kimchi into sandwiches the next day, garnished with tomatoes and mayonnaise. 5 | 6 | scale by 7 | 8 | 2 tablespoons gochugaru 9 | 2 tablespoons distilled white vinegar 10 | 2 tablespoons toasted sesame oil 11 | 3 teaspoons grated garlic 12 | 2 teaspoons grated peeled ginger 13 | 1 teaspoon kosher salt (such as Diamond Crystal), plus more for seasoning 14 | ½ teaspoon fish sauce 15 | 1 tablespoon plus ½ teaspoon granulated sugar 16 | 1½ pounds baby bok choy, quartered lengthwise 17 | 3 scallions, halved lengthwise and thinly sliced on the diagonal 18 | 2 tablespoons gochujang (Korean chile paste) 19 | 2 tablespoons neutral oil, such as safflower or canola 20 | 1 tablespoon low-sodium soy sauce 21 | 1 teaspoon ground black pepper, plus more for seasoning 22 | 2 pounds pork shoulder, thinly sliced crosswise and pounded ⅛-inch-thick (see Tip) 23 | 1 large white onion, peeled and sliced into ¼-inch-thick rings 24 | Steamed rice, for serving 25 | 26 | Preparation 27 | Step 1 28 | In a large bowl, combine the gochugaru, vinegar, sesame oil, 1 teaspoon of the garlic, 1 teaspoon of the ginger, 1 teaspoon salt, the fish sauce and ½ teaspoon of the sugar; mix well. Add bok choy and scallions, and toss with your hands, working the sauce in between and all over the leaves. 29 | 30 | Step 2 31 | Heat a grill to medium-high or heat a stovetop griddle pan over medium-high. In a large bowl, combine the gochujang, neutral oil, soy sauce, 1 teaspoon black pepper and the remaining 2 teaspoons garlic, 1 teaspoon ginger and 1 tablespoon sugar; mix well. Very lightly season the pork with salt and pepper. Add pork and onion to the marinade and toss, gently massaging the marinade all over the meat (The meat does not need to rest in the marinade before it is grilled, but it can be marinated for up to 3 hours.) 32 | 33 | Step 3 34 | Grill the pork and onion, in batches if necessary, until nicely charred and caramelized around the edges, and the pork is cooked through, about 3 minutes per side. Transfer to a serving platter. 35 | 36 | Step 4 37 | Serve the grilled pork and onions with the fresh sesame kimchi and rice on the side. -------------------------------------------------------------------------------- /sample-data-old/gochujang-pork.txt: -------------------------------------------------------------------------------- 1 | gochujang pork 2 | Grilled Gochujang Pork With Fresh Sesame Kimchi 3 | 4 | Pork shoulder is often prepared as a large roast, requiring hours of cooking until it’s tender. But if you slice it thinly and pound it, the meat quickly absorbs this savory gochujang marinade and cooks up in no time. The spicy pork is balanced by a cool and crisp sesame kimchi, eaten fresh like a salad rather than fermented like traditional preparations. Baby bok choy stands in for the usual napa cabbage, and it’s coated in a vibrant sauce of garlic, ginger, gochugaru, fish sauce and nutty sesame oil. Tuck any leftover pork and kimchi into sandwiches the next day, garnished with tomatoes and mayonnaise. 5 | 6 | scale by 7 | 8 | 2 tablespoons gochugaru 9 | 2 tablespoons distilled white vinegar 10 | 2 tablespoons toasted sesame oil 11 | 3 teaspoons grated garlic 12 | 2 teaspoons grated peeled ginger 13 | 1 teaspoon kosher salt (such as Diamond Crystal), plus more for seasoning 14 | ½ teaspoon fish sauce 15 | 1 tablespoon plus ½ teaspoon granulated sugar 16 | 1½ pounds baby bok choy, quartered lengthwise 17 | 3 scallions, halved lengthwise and thinly sliced on the diagonal 18 | 2 tablespoons gochujang (Korean chile paste) 19 | 2 tablespoons neutral oil, such as safflower or canola 20 | 1 tablespoon low-sodium soy sauce 21 | 1 teaspoon ground black pepper, plus more for seasoning 22 | 2 pounds pork shoulder, thinly sliced crosswise and pounded ⅛-inch-thick (see Tip) 23 | 1 large white onion, peeled and sliced into ¼-inch-thick rings 24 | Steamed rice, for serving 25 | 26 | Preparation 27 | Step 1 28 | In a large bowl, combine the gochugaru, vinegar, sesame oil, 1 teaspoon of the garlic, 1 teaspoon of the ginger, 1 teaspoon salt, the fish sauce and ½ teaspoon of the sugar; mix well. Add bok choy and scallions, and toss with your hands, working the sauce in between and all over the leaves. 29 | 30 | Step 2 31 | Heat a grill to medium-high or heat a stovetop griddle pan over medium-high. In a large bowl, combine the gochujang, neutral oil, soy sauce, 1 teaspoon black pepper and the remaining 2 teaspoons garlic, 1 teaspoon ginger and 1 tablespoon sugar; mix well. Very lightly season the pork with salt and pepper. Add pork and onion to the marinade and toss, gently massaging the marinade all over the meat (The meat does not need to rest in the marinade before it is grilled, but it can be marinated for up to 3 hours.) 32 | 33 | Step 3 34 | Grill the pork and onion, in batches if necessary, until nicely charred and caramelized around the edges, and the pork is cooked through, about 3 minutes per side. Transfer to a serving platter. 35 | 36 | Step 4 37 | Serve the grilled pork and onions with the fresh sesame kimchi and rice on the side. -------------------------------------------------------------------------------- /src/HighlightHoverCard.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from "mobx-react-lite"; 2 | import { useState } from "react"; 3 | import { textDocumentsMobx } from "./primitives"; 4 | import { getTextForHighlight } from "./utils"; 5 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card"; 6 | import { Highlight } from "./highlight"; 7 | 8 | export const HighlightHoverCardContent = observer( 9 | ({ highlight }: { highlight: Highlight }) => { 10 | const [extraLinesToShow, setExtraLinesToShow] = useState(3); 11 | const textDocument = textDocumentsMobx.get(highlight.documentId); 12 | if (textDocument === undefined) { 13 | return null; 14 | } 15 | const highlightLineStart = textDocument.text.lineAt( 16 | highlight.span[0] 17 | ).number; 18 | const startPosOfHighlightLine = 19 | textDocument.text.line(highlightLineStart).from; 20 | const highlightLineEnd = textDocument.text.lineAt(highlight.span[1]).number; 21 | const extraContextText = textDocument.text.sliceString( 22 | highlight.span[1], 23 | textDocument.text.line( 24 | Math.min(highlightLineEnd + extraLinesToShow, textDocument.text.lines) 25 | ).to 26 | ); 27 | 28 | const startLine = textDocument.text.lineAt(highlight.span[0]).number; 29 | const endLine = textDocument.text.lineAt(highlight.span[1]).number; 30 | 31 | return ( 32 | <> 33 |
34 | {textDocument.name} (Line{" "} 35 | {startLine === endLine ? startLine : `${startLine} - ${endLine}`}) 36 |
37 |
38 | {highlight.span[0] > startPosOfHighlightLine ? ( 39 | 40 | {textDocument.text.sliceString( 41 | startPosOfHighlightLine, 42 | highlight.span[0] 43 | )} 44 | 45 | ) : null} 46 | 47 | {getTextForHighlight(highlight)} 48 | 49 | {extraContextText.length > 0 ? {extraContextText} : null} 50 |
51 |
52 | {highlightLineEnd + extraLinesToShow < textDocument.text.lines ? ( 53 | 61 | ) : null} 62 |
63 | 64 | ); 65 | } 66 | ); 67 | 68 | export function HighlightHoverCard({ 69 | children, 70 | highlight, 71 | }: { 72 | children: React.ReactNode; 73 | highlight: Highlight; 74 | }) { 75 | return ( 76 | 77 | {children} 78 | 83 | 84 | 85 | 86 | 87 | ); 88 | } 89 | -------------------------------------------------------------------------------- /src/refactor/watcher.mjs: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import fs from "node:fs" 3 | import Watcher from 'watcher' 4 | import { readDocumentSheets, readHighlighter, writeDocumentSheets, writeHighlighter } from './utils.mjs' 5 | import outdent from 'outdent' 6 | import { fileURLToPath } from 'url'; 7 | const __filename = fileURLToPath(import.meta.url); 8 | const __dirname = path.dirname(__filename); 9 | 10 | const watchPath = process.argv[2] 11 | const fullPath = path.resolve(process.cwd(), watchPath) 12 | 13 | const watcher = new Watcher(fullPath, { renameDetection: true }) 14 | 15 | watcher.on('rename', (filePath, filePathNext) => { 16 | if (filePath.endsWith('.txt') && filePathNext.endsWith('.txt')) { 17 | onRenameDocument(filePath, filePathNext) 18 | } else if (filePath.endsWith('.highlighter') && filePathNext.endsWith('.highlighter')) { 19 | onRenameHighlighter(filePath, filePathNext) 20 | } 21 | }) 22 | 23 | generateDefaultStateFile() 24 | 25 | function onRenameHighlighter (oldName, newName) { 26 | const oldConfigId = path.basename(oldName, '.highlighter') 27 | const newConfigId = path.basename(newName, '.highlighter') 28 | 29 | console.log(`rename highlighter ${oldConfigId} => ${newConfigId}`) 30 | 31 | const documentsSheets = readDocumentSheets(fullPath) 32 | const highlighter = readHighlighter(fullPath, newConfigId) 33 | 34 | let changeCount = 0 35 | 36 | const refactoredHighlighter = { ...highlighter, id: newConfigId } 37 | const refactoredDocumentsSheet = documentsSheets.map((documentSheet) => { 38 | if (documentSheet.configId !== oldConfigId) { 39 | return documentSheet 40 | } 41 | 42 | changeCount += 1 43 | 44 | return { ...documentSheet, configId: newConfigId } 45 | }) 46 | 47 | console.log(`changed ${changeCount} sheets`) 48 | 49 | writeHighlighter(fullPath, newConfigId, refactoredHighlighter) 50 | writeDocumentSheets(fullPath, refactoredDocumentsSheet) 51 | generateDefaultStateFile() 52 | } 53 | 54 | function onRenameDocument (oldName, newName) { 55 | const oldTextDocumentId = path.basename(oldName, '.txt') 56 | const newTextDocumentId = path.basename(newName, '.txt') 57 | 58 | console.log(`rename document ${oldTextDocumentId} => ${newTextDocumentId}`) 59 | 60 | const documentsSheets = readDocumentSheets(fullPath) 61 | 62 | let changeCount = 0 63 | 64 | const refactoredDocumentsSheet = documentsSheets.map((documentSheet) => { 65 | if (documentSheet.textDocumentId !== oldTextDocumentId) { 66 | return documentSheet 67 | } 68 | 69 | changeCount += 1 70 | 71 | return { ...documentSheet, textDocumentId: newTextDocumentId } 72 | }) 73 | 74 | console.log(`changed ${changeCount} sheets`) 75 | 76 | writeDocumentSheets(fullPath, refactoredDocumentsSheet) 77 | generateDefaultStateFile() 78 | } 79 | 80 | function generateDefaultStateFile () { 81 | console.log('generate DefaultState.ts') 82 | 83 | const imports = [] 84 | const exportEntries = [] 85 | 86 | fs.readdirSync(fullPath).forEach((file, index )=> { 87 | const importName = `file_${index}` 88 | 89 | imports.push(`import ${importName} from "../sample-data/${file}?raw"`) 90 | exportEntries.push(` "/${file}" : ${importName}`) 91 | 92 | }) 93 | 94 | const content = outdent` 95 | // auto generated, do not edit 96 | ${imports.join("\n")} 97 | 98 | export const DefaultFiles = { 99 | ${exportEntries.join(',\n')} 100 | } 101 | ` 102 | 103 | fs.writeFileSync(path.join(__dirname, "../DefaultState.ts"), content) 104 | } -------------------------------------------------------------------------------- /src/SheetCalendar.tsx: -------------------------------------------------------------------------------- 1 | import { Calendar, dateFnsLocalizer, Views } from "react-big-calendar"; 2 | import format from "date-fns/format"; 3 | import parse from "date-fns/parse"; 4 | import startOfWeek from "date-fns/startOfWeek"; 5 | import getDay from "date-fns/getDay"; 6 | import enUS from "date-fns/locale/en-US"; 7 | import "react-big-calendar/lib/css/react-big-calendar.css"; 8 | import { observer } from "mobx-react-lite"; 9 | import { 10 | hoverHighlightsMobx, 11 | SheetConfig, 12 | SheetValueRow, 13 | TextDocument, 14 | PropertyDefinition, 15 | } from "./primitives"; 16 | import { useMemo, useState } from "react"; 17 | import { getIntValue, getTextForHighlight, isValueRowHighlight } from "./utils"; 18 | import addDays from "date-fns/addDays"; 19 | import { action } from "mobx"; 20 | import { HighlightHoverCard } from "./HighlightHoverCard"; 21 | import { SheetViewProps } from "./SheetComponent"; 22 | import { Highlight } from "./highlight"; 23 | 24 | function getDateForRow(row: SheetValueRow) { 25 | const { day, month, year } = 26 | typeof row.data.year === "number" ? row.data : row.data.date.data; 27 | return new Date(2000 + getIntValue(year), getIntValue(month) - 1, getIntValue(day)); 28 | } 29 | 30 | type CalendarEvent = { 31 | id: string; 32 | title: string; 33 | start: Date; 34 | end: Date; 35 | highlight: Highlight; 36 | }; 37 | 38 | function CalendarMonthEvent({ 39 | event, 40 | title, 41 | }: { 42 | event: CalendarEvent; 43 | title: string; 44 | }) { 45 | return ( 46 | 47 |
{ 49 | const childrenHighlights = Object.values( 50 | event.highlight.data 51 | ).flatMap((columnData) => 52 | isValueRowHighlight(columnData) ? [columnData] : [] 53 | ); 54 | hoverHighlightsMobx.replace( 55 | childrenHighlights.length > 0 56 | ? childrenHighlights 57 | : [event.highlight] 58 | ); 59 | })} 60 | onMouseLeave={action(() => { 61 | hoverHighlightsMobx.clear(); 62 | })} 63 | > 64 | {title} 65 |
66 |
67 | ); 68 | } 69 | 70 | export const SheetCalendar = observer( 71 | ({ textDocument, sheetConfig, columns, rows }: SheetViewProps) => { 72 | const localizer = dateFnsLocalizer({ 73 | format, 74 | parse, 75 | startOfWeek, 76 | getDay, 77 | locales: { 78 | "en-US": enUS, 79 | }, 80 | }); 81 | const titleColumnName = sheetConfig.properties[0].name; 82 | const events = useMemo( 83 | () => 84 | rows.map((row, i): CalendarEvent => { 85 | const date = getDateForRow(row); 86 | return { 87 | id: `${i}`, 88 | title: `${getTextForHighlight(row.data[titleColumnName])}`, 89 | start: date, 90 | end: addDays(date, 1), 91 | highlight: row as Highlight, 92 | }; 93 | }), 94 | [rows] 95 | ); 96 | const [defaultDate] = useState(() => getDateForRow(rows[rows.length - 1])); 97 | return ( 98 |
99 | 110 |
111 | ); 112 | } 113 | ); 114 | -------------------------------------------------------------------------------- /src/compute.ts: -------------------------------------------------------------------------------- 1 | import { comparer, computed, IComputedValue } from "mobx"; 2 | import { evaluateSheet } from "./formulas"; 3 | import { 4 | getSheetConfigsOfTextDocument, 5 | selectedTextDocumentIdBox, 6 | SheetConfig, 7 | sheetConfigsMobx, SheetValueRow, 8 | TextDocument, 9 | TextDocumentSheet, 10 | textDocumentsMobx, 11 | } from "./primitives"; 12 | import {Highlight} from "./highlight"; 13 | import { isValueRowHighlight } from "./utils"; 14 | 15 | const getDocumentSheetKey = ( 16 | textDocumentId: string, 17 | sheetConfigId: string, 18 | firstColumnOnly: boolean = false 19 | ) => `${textDocumentId}:${sheetConfigId}${firstColumnOnly ? ":first" : ""}`; 20 | const documentSheetCache: Record> = {}; 21 | 22 | export function getComputedSheetValue( 23 | textDocumentId: string, 24 | sheetConfigId: string, 25 | firstColumnOnly: boolean = false 26 | ): IComputedValue { 27 | const key = getDocumentSheetKey( 28 | textDocumentId, 29 | sheetConfigId, 30 | firstColumnOnly 31 | ); 32 | if (documentSheetCache[key] === undefined) { 33 | documentSheetCache[key] = computed(() => { 34 | const textDocument = textDocumentsMobx.get(textDocumentId); 35 | const sheetConfig = sheetConfigsMobx.get(sheetConfigId); 36 | if (textDocument === undefined || sheetConfig === undefined) { 37 | return []; 38 | } 39 | return evaluateSheet(textDocument, sheetConfig, firstColumnOnly); 40 | }); 41 | } 42 | return documentSheetCache[key]; 43 | } 44 | 45 | const documentValueCache: Record< 46 | string, 47 | IComputedValue<{ [sheetConfigId: string]: SheetValueRow[] }> 48 | > = {}; 49 | export function getComputedDocumentValues( 50 | textDocumentId: string 51 | ): IComputedValue<{ [sheetConfigId: string]: SheetValueRow[] }> { 52 | if (documentValueCache[textDocumentId] === undefined) { 53 | documentValueCache[textDocumentId] = computed(() => { 54 | const textDocument = textDocumentsMobx.get(textDocumentId); 55 | if (textDocument === undefined) { 56 | return {}; 57 | } 58 | const sheetConfigs = getSheetConfigsOfTextDocument(textDocument); 59 | return Object.fromEntries( 60 | sheetConfigs.map((sheetConfig) => [ 61 | sheetConfig.id, 62 | getComputedSheetValue(textDocument.id, sheetConfig.id).get(), 63 | ]) 64 | ); 65 | }); 66 | } 67 | return documentValueCache[textDocumentId]; 68 | } 69 | 70 | export const editorSelectionHighlightsComputed = computed( 71 | () => { 72 | const textDocumentId = selectedTextDocumentIdBox.get(); 73 | const textDocument = textDocumentsMobx.get(textDocumentId); 74 | if (textDocument === undefined) { 75 | return []; 76 | } 77 | const documentValueRows = getComputedDocumentValues(textDocumentId).get(); 78 | return Object.entries(documentValueRows) 79 | .map(([sheetConfigId, sheetValueRows]) => 80 | sheetValueRows.filter((r): r is Highlight => { 81 | return isValueRowHighlight(r); 82 | }) 83 | ) 84 | .flat(); 85 | }, 86 | { equals: comparer.structural } 87 | ); 88 | 89 | // we cheat a little here, for the sheetConfigId we only eval the first column to avoid 90 | // circular dependencies, this is necessary for the formula like NextValuesUntil(activity, HasType("workouts")) 91 | // here we reference workouts in the workout table 92 | export function getComputedHighlightsForDocumentAvoidingCircular( 93 | textDocument: TextDocument, 94 | sheetConfigIdToGetFirstColumnOnly: string 95 | ): IComputedValue { 96 | return computed(() => { 97 | return textDocument.sheets.flatMap((sheet) => 98 | getComputedSheetValue( 99 | textDocument.id, 100 | sheet.configId, 101 | sheet.configId === sheetConfigIdToGetFirstColumnOnly 102 | ) 103 | .get() 104 | .filter((r): r is Highlight => isValueRowHighlight(r)) 105 | ); 106 | }); 107 | } 108 | -------------------------------------------------------------------------------- /documents/plant-tracker.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | const day = 24 * 60 * 60 * 1000 3 | 4 | const date1 = new Date(Date.now() - (7 * day)) 5 | const date2 = new Date(Date.now() - (4 * day)) 6 | const date3 = new Date(Date.now() - (4 * day)) 7 | const date4 = new Date(Date.now() - (4 * day)) 8 | 9 | function dateToString (date) { 10 | return `${(date.getMonth() + 1)}/${date.getDate()}/${date.getFullYear()}` 11 | } 12 | 13 | return { 14 | "textDocument": { 15 | "id": "plant-watering", 16 | "name": "Plant Watering Tracker", 17 | "text": [ 18 | "## Plants", 19 | "", 20 | "🌱 Fiddle leaf: every 6 days, last watered on " + dateToString(date1), 21 | "🌱 Montsera: every 4 days, last watered on " + dateToString(date2), 22 | "🌱 Yuzu tree: every 5 days, last watered on " + dateToString(date3), 23 | "- The Yuzu tree needs some holes in the pot so that water can drain.", 24 | "🌱 Pine Bonsai: every 4 days, last watered on " + dateToString(date4) 25 | ], 26 | "sheets": [ 27 | { 28 | "id": "_j3k9Ygb6rLwbSyd5vinuv", 29 | "configId": "plant-watering.2", 30 | "hideHighlightsInDocument": false 31 | }, 32 | { 33 | "id": "_a8dvDDZ1nZurQsdKLnzjR", 34 | "configId": "plant-watering.1", 35 | "hideHighlightsInDocument": false 36 | }, 37 | { 38 | "id": "_FcKOW7acW3QAUd18PMGAt", 39 | "configId": "markdown", 40 | "hideHighlightsInDocument": true 41 | } 42 | ] 43 | }, 44 | "sheetConfigs": [ 45 | { 46 | "id": "plant-watering.2", 47 | "name": "schedule", 48 | "properties": [ 49 | { 50 | "name": "$", 51 | "formula": "{dates:lastWatered}", 52 | "visibility": "HIDDEN" 53 | }, 54 | { 55 | "name": "daysSince", 56 | "formula": "Round((Date.now() - Date.parse(lastWatered)) / (24 * 60 * 60 * 1000), 1)", 57 | "visibility": "HIDDEN" 58 | }, 59 | { 60 | "name": "needsWater", 61 | "formula": "daysSince > PrevOfType($, \"interval\").data.intervalInt", 62 | "visibility": "HIDDEN" 63 | }, 64 | { 65 | "name": "color", 66 | "formula": "needsWater ? \"red\": \"green\"", 67 | "visibility": "STYLE" 68 | }, 69 | { 70 | "name": "button", 71 | "formula": "TemplateButton($, \"🚿\", DateTime.now().toLocaleString(), \"replace\")", 72 | "visibility": "INLINE" 73 | } 74 | ] 75 | }, 76 | { 77 | "id": "plant-watering.1", 78 | "name": "interval", 79 | "properties": [ 80 | { 81 | "name": "$", 82 | "formula": "every {number:interval} days", 83 | "visibility": "HIDDEN" 84 | }, 85 | { 86 | "name": "intervalInt", 87 | "formula": "ParseInt(interval)", 88 | "visibility": "HIDDEN" 89 | }, 90 | { 91 | "name": "daysSince", 92 | "formula": "Round((Date.now() - Date.parse(dates)) / (24 * 60 * 60 * 1000), 1)", 93 | "visibility": "HIDDEN" 94 | }, 95 | { 96 | "name": "needsWater", 97 | "formula": "daysSince > intervalInt", 98 | "visibility": "HIDDEN" 99 | } 100 | ] 101 | }, 102 | { 103 | "id": "markdown", 104 | "name": "markdown", 105 | "properties": [ 106 | { 107 | "name": "$", 108 | "formula": "=Markdown()", 109 | "visibility": "HIDDEN" 110 | }, 111 | { 112 | "name": "type", 113 | "formula": "$.data.type", 114 | "visibility": "HIDDEN" 115 | }, 116 | { 117 | "name": "font-weight", 118 | "formula": "type.startsWith(\"h\") || type === \"bold\" ? \"bold\" : \"normal\"", 119 | "visibility": "STYLE" 120 | }, 121 | { 122 | "name": "font-style", 123 | "formula": "type === \"italic\" ? \"italic\" : \"normal\"", 124 | "visibility": "STYLE" 125 | }, 126 | { 127 | "name": "font-size", 128 | "formula": "({\"h1\": \"1.3rem\",\n \"h2\": \"1rem\"}[type]) || \"normal\"", 129 | "visibility": "STYLE" 130 | } 131 | ] 132 | } 133 | ] 134 | } 135 | })() -------------------------------------------------------------------------------- /src/highlight.ts: -------------------------------------------------------------------------------- 1 | import { sheetConfigsMobx, SheetValueRow, Span, textDocumentsMobx } from "./primitives"; 2 | import { coerceValueToNumber, getTextForHighlight, isNumericish, isValueRowHighlight } from "./utils"; 3 | import { isNaN, isString, orderBy, get, pick, sortBy, isEqual, groupBy} from "lodash"; 4 | import { getComputedSheetValue } from "./compute"; 5 | 6 | export class Highlight { 7 | constructor( 8 | readonly documentId: string, 9 | readonly sheetConfigId: string, // sheet config id should be optional to represent sub highlights in patterns, for now just set the sheetConfigId to empty string to represent undefined configId 10 | readonly span: Span, 11 | readonly data: { [columnName: string]: any }, 12 | ) { 13 | } 14 | 15 | allPrev(type: string | string[]) { 16 | const typeSheetConfigs = Array.from(sheetConfigsMobx.values()).filter( 17 | (sheetConfig) => 18 | isString(type) 19 | ? sheetConfig.name === type 20 | : type.includes(sheetConfig.name) 21 | ); 22 | 23 | return orderBy( 24 | typeSheetConfigs 25 | .flatMap((typeSheetConfig) => { 26 | const firstColumnOnly = 27 | typeSheetConfig.id === this.sheetConfigId; 28 | 29 | return getComputedSheetValue(this.documentId, typeSheetConfig.id, firstColumnOnly).get() 30 | }) 31 | .filter((row) => ( 32 | "span" in row && 33 | row.span[1] < this.span[0] 34 | )) as Highlight[], 35 | 36 | [({ span }) => span[0]], 37 | ['desc'] 38 | ); 39 | } 40 | 41 | prev(type: string | string[]) { 42 | return this.allPrev(type)[0] 43 | } 44 | 45 | allNext(type: string | string[]) { 46 | const typeSheetConfigs = Array.from(sheetConfigsMobx.values()).filter( 47 | (sheetConfig) => 48 | isString(type) 49 | ? sheetConfig.name === type 50 | : type.includes(sheetConfig.name) 51 | ); 52 | 53 | return orderBy( 54 | typeSheetConfigs 55 | .flatMap((typeSheetConfig) => { 56 | const firstColumnOnly = 57 | typeSheetConfig.id === this.sheetConfigId; 58 | 59 | return getComputedSheetValue(this.documentId, typeSheetConfig.id, firstColumnOnly).get() 60 | }) 61 | .filter((row) => ( 62 | "span" in row && 63 | row.span[0] > this.span[1] 64 | )) as Highlight[], 65 | 66 | [({ span }) => span[0]], 67 | ['asc'] 68 | ); 69 | } 70 | 71 | next(type: string | string[]) { 72 | return this.allNext(type)[0] 73 | } 74 | 75 | valueOf() { 76 | const spanText = this.text() 77 | 78 | if (!spanText) { 79 | return 80 | } 81 | 82 | if (isNumericish(spanText)) { 83 | return parseFloat(spanText) 84 | } 85 | 86 | return spanText 87 | } 88 | 89 | toString() { 90 | return this.text() 91 | } 92 | 93 | text() { 94 | return getTextForHighlight(this) 95 | } 96 | 97 | isEqualTo(value: any) { 98 | if (isValueRowHighlight(value)) { 99 | return this.text() === value.text() 100 | } 101 | 102 | return this.text() === value 103 | } 104 | 105 | wholeLine () { 106 | const textDocument = textDocumentsMobx.get(this.documentId) 107 | 108 | if (!textDocument) { 109 | return 110 | } 111 | 112 | const fromLine = textDocument.text.lineAt(this.span[0]) 113 | const toLine = textDocument.text.lineAt(this.span[0]) 114 | 115 | return Highlight.from({ 116 | ...this, 117 | span: [fromLine.from, toLine.to] 118 | }) 119 | } 120 | 121 | static from(highlight: { 122 | documentId: string, 123 | sheetConfigId: string, 124 | span: Span, 125 | data: { [columnName: string]: any }, 126 | }) { 127 | return new Highlight(highlight.documentId, highlight.sheetConfigId, highlight.span, highlight.data) 128 | } 129 | } 130 | 131 | 132 | Object.defineProperty(Array.prototype, 'sumOf', { 133 | value: function (path?: string) { 134 | let sum = 0; 135 | 136 | this.forEach((item: any) => { 137 | const number = coerceValueToNumber(path ? get(item, path) : item) 138 | 139 | console.log(item, number, path) 140 | 141 | if (!isNaN(number)) { 142 | sum += number 143 | } 144 | }) 145 | 146 | return sum; 147 | } 148 | }); 149 | 150 | Object.defineProperty(Array.prototype, "sortBy", { 151 | value: function (fn: (item: any) => any) { 152 | return sortBy(this, fn); 153 | } 154 | }) 155 | 156 | Object.defineProperty(Array.prototype, "isEqual", { 157 | value: function (value: any) { 158 | return isEqual(this, value) 159 | } 160 | }) 161 | 162 | Object.defineProperty(Array.prototype, "groupBy", { 163 | value: function (fn: (item: any) => any) { 164 | return Object.entries(groupBy(this, fn)).map(([group, items]) => ({group, items })) 165 | } 166 | }) -------------------------------------------------------------------------------- /documents/coffee.json: -------------------------------------------------------------------------------- 1 | { 2 | "textDocument": { 3 | "id": "aeropress", 4 | "name": "☕️ James Hoffmann Aeropress", 5 | "text": [ 6 | "## Recipe", 7 | "Grind 11 g coffee, medium-fine. ", 8 | "Add 200 g water, brew 2 minutes, plunge!", 9 | "", 10 | "scale by", 11 | "", 12 | "## Notes", 13 | "6/22/22: Pretty good, but forgot to swirl.", 14 | "6/23/22: Felt weak and under-extracted. Grind finer?" 15 | ], 16 | "sheets": [ 17 | { 18 | "id": "eiIXzGv8Zq1zybgMZdd1z", 19 | "configId": "common.number", 20 | "hideHighlightsInDocument": false 21 | }, 22 | { 23 | "id": "X8bBiHSTseJNK4lyNkl2D", 24 | "configId": "food.quantity", 25 | "hideHighlightsInDocument": false 26 | }, 27 | { 28 | "id": "JdoUlaXUw0gg15KLpYHp6", 29 | "configId": "common.duration", 30 | "hideHighlightsInDocument": false 31 | }, 32 | { 33 | "id": "fUpyOI1wmdDbCnuq6dNfU", 34 | "configId": "food.scale", 35 | "hideHighlightsInDocument": false 36 | }, 37 | { 38 | "id": "PL4sKTsqJcM18ppBJIviZ", 39 | "configId": "common.date", 40 | "hideHighlightsInDocument": false 41 | }, 42 | { 43 | "id": "Htzihyo06gMLB1lCrEl24", 44 | "configId": "markdown", 45 | "hideHighlightsInDocument": false 46 | } 47 | ] 48 | }, 49 | "sheetConfigs": [ 50 | { 51 | "id": "common.number", 52 | "name": "number", 53 | "properties": [ 54 | { 55 | "name": "$", 56 | "formula": "{/[0-9][0-9\\.]*/}", 57 | "visibility": "HIDDEN" 58 | }, 59 | { 60 | "name": "value", 61 | "formula": "ParseFloat($)", 62 | "visibility": "HIDDEN" 63 | } 64 | ] 65 | }, 66 | { 67 | "id": "food.quantity", 68 | "name": "quantity", 69 | "properties": [ 70 | { 71 | "name": "$", 72 | "formula": "{number:amount} {/(cup|tablespoon|tbsp|teaspoon|tsp|pound|lb|gram|g|milliliter|ml)s?/:unit}", 73 | "visibility": "HIDDEN" 74 | }, 75 | { 76 | "name": "scaleFactor", 77 | "formula": "Find(\"scale\")?.data.sliderValue", 78 | "visibility": "HIDDEN" 79 | }, 80 | { 81 | "name": "scaledAmount", 82 | "formula": "(scaleFactor && scaleFactor !== 1 && amount) ? `${scaleFactor * amount.data.value} ${unit}${amount.data.value === 1 ? 's' : ''}` : undefined", 83 | "visibility": "REPLACE" 84 | } 85 | ] 86 | }, 87 | { 88 | "id": "common.duration", 89 | "name": "durations", 90 | "properties": [ 91 | { 92 | "name": "$", 93 | "formula": "{/((\\\\d+\\\\s+(hours?|minutes?|seconds?))\\\\s*)*(\\\\d+\\\\s+(hours?|minutes?|seconds?))/}", 94 | "visibility": "HIDDEN" 95 | }, 96 | { 97 | "name": "timer", 98 | "formula": "Timer($)", 99 | "visibility": "INLINE" 100 | } 101 | ] 102 | }, 103 | { 104 | "id": "food.scale", 105 | "name": "scale", 106 | "properties": [ 107 | { 108 | "name": "$", 109 | "formula": "scale by", 110 | "visibility": "HIDDEN" 111 | }, 112 | { 113 | "name": "slider", 114 | "formula": "Slider($)", 115 | "visibility": "SUPERSCRIPT" 116 | }, 117 | { 118 | "name": "sliderValue", 119 | "formula": "slider.data.value", 120 | "visibility": "INLINE" 121 | } 122 | ] 123 | }, 124 | { 125 | "id": "common.date", 126 | "name": "dates", 127 | "properties": [ 128 | { 129 | "name": "date", 130 | "formula": "{number:month}/{number:day}/{number:year}", 131 | "visibility": "HIDDEN" 132 | } 133 | ] 134 | }, 135 | { 136 | "id": "markdown", 137 | "name": "markdown", 138 | "properties": [ 139 | { 140 | "name": "$", 141 | "formula": "=Markdown()", 142 | "visibility": "HIDDEN" 143 | }, 144 | { 145 | "name": "type", 146 | "formula": "$.data.type", 147 | "visibility": "HIDDEN" 148 | }, 149 | { 150 | "name": "font-weight", 151 | "formula": "type.startsWith(\"h\") || type === \"bold\" ? \"bold\" : \"normal\"", 152 | "visibility": "STYLE" 153 | }, 154 | { 155 | "name": "font-style", 156 | "formula": "type === \"italic\" ? \"italic\" : \"normal\"", 157 | "visibility": "STYLE" 158 | }, 159 | { 160 | "name": "font-size", 161 | "formula": "({\"h1\": \"1.3rem\",\n \"h2\": \"1rem\"}[type]) || \"normal\"", 162 | "visibility": "STYLE" 163 | } 164 | ] 165 | } 166 | ] 167 | } --------------------------------------------------------------------------------