diff --git a/README.md b/README.md index a6bbd11..669db1d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Personal Microclient -

-Aren't you tired of distracting social media platforms? +

Aren't you tired of distracting social media platforms? So many buttons and advertising makes a simple task of reading and composing notes to your community very draining. You can use a next generation social media protocol to transform @@ -9,13 +8,11 @@ your personal website into a micro social media client that will clear up space on your phone for media.

-

-I'm using ProseMirror, +

I'm using ProseMirror, to build a WYSIWYM style rich text editor for visitors to compose notes with. I'm taking inspiration from tumblr and medium's text editors to build something minimal and -intuitive that will run easily via browsers. -

+intuitive that will run easily via browsers.

I scaffolded the project with PWA Builder and am using @@ -29,6 +26,16 @@ from a relay.

If you're on any chrome-based, firefox or safari browser try visiting the webpage then tapping on the arrow pointing up in the bottom toolbar. Scroll down a bit to tap on "Add to Home Screen". If you're on a chromium-based browser you should be able to do the same.

+

Potential Use cases +Put public library record digitized in a nostr client feed for their public records + +blog feed and reflect activity + +for pitch to group, have a side by side example of blog written in markdown, microsoft word, and rich text. + +

+ + # TODO diff --git a/package-lock.json b/package-lock.json index d5ff3ee..f4f6b59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "express": "^4.21.1", "lazy.js": "^0.5.1", "lit": "^3.2.1", + "marked": "^15.0.6", "nostr-tools": "^2.10.1", "prosemirror-markdown": "^1.13.1", "prosemirror-menu": "^1.2.4", @@ -4460,6 +4461,17 @@ "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/marked": { + "version": "15.0.6", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.6.tgz", + "integrity": "sha512-Y07CUOE+HQXbVDCGl3LXggqJDbXDP2pArc2C1N1RRMN0ONiShoSsIInMd5Gsxupe7fKLpgimTV+HOJ9r7bA+pg==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", diff --git a/package.json b/package.json index c31c06e..9dd3ec4 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "express": "^4.21.1", "lazy.js": "^0.5.1", "lit": "^3.2.1", + "marked": "^15.0.6", "nostr-tools": "^2.10.1", "prosemirror-markdown": "^1.13.1", "prosemirror-menu": "^1.2.4", diff --git a/public/assets/icons/144x144.png b/public/assets/icons/144x144.png new file mode 100644 index 0000000..3c56468 Binary files /dev/null and b/public/assets/icons/144x144.png differ diff --git a/public/assets/icons/192x192.png b/public/assets/icons/192x192.png index e63fa2f..1b1f9b4 100644 Binary files a/public/assets/icons/192x192.png and b/public/assets/icons/192x192.png differ diff --git a/public/assets/icons/20x20.png b/public/assets/icons/20x20.png new file mode 100644 index 0000000..f40a788 Binary files /dev/null and b/public/assets/icons/20x20.png differ diff --git a/public/assets/icons/24x24.png b/public/assets/icons/24x24.png deleted file mode 100644 index 067fbdb..0000000 Binary files a/public/assets/icons/24x24.png and /dev/null differ diff --git a/public/assets/icons/48x48.png b/public/assets/icons/48x48.png index f3d4666..855c6e8 100644 Binary files a/public/assets/icons/48x48.png and b/public/assets/icons/48x48.png differ diff --git a/public/assets/icons/512x512.png b/public/assets/icons/512x512.png deleted file mode 100644 index 59ae864..0000000 Binary files a/public/assets/icons/512x512.png and /dev/null differ diff --git a/public/assets/icons/casto_kingdom.png b/public/assets/icons/gold_crown.png similarity index 100% rename from public/assets/icons/casto_kingdom.png rename to public/assets/icons/gold_crown.png diff --git a/public/assets/icons/icon_24.png b/public/assets/icons/icon_24.png deleted file mode 100644 index cadeeda..0000000 Binary files a/public/assets/icons/icon_24.png and /dev/null differ diff --git a/public/assets/icons/icon_48.png b/public/assets/icons/icon_48.png deleted file mode 100644 index 69d5b2e..0000000 Binary files a/public/assets/icons/icon_48.png and /dev/null differ diff --git a/public/assets/img/nostr-icon.png b/public/assets/img/nostr-icon.png new file mode 100755 index 0000000..924b393 Binary files /dev/null and b/public/assets/img/nostr-icon.png differ diff --git a/public/manifest.json b/public/manifest.json index d307637..17c3fcc 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,10 +1,10 @@ { - "id": "https://miguelalmodo.com/dist2", - "scope": "/", + "id": "https://miguelalmodo.com/", + "scope": "/dist/", "name": "fostr", "display": "standalone", - "start_url": "/", - "short_name": "micro app", + "start_url": "/dist/", + "short_name": "miggymofongo", "theme_color": "#E1477E", "description": "This is a miggymofongo project", "orientation": "any", @@ -17,12 +17,12 @@ }, "icons": [ { - "src": "assets/icons/512x512.png", + "src": "assets/icons/gold_crown.png", "sizes": "512x512", "type": "image/png" }, { - "src": "assets/icons/192x192.png", + "src": "assets/icons/144x144.png", "sizes": "192x192", "type": "image/png" }, @@ -32,10 +32,11 @@ "type": "image/png" }, { - "src": "assets/icons/24x24.png", + "src": "assets/icons/20x20.png", "sizes": "24x24", "type": "image/png" } + ], "screenshots": [ { @@ -67,7 +68,7 @@ "tag": "starterWidget", "ms_ac_template": "widget/ac.json", "data": "widget/data.json", - "description": "A simple widget example from pwa-starter.", + "description": "Coming soon", "screenshots": [ { "src": "assets/screenshots/widget-screen.png", diff --git a/src/components/header.ts b/src/components/header.ts index 926d590..db3c042 100644 --- a/src/components/header.ts +++ b/src/components/header.ts @@ -23,8 +23,8 @@ export class AppHeader extends LitElement { align-items: center; background: var(--app-color-primary); color: white; - padding: 12px; - padding-top: 4px; + padding: 40px; + padding-top: 40px; position: fixed; left: env(titlebar-area-x, 0); @@ -61,6 +61,10 @@ export class AppHeader extends LitElement { color: initial; } } + + #signin { + padding-right: 80px; + } `; /** the connected callback method one of the lifecycle methods that runs once the element is added * to the dom. there is no disconnectedCallback because the header is never removed from the DOM. @@ -163,14 +167,15 @@ export class AppHeader extends LitElement { ${this.enableBack ? html` Back ` : null} + Nostr Icon -

${this.title}

+

${this.title}

- +
${this.isSignedIn ? 'Sign out' : 'Sign in'} - +
`; } diff --git a/src/components/menu-plugin.ts b/src/components/menu-plugin.ts deleted file mode 100644 index a154468..0000000 --- a/src/components/menu-plugin.ts +++ /dev/null @@ -1,37 +0,0 @@ -// menu-plugin.ts -import { MenuItem } from "prosemirror-menu"; -import { menuBar } from "prosemirror-menu"; -import { toggleMark } from "prosemirror-commands"; -import { Plugin, PluginKey } from "prosemirror-state"; - - -const menuPluginKey = new PluginKey('menuPlugin') -// Modify the menuBar function to accept customSchema as a parameter -export function createMenuPlugin(customSchema: any) { - const boldButton = new MenuItem({ - title: "Bold", - run: toggleMark(customSchema.marks.bold), - active: (state) => state.selection.$head.marks().some(mark => mark.type === customSchema.marks.bold), - }); - - const italicButton = new MenuItem({ - title: "Italic", - run: toggleMark(customSchema.marks.italic), - active: (state) => state.selection.$head.marks().some(mark => mark.type === customSchema.marks.italic), - }); - - const menuContent = [ - [boldButton, italicButton] // Array of buttons or other menu items - ]; - - menuBar({ - content: menuContent, - floating: true, - }); - console.log('Creating menu plugin'); - - return new Plugin({ - key: menuPluginKey - }) - -} diff --git a/src/pages/app-home.ts b/src/pages/app-home.ts index cb708f5..fa65aee 100644 --- a/src/pages/app-home.ts +++ b/src/pages/app-home.ts @@ -34,20 +34,6 @@ export class AppHome extends LitElement { styles, css` - - - @media (horizontal-viewport-segments: 2) { - #welcomeBar { - flex-direction: row; - align-items: flex-start; - justify-content: space-between; - } - - #welcomeCard { - margin-right: 64px; - } - } - /* ======================================== Profile Picture Container @@ -62,13 +48,10 @@ Profile Picture Container } -.profile-picture-container p { - margin: 0; -} .profile-pic { grid-area: 1/1; - margin-top: 5px; + margin-top: 30px; margin-left: 100px; margin-bottom: 5px; @@ -129,7 +112,7 @@ Profile Picture Container } handlesSignOut() { - this.profilePic = ''; + this.profilePic = '/dist/assets/img/default_pfp.png'; this.bio = ''; this.nostrAddy = 'Guest'; this.isSignedIn = false; @@ -139,17 +122,16 @@ Profile Picture Container async fetchAndDisplayProfile(pubkey: string) { this.nostrAddy = pubkey; this.bio = 'Loading profile info...'; - this.profilePic = '/assets/img/loading_pfp.png'; try { - // Fetch profile metadata from a relay + // try to fetch profile metadata from a relay const relay = await Relay.connect('wss://notes.miguelalmodo.com'); // Example URL const sub = relay.subscribe([{ kinds: [0], authors: [pubkey] }], { onevent: (event) => { const profileData = JSON.parse(event.content); this.nostrAddy = profileData.nip05 || 'No address available'; this.bio = profileData.about || 'No bio available'; - this.profilePic = profileData.picture || '/assets/img/default_pfp.png'; + this.profilePic = profileData.picture || '/dist/assets/img/default_pfp.png'; this.requestUpdate(); }, oneose: () => sub.close(), @@ -165,7 +147,7 @@ Profile Picture Container // Set initial values for guest view this.nostrAddy = ''; this.bio = 'Welcome, guest! Please sign in with a browser extension to view your profile.'; - this.profilePic = '/assets/img/default_pfp.png'; // Could be a placeholder image for guests + this.profilePic = '/dist/assets/img/default_pfp.png'; this.isSignedIn = false; } @@ -175,7 +157,7 @@ Profile Picture Container (navigator as any).share({ title: 'A MiggyMofongo Project', text: 'This is a personal progressive social web app', - url: 'https://miguelalmodo.com/dist2', + url: 'https://miguelalmodo.com/dist', }); } } @@ -194,15 +176,14 @@ Profile Picture Container

Welcome to ${this.nostrAddy || 'Guest'}'s Profile

- You can upgrade your website into - a micro blog client with a social protocol to do things - like browse a feed, compose a note, or post to your network. - + You can upgrade your personal website with a social + protocol to do things like browse a feed, compose + a note, or post to your network.

-Profile Picture +Profile Picture

${this.bio || 'Welcome, guest! Please sign in to view your profile.'}

@@ -215,7 +196,7 @@ Profile Picture Container ${'share' in navigator ? html` - Share this personal website with a friend! + Share with a homie! ` : null} diff --git a/src/pages/app-write/app-write.ts b/src/pages/app-write/app-write.ts index 9e46305..41124e5 100644 --- a/src/pages/app-write/app-write.ts +++ b/src/pages/app-write/app-write.ts @@ -1,4 +1,4 @@ -import { LitElement, html } from 'lit'; +import { LitElement, html, css } from 'lit'; import { customElement, query } from 'lit/decorators.js'; import '@shoelace-style/shoelace/dist/components/card/card.js'; @@ -160,7 +160,16 @@ export class AppWrite extends LitElement { static styles = [ styles, - editorStyles + editorStyles, + + css`:root { + --main-line-color: hsl(234, 62%, 86%); + --side-line-color: hsl(350, 100%, 91%); + --paper-color: hsl(0, 15%, 95%); + --ink-color: hsl(0, 0%, 12%); + --line-thickness: 3px; + --top-space: 4lh; +}` ]; constructor() { @@ -209,7 +218,7 @@ export class AppWrite extends LitElement { let doc = customSchema.node('doc', null, [ - customSchema.node('heading', null, [customSchema.text('Título')]), + customSchema.node('heading', null, [customSchema.text('Título h1')]), customSchema.node('blockquote', null, [ customSchema.node('paragraph', null, [customSchema.text('"WEPA"')]), @@ -220,8 +229,9 @@ export class AppWrite extends LitElement { ]), customSchema.node('horizontal_rule', null), customSchema.node('paragraph', null, [customSchema.text('Escribe mas aquí...')]), - customSchema.node('paragraph', null, [customSchema.text('Presiona Shift + Space para colocar una estrella')]), - customSchema.node('paragraph', null, [customSchema.text('Presiona Ctrl + g para marcar la selección con la marca "shouting" ')]) + customSchema.node('paragraph', null, [customSchema.text('Presione Shift + Space para colocar una estrella')]), + customSchema.node('paragraph', null, [customSchema.text('Presione Ctrl + g para marcar la selección con la marca "shouting" ')]), + customSchema.node('heading', { level: 2 }, [customSchema.text('Título h2')]), ]) @@ -271,10 +281,12 @@ export class AppWrite extends LitElement { protected render() { return html` -
+ +
+
Try selecting some text below to test out a minimal rich text editor. -
+
`; diff --git a/src/pages/app-write/write-menu.ts b/src/pages/app-write/write-menu.ts deleted file mode 100644 index a3a421a..0000000 --- a/src/pages/app-write/write-menu.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { LitElement, PropertyValues, css, html } from "lit"; -import { styles } from "../../styles/shared-styles"; - - - -export class MenuBar extends LitElement { - - static properties = { - items: { type: Array }, // Array of menu items - editorView: { type: Object }, // Reference to the editor view - }; - - constructor() { - super(); - this.items = []; - this.editorView = null; - } - - static stylse = [styles, css ` - .menubar { - display: flex; - gap: 10px; - padding: 5px; - background-color: #f9f9f9; - border-bottom: 1px solid #ddd; - } - .menubar button { - padding: 5px 10px; - border: none; - background-color: #fff; - cursor: pointer; - border-radius: 3px; - font-size: 14px; - } - .menubar button:hover { - background-color: #eee; - } - .menubar button:disabled { - background-color: #ccc; - cursor: not-allowed; - } `, ] - ; - - async _isItemActive(item) { - if (this.editorView && typeof item.command === 'function') { - return item.command(this.editorView.state, null, this.editorView); - } - return false; - } - - async _onItemClick(event, item) { - event.preventDefault(); - if (this.editorView) { - this.editorView.focus(); - item.command(this.editorView.state, this.editorView.dispatch, this.editorView); - } - } - -async _updateMenu() { - if (this.items && this.editorView) { - this.items.forEach((item) => { - const isActive = this._isItemActive(item); - item.dom && (item.dom.style.display = isActive ? '' : 'none'); - }); - } - } - -connectedCallback(): void { - super.connectedCallback(); - console.log("editor menu component added"); - this._updateMenu(); - - -} - -disconnectedCallback(): void { - super.disconnectedCallback(); - this.items.forEach((item) => { - if (item.dom) { - item.dom.remove(); - } - }); - -} - - -render() { - - return html` - - - -
- -
- - ` -} -} - -customElements.define('menu-bar', MenuBar) \ No newline at end of file diff --git a/src/pages/app-write/write-styles.ts b/src/pages/app-write/write-styles.ts index c637cce..aa57af5 100644 --- a/src/pages/app-write/write-styles.ts +++ b/src/pages/app-write/write-styles.ts @@ -2,8 +2,8 @@ import { css } from "lit" export const editorStyles = css` + :host .ProseMirror { - background: black; color: white; background-clip: padding-box; padding: 5px 0; @@ -31,36 +31,75 @@ export const editorStyles = css` --markdown-editor-typography-letter-spacing, var(--mdc-typography-subtitle1-letter-spacing, 0.009375em) ); - } +} - .ProseMirror pre { - white-space: pre-wrap; - } +.ProseMirror { + word-wrap: break-word; + white-space: pre-wrap; + white-space: break-spaces; + -webkit-font-variant-ligatures: none; + font-variant-ligatures: none; + font-feature-settings: "liga" 0; /* the above doesn't seem to work in Edge */ +} - .ProseMirror a { - color: var(--markdown-editor-typography-anchor-color, -webkit-link); - text-decoration: var(--markdown-editor-typography-anchor-text-decoration); - } +.ProseMirror pre { + white-space: pre-wrap; +} - .ProseMirror-focused .ProseMirror-gapcursor { - display: block; - } - shouting { - all: unset; /* Remove inherited or conflicting styles */ - font-weight: bold; - text-transform: uppercase; - color: red; } +.ProseMirror li { + position: relative; +} - .boring { +.ProseMirror-hideselection *::selection { background: transparent; } +.ProseMirror-hideselection *::-moz-selection { background: transparent; } +.ProseMirror-hideselection { caret-color: transparent; } + +/* See https://github.com/ProseMirror/prosemirror/issues/1421#issuecomment-1759320191 */ +.ProseMirror [draggable][contenteditable=false] { user-select: text } + +.ProseMirror-selectednode { + outline: 2px solid #8cf; +} + +/* Make sure li selections wrap around markers */ + +li.ProseMirror-selectednode { + outline: none; +} + +li.ProseMirror-selectednode:after { + content: ""; + position: absolute; + left: -32px; + right: -2px; top: -2px; bottom: -2px; + border: 2px solid #8cf; + pointer-events: none; +} + +/* Protect against generic img rules */ + +img.ProseMirror-separator { + display: inline !important; + border: none !important; + margin: 0 !important; +} + shouting { + all: unset; /* Remove inherited or conflicting styles */ + font-weight: bold; + text-transform: uppercase; + color: red; } + + .boring { background: grey; } - blockquote { - font-style: italic; - border-left: 2px solid gray; - padding-left: 10px; - color: darkgray; + blockquote { + font-style: italic; + border-left: 2px solid gray; + padding-left: 10px; + color: darkgray; +} ` \ No newline at end of file diff --git a/src/pages/note-wall.ts b/src/pages/note-wall.ts index 21ffb8e..debf5fd 100644 --- a/src/pages/note-wall.ts +++ b/src/pages/note-wall.ts @@ -1,6 +1,8 @@ import { LitElement, css, html } from 'lit'; import { property, customElement } from 'lit/decorators.js'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js' import { Relay } from 'nostr-tools'; +import {marked} from 'marked'; import '@shoelace-style/shoelace/dist/components/card/card.js'; import '@shoelace-style/shoelace/dist/components/button/button.js'; @@ -23,71 +25,37 @@ note = ''; // store notes styles, css` -.comment-wall .main-section-header { - margin-bottom: 3px; -} - -.comment-wall .main-section-h2 { - margin-bottom: 0; -} - -#comment-counter { - margin-top: 0; - margin-left: 15px; - margin-bottom: 3px; -} .comment-wall table { margin: auto; - margin-bottom: 5px; - color: black; + margin-bottom: 50px; } .comment-wall th { - width: 158px; - padding: 3px; vertical-align: top; } .comment-wall td { vertical-align: top; - width: 269px; + width: 600px; padding: 3px; } -.comment-wall figcaption, -.comment-wall figure { - margin: 0; -} - -.comment-wall figcaption { - margin-bottom: 1em; -} - -.comment-wall figure { - margin-bottom: 49.33px; -} - -.comment-wall h3 { - font-size: 10pt; - margin: 0; - margin-bottom: 3em; -} - -.comment-wall p { - font-weight: normal; - text-align: center; - margin: 0; -} - #add-comment { text-align: right; margin-right: 10px; margin-bottom: 5px; } - +.note-content { + text-align: left; + white-space: pre-wrap; + } + .note-content img { + max-width: 100%; + height: auto; + } `]; @@ -184,58 +152,33 @@ async displayLongNotes() { render() { return html` -
- -
- -
-

Recent Notes from ${this.relayName}

-
- - - - - - ${this.notes.map(note => { - // extract URL from note content - const urlMatch = note.content.match(/https?:\/\/[^\s]+/); - const textContent = note.content.replace(urlMatch?.[0] || '', '').trim(); - - - // Check for yt links and extract video ID - const youtubeRegex = /(?:https?:\/\/(?:www\.)?youtube\.com\/watch\?v=|https?:\/\/youtu\.be\/)([a-zA-Z0-9_-]{11})/; - const youtubeMatch = urlMatch?.[0].match(youtubeRegex); - const youtubeVideoId = youtubeMatch ? youtubeMatch[1] : null; - - // build yt thumbnail URL if applicable - const thumbnailUrl = youtubeVideoId - ? `https://img.youtube.com/vi/${youtubeVideoId}/maxresdefault.jpg` - : null; - - //check for image links - const imgRegex = /(https?:\/\/[^\s]+\.(?:jpg|jpeg|png|gif))/i; - const imgMatch = urlMatch?.find(url => imgRegex.test(url)); - - return html` - - - - - `; - })} -

${note.date}

-

${textContent}

- ${thumbnailUrl - ? html`YouTube thumbnail` - : imgMatch - ? html`Note image` - : ''} -
- - - -
- +
+
+ +
+
+

+ Recent Notes from ${this.relayName} +

+
+ + ${this.notes.map( + (note) => html` + + + + + ` + )} +
+

${note.date}

+
+ ${unsafeHTML(marked(note.content) as string)} +
+
+
+
+
`; } } diff --git a/src/router.ts b/src/router.ts index bec68d0..8fcc66d 100644 --- a/src/router.ts +++ b/src/router.ts @@ -34,7 +34,7 @@ export const router = new Router({ }, { path: resolveRouterPath('note-wall'), - title: 'Note Wall', + title: 'Feed', plugins: [ lazy(() => import('./pages/note-wall.js')), ], diff --git a/src/styles/shared-styles.ts b/src/styles/shared-styles.ts index a390769..8d52b19 100644 --- a/src/styles/shared-styles.ts +++ b/src/styles/shared-styles.ts @@ -6,6 +6,7 @@ export const styles = css` main { margin-top: 100px; padding: 12px; + background-color: ; } @@ -40,10 +41,29 @@ export const styles = css` } } - @media (horizontal-viewport-segments: 2) { + @media (horizontal-viewport-segments: 2) { #welcomeBar { flex-direction: row; align-items: flex-start; justify-content: space-between; -}} -` \ No newline at end of file + } + + #welcomeCard { + margin-right: 64px; + } + } + + + + +` + + +/** + * + */ \ No newline at end of file