added marked and unsafe-html to render markdown from
long form notes and adjusted note wall. looking at tumblr for inspiration here...
19
README.md
|
@ -1,7 +1,6 @@
|
|||
# Personal Microclient
|
||||
|
||||
<p>
|
||||
Aren't you tired of distracting social media platforms?
|
||||
<p>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. </p>
|
||||
|
||||
|
||||
<p>
|
||||
I'm using <a href="https://prosemirror.net/">ProseMirror</a>,
|
||||
<p>I'm using <a href="https://prosemirror.net/">ProseMirror</a>,
|
||||
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.
|
||||
</p>
|
||||
intuitive that will run easily via browsers. </p>
|
||||
|
||||
|
||||
<p>I scaffolded the project with PWA Builder and am using
|
||||
|
@ -29,6 +26,16 @@ from a relay. </p>
|
|||
<p>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.</p>
|
||||
|
||||
<p>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.
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
|
||||
# TODO
|
||||
|
|
12
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
BIN
public/assets/icons/144x144.png
Normal file
After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 36 KiB |
BIN
public/assets/icons/20x20.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3.9 KiB |
BIN
public/assets/img/nostr-icon.png
Executable file
After Width: | Height: | Size: 4.5 KiB |
|
@ -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",
|
||||
|
|
|
@ -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`<sl-button size="small" href="${resolveRouterPath()}">
|
||||
Back
|
||||
</sl-button>` : null}
|
||||
<img src="/dist/assets/img/nostr-icon.png" alt="Nostr Icon" class="nostr-icon" style="width: 50px"/>
|
||||
|
||||
<h1>${this.title}</h1>
|
||||
<h2>${this.title}</h2>
|
||||
</div>
|
||||
|
||||
<div id="signin">
|
||||
<sl-button variant="primary" @click="${this.isSignedIn ? this.signOut : this.signInWithNostr}">
|
||||
${this.isSignedIn ? 'Sign out' : 'Sign in'}
|
||||
|
||||
</sl-button>
|
||||
</sl-button></div>
|
||||
</header>
|
||||
`;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
||||
}
|
|
@ -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
|
|||
<h2>Welcome to ${this.nostrAddy || 'Guest'}'s Profile</h2>
|
||||
</div>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
|
||||
<div class="profile-picture-container">
|
||||
<img class="profile-pic" src="${this.profilePic || '/assets/img/default_pfp.png'}" alt="Profile Picture" width="200px" height="200px">
|
||||
<img class="profile-pic" src="${this.profilePic || '/dist/assets/img/default_pfp.png'}" alt="Profile Picture" width="200px" height="200px">
|
||||
|
||||
<p class="personal-msg"><b>${this.bio || 'Welcome, guest! Please sign in to view your profile.'}</b></p>
|
||||
|
||||
|
@ -215,7 +196,7 @@ Profile Picture Container
|
|||
${'share' in navigator
|
||||
? html`<sl-button slot="footer" variant="default" @click="${this.share}">
|
||||
<sl-icon slot="prefix" name="share"></sl-icon>
|
||||
Share this personal website with a friend!
|
||||
Share with a homie!
|
||||
</sl-button>`
|
||||
: null}
|
||||
</sl-card>
|
||||
|
|
|
@ -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`
|
||||
<main><app-header ?enableBack="${true}"></app-header>
|
||||
<app-header ?enableBack="${true}"></app-header>
|
||||
|
||||
<main>
|
||||
<div class="ProseMirror"> Try selecting some text below to test out a minimal rich text editor.
|
||||
|
||||
<div class="ProseMirror"></div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
`;
|
||||
|
|
|
@ -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`
|
||||
|
||||
<app-header></app-header>
|
||||
|
||||
<main>
|
||||
|
||||
</main>
|
||||
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('menu-bar', MenuBar)
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
`
|
|
@ -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`
|
||||
<app-header ?enableBack="${true}"></app-header>
|
||||
<main> <div id="welcomeBar">
|
||||
<sl-card id="WelcomeCard">
|
||||
<section>
|
||||
|
||||
<header class="main-section-header">
|
||||
<h2 class="main-section-h2">Recent Notes from ${this.relayName}</h2>
|
||||
</header>
|
||||
|
||||
|
||||
|
||||
|
||||
<table class="comment-wall">
|
||||
${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`
|
||||
<tr>
|
||||
<td><h3>${note.date}</h3></td>
|
||||
<td>
|
||||
<p>${textContent}</p>
|
||||
${thumbnailUrl
|
||||
? html`<img src="${thumbnailUrl}" alt="YouTube thumbnail" style="max-width: 30%; height: 30%;">`
|
||||
: imgMatch
|
||||
? html`<img src="${imgMatch}" alt="Note image" style="max-width: 30%; height: 30%;">`
|
||||
: ''}
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
})}
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
</section></main>
|
||||
</sl-card>
|
||||
<main>
|
||||
<div id="welcomeBar">
|
||||
<sl-card id="WelcomeCard">
|
||||
<section>
|
||||
<header class="">
|
||||
<h2 class="">
|
||||
Recent Notes from ${this.relayName}
|
||||
</h2>
|
||||
</header>
|
||||
<table class="comment-wall">
|
||||
${this.notes.map(
|
||||
(note) => html`
|
||||
<tr>
|
||||
<td>
|
||||
<h3>${note.date}</h3>
|
||||
</td>
|
||||
<td class="note-content">
|
||||
${unsafeHTML(marked(note.content) as string)}
|
||||
</td>
|
||||
</tr>
|
||||
`
|
||||
)}
|
||||
</table>
|
||||
</section>
|
||||
</sl-card>
|
||||
</div>
|
||||
</main>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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')),
|
||||
],
|
||||
|
|
|
@ -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;
|
||||
}}
|
||||
`
|
||||
}
|
||||
|
||||
#welcomeCard {
|
||||
margin-right: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
`
|
||||
|
||||
|
||||
/**
|
||||
* <!-- <p>${textContent}</p>
|
||||
${thumbnailUrl
|
||||
? html`<img src="${thumbnailUrl}" alt="YouTube thumbnail" style="max-width: 30%; height: 30%;">`
|
||||
: imgMatch
|
||||
? html`<img src="${imgMatch}" alt="Note image" style="max-width: 30%; height: 30%;">`
|
||||
: ''} -->
|
||||
*/
|