Jason Leow

Indie hacker, solopreneur | Creating a diverse portfolio of products + services.

Wrote "One more thing..." as a fitting last post, and a vital public service announcement - https://writelier.com/one-more-thing...-33814532-f38e-479f-99b1-9e5483d5265a

Installed Droid Serif font for post body text and Font Awesome for icons

Thinking that fontawesome icons may look cleaner and more aligned to the site, rather than emojis πŸ€”Also, the typesetting is looking waaay better now! πŸ‘‡πŸ»

Designed new logo for - what do you guys think?

Lifelog is about setting your most ambitious goals and life aspirations, and writing your way towards them, every day. It's a community focused on intentional living and deliberate experimentation. Share your goals, your daily thoughts, tasks and plans, and get inspired by what others are doing.

Tweaked layout of community page to be tile based. Flexbox is not very intuitive! 😩

Jason Leow Author

Thanks for explaining! Will definitely check it out on your recommendation… finding some snippet on Codepen now to play around with

0 Likes
Jason Leow Author

Oh wow, thanks so much Daniel! Oh seems simpler, rather than the confusing terms on flex. Just forked the pen to play around with it more. Cheers man 🍻

0 Likes

Tweaked styles for consistency on _slug, community, profile pages, and comment threads

Added writing prompts to /write page 'invisible' toolbar

Borrowed the same randomized prompts generators from home page to write page

Pretty proud of this main piece of UI/UX for Lifelog (working title) - distraction-free writing page with just the title and content, and everything else is faded to background until you hover over it.

Had to use pretty interesting Vue tricks like below to make this hover effect (but without using methods!):

Hover here

Show this

...
data() {
return {
hover: false,
}
},

Created a randomized prompting question feature to welcome user back to the site on home page

So fun to do this! This is completely out of the MVP scope but once in a while you gotta work on features you like to keep the motivation going. Now I can collect all those tweets from platitude Twitter and use them in my app lol πŸ˜‚

[UPDATE:] The list of prompting questions had grown to 53! 😯 This in itself could be a product!

Styling tweaks all day! Tweaked SetGoals page's success message, style for /write page navbar and textarea. Still can't figure out how to implement a modal on Bulma! 😩Navbar classes are wonky because I don't know wtf I'm doing, and found that there's no easy solution for a textarea that auto resizes as the input gets longer! 😡 Spent almost the whole day figuring out the right package/code to use. Usually frotnend and design is easy for me, but today has got to be one styling nightmare after another! πŸ€•

Reflection: All this difficulty is due to not having design system. Must start!

πŸ—’ Style tweaks to the writing page to make it even more minimal for distraction-free writing. But really, I need a design system! Gotta get back to Figma for a bit...to be continued

πŸ’š Really enjoying using Vue.js! Added a reactive v-if feature that hides the goals checkboxes and shows a prompt instead to the user to go add a goal if the user doesn't have any goals


...
Looks like you had yet to set any goals! Please
create new goals here.

πŸ—’ Created new minimal layout with just a back button for distraction-free writing

πŸš… Added a page for user to set goals

Set this up real fast, borrowing code from past pages. Love how exponential it is once the basic components are up!

πŸ₯‡πŸ₯ˆπŸ₯‰ Added added gamification tags that say different things upon reaching different levels of word count

First time using Vue.js v-else-if directives

πŸ”₯ Wow you're on fire!

πŸ’ͺπŸ‹πŸ»β€β™‚οΈπŸ₯‡ *Feeling victorious!* Added a new method using (another!) regex, to remove line breaks and carriage returns from the post excerpt (so that I can keep the layout on home page feed consistent)

methods: {
removeLinebreaks(text) {
return text.replace(/\r\n|\n|\r/gm, ' ')
},
}

---

Learning notes on this regex:
\r\n = carriage return+line feed on WinDOS,
\n = line feed/break on Linux,
\r = carriage return on old Macs,
g = regex flag for global search,
m = regex flag for multi-line search

Added localStorage to store drafts as user types

Just like that, added this new localStorage feature that deployed successfully the first time. Almost too easy (becoming cynical lol) 🧐

πŸš€ Created landing page for custom Carrd plugins... so I guess it's launched...finally? https://plugins.carrd.co

Finally shared the first of my Carrd demos (https://animatedaccordionfaqs.carrd.co/ ) with a Carrd community on Facebook, just to see interest and further validate demand for Carrd extension/plugins

πŸ•Ή Designing frontend UI had always been a more fav part of coding for me, and got to play with the styling simplicity of Bulma and the delightful reactivity of Vue

Was delighted when I discovered I could hide/show UI based on certain states/data! Like in this case when the word count reaches a certain number, show πŸ‘Ž+ is-light class using a v-if, and πŸ‘+is-primary class on v-else. I also could v-bind the disabled property of a button to the word count, so that it becomes active when the condition is reached (in this case, the word count). Now we talkin! 🀩

πŸ”’Figured out the word count feature today, using a splattering of regex (today seems to be learning regex day), borrowing code bits from my past tinkering on Codepen!

On other news, not sure if eslint is helping me or killing me today. Had to disable it so much, yet it still works! 😀
Jason Leow Author

Thanks Antonio! Indeed, every day is a new learning (and beating) lol πŸ˜‚

0 Likes

the life of the maker, you learn something new everyday. Keep pushing πŸ’ͺ

0 Likes

Added truncated post excerpt with markdown rendering to main home page feed of posts

Learning point: Features I thought was pretty simple seem to end up being 2-3x more complex and 10x more time and effort to solve. This was one of them. Just do a simple truncate filter to create a post excerpt, and just {{ post.content | truncate }}, right? NOOOO. Because I did that and realised that it wouldn't render markdown which I use in the app πŸ€¦β€β™‚οΈ So I had to find a way to first render the content in md, then truncate. But the truncate package only limit characters and not words, so I had to write another replace() function for it! 😩

------------

Notes to self about how to read the below regular expression:
-- A regular expression is a sequence of characters that forms a search pattern. The search pattern can be used for text search and text replace operations.
-- a literal regex is enclosed within fwd slashes //
-- ^ is a boundary-type assertion = the start of the input
-- () captures a group of characters and remembers it, in this case, to rem for the excerpt
-- . matches any single character except line terminators, eg /.y/ matches "my" and "ay", but not "yes"
-- {n} is a quantifier where "n" is a positive integer, matches exactly "n" occurrences of the preceding item "x". For example, /a{2}/ doesn't match the "a" in "candy", but it matches all of the "a"'s in "caandy", and the first two "a"'s in "caaandy". In this case, matching 440 chars after the start
-- [^xyz] is a negated char set, matches anything that is not enclosed in the brackets. in this case
-- \s is a char class that matches a single white space character, including space, tab, form feed, line feed, and other Unicode spaces
-- * is a quantifier matches the preceding item "x" 0 or more times, eg bo* matches "boooo" in "A ghost booooed" and "b" in "A bird warbled", but nothing in "A goat grunted".
-- ().* is to match to whatever that's left of truncatedMdtext after the initial 440 char group (because the whole regex has to match up with truncatedMdtext and we can't forget to account for them in the searchvalue). eg. the pattern /ab*c/: the * after "b" means "0 or more occurrences of the preceding item." In the string "cbbabbbbcdebc", this pattern will match the substring "abbbbc".
-- '$1' is to access the first result match from the captured group of chars within (). Matches are accessed using the index of the the result's elements ([1], ..., [n]) or from the predefined RegExp object's properties ($1, ..., $9).
-- , '$1' means to replace with a string of the first match from the regex, where string.replace(searchvalue, newvalue), where searchvalue is the required value, or regular expression, that will be replaced by the new value, while newvalueis the required value to replace the search value with
== hence in layman, the regex = search from start of truncatedMdtext, remember first 440 chars of truncatedMdtext excluding all single white spaces, and replace truncatedMdtext with the remembered result
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/n

β˜‘οΈβ˜‘οΈβ˜‘οΈCreated multi-selectable checkboxes for goals unique to user that works on POST

Wow spent 2 days figuring out how to:
1) dynamically create a list of checkboxes using v-for,
2) how to select multiple checkboxes that submits the id value of the each item, instead of a boolean true/false
3) how to use v-model so that the right data is POSTed

Went down rabbit hole between using checkboxes inside a `select` field (UI/a11y nightmare), or tweaking radio buttons to behave like multi-selectable checkboxes (impossible), or just making multiple checkboxes send an id integer instead of boolean. In the end, it was back to good ol' Vue.js docs (https://vuejs.org/v2/guide/forms.html#Checkbox) that gave the answer, Stack Overflow didn't even helped much.

Answer is: Multiple checkboxes bound to the same array, with each input's id and value being unique, while v-model is same for all. Change data definition of your v-model attribute to an array `[]` instead of a string `' '`.

Sometimes the simplest things are the hardest. πŸ˜΅πŸ˜“

⏰ Added timestamp to slug to prevent error arising from duplicates. Thank you @keenencharles for the guidance on using `new Date().getTime()` !

slugify(data.title + '-' + new Date().getTime(), {lower: true, strict: true});

πŸ”₯🎰πŸ”₯ ON A ROLL TODAY! Successfully created a new GET endpoint for selecting goals unique to the user when writing a post

Figured out the tricky raw SQL query by leaning on the previous query of using authenticated user object, and then adding `.rows` after the `const`, in order to retrieve a cleaner set of data

`const authorGoals = await strapi.connections.default.raw(`SELECT id, title, description, author FROM goals WHERE author = ${ctx.state.user.id}`);

return authorGoals.rows;`