refactored

straightened up home view
tweaked the editor
This commit is contained in:
miggymofongo 2025-01-06 19:46:24 -04:00
parent e440f19188
commit d4d85804bd
50 changed files with 388 additions and 2495 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View file

@ -1,57 +0,0 @@
import{i as l,r as p,x as h,s as m,t as c}from"./index-COc6jZBc.js";const d=l`
@media(min-width: 1000px) {
sl-card {
max-width: 70vw;
}
}
`;var u=Object.defineProperty,y=Object.getOwnPropertyDescriptor,f=(n,o,a,t)=>{for(var e=t>1?void 0:t?y(o,a):o,s=n.length-1,r;s>=0;s--)(r=n[s])&&(e=(t?r(o,a,e):r(e))||e);return t&&e&&u(o,a,e),e};let i=class extends p{render(){return h`
<app-header ?enableBack="${!0}"></app-header>
<main><div id="welcomeBar">
<h2>Frequently Asked Questions</h2>
<sl-card id="welcomeCard">
<h3>What am I looking at?</h3>
<p>
This is a personal micro-social media client that can be installed to
your desktop or smartphone home screen. You can sign in via an extension
to display your profile data and notes from my relay. You
can compose a note using a rich text editor I built with Prosemirror.
</p>
<h3>How do I install this on my home screen?</h3>
<p>Look for "Add to Home Screen" in your browser toolbar
to install it to your homescreen. </p>
<h3>What is Nostr?</h3>
<p>Notes and Other Stuff Transmitted Over Relays is a simple open source
social media protocol that enables anybody to implement social media functionalities
into their websites.</p>
<hr>
<h2>Technology Used</h2>
<ul>
<li>
I am using <a href="https://www.npmjs.com/package/nostr-tools?activeTab=dependencies">Nostr Tools</a> to implement an extension
sign in and pull notes from my personal relay.
</li>
<li>
<a href="https://lit.dev">Lit Web Components Library</a>
</li>
<li>
<a href="https://github.com/hoytech/strfry"
>Strfry</a>
</li>
</ul>
</sl-card></div>
</main>
`}};i.styles=[m,d,l`
`];i=f([c("app-about")],i);export{i as AppAbout};
//# sourceMappingURL=app-about-BGKiJfUr.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"app-about-BGKiJfUr.js","sources":["../../src/pages/app-about/about-styles.ts","../../src/pages/app-about/app-about.ts"],"sourcesContent":["import { css } from 'lit';\n\n// these styles can be imported from any component\n// for an example of how to use this, check /pages/about-about.ts\nexport const styles = css`\n @media(min-width: 1000px) {\n sl-card {\n max-width: 70vw;\n }\n }\n`;","import { LitElement, html, css } from 'lit';\nimport { customElement } from 'lit/decorators.js';\n\nimport { styles } from './about-styles';\n\nimport { styles as sharedStyles } from '../../styles/shared-styles'\nimport '@shoelace-style/shoelace/dist/components/card/card.js';\n\n@customElement('app-about')\nexport class AppAbout extends LitElement {\n static styles = [\n sharedStyles,\n styles,\n css`\n\n `\n ]\n\n render() {\n return html`\n <app-header ?enableBack=\"${true}\"></app-header>\n\n <main><div id=\"welcomeBar\">\n <h2>Frequently Asked Questions</h2>\n\n<sl-card id=\"welcomeCard\">\n <h3>What am I looking at?</h3>\n\n <p>\n This is a personal micro-social media client that can be installed to\n your desktop or smartphone home screen. You can sign in via an extension\n to display your profile data and notes from my relay. You\n can compose a note using a rich text editor I built with Prosemirror.\n </p>\n\n\n\n <h3>How do I install this on my home screen?</h3>\n <p>Look for \"Add to Home Screen\" in your browser toolbar\n to install it to your homescreen. </p>\n\n <h3>What is Nostr?</h3>\n <p>Notes and Other Stuff Transmitted Over Relays is a simple open source\n social media protocol that enables anybody to implement social media functionalities\n into their websites.</p>\n\n <hr>\n <h2>Technology Used</h2>\n\n <ul>\n <li>\n I am using <a href=\"https://www.npmjs.com/package/nostr-tools?activeTab=dependencies\">Nostr Tools</a> to implement an extension\n sign in and pull notes from my personal relay.\n </li>\n\n <li>\n <a href=\"https://lit.dev\">Lit Web Components Library</a>\n </li>\n\n <li>\n <a href=\"https://github.com/hoytech/strfry\"\n >Strfry</a>\n </li>\n </ul>\n </sl-card></div>\n </main>\n `;\n }\n}\n"],"names":["styles","css","AppAbout","LitElement","html","sharedStyles","__decorateClass","customElement"],"mappings":"oEAIO,MAAMA,EAASC;AAAAA;AAAAA;AAAAA;AAAAA;AAAAA;AAAAA,kMCKT,IAAAC,EAAN,cAAuBC,CAAW,CASvC,QAAS,CACA,OAAAC;AAAAA,iCACsB,EAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAAA,CAgDrC,EA3DaF,EACJ,OAAS,CACdG,EACAL,EACAC;AAAAA;AAAAA,KAGF,EAPWC,EAANI,EAAA,CADNC,EAAc,WAAW,CAAA,EACbL,CAAA"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
:root{--font-family: sans-serif}html,body{font-family:var(--font-family);padding:0;margin:0;height:100%}@media (prefers-color-scheme: dark){html,body{background-color:#181818;color:#fff}}@media (prefers-color-scheme: light){html,body{background-color:#f5f5f5;color:#000}}

View file

@ -1,57 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>fostr</title>
<base href="/" />
<!-- This meta viewport ensures the webpage's dimensions change according to the device it's on. This is called Responsive Web Design.-->
<meta name="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0" />
<meta name="description" content="This is a fostr app" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#181818" />
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#f3f3f3" />
<!-- These meta tags are Apple-specific, and set the web application to run in full-screen mode with a black status bar. Learn more at https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html-->
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="fostr" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<!-- Imports an icon to represent the document. -->
<link rel="icon" href="/dist2/assets/icons/casto_kingdom.png" type="image/png" />
<!-- Imports the manifest to represent the web application. A web app must have a manifest to be a PWA. -->
<link rel="manifest" href="/dist2/manifest.json" />
<!-- light mode and dark mode CSS -->
<link rel="stylesheet" media="(prefers-color-scheme:light)"
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.16.0/dist/themes/light.css">
<link rel="stylesheet" media="(prefers-color-scheme:dark)"
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.16.0/dist/themes/dark.css"
onload="document.documentElement.classList.add('sl-theme-dark');">
<script type="module" crossorigin src="/dist2/code/index-COc6jZBc.js"></script>
<link rel="stylesheet" crossorigin href="/dist2/code/index-DkYRBo1e.css">
</head>
<body>
<!-- Our app-index web component. This component is defined in src/pages/app-index.ts-->
<app-index></app-index>
<script>
if ('serviceWorker' in navigator) {
window.onload = () => {
navigator.serviceWorker.register(
'/sw.js'
);
}
}
</script>
</body>
</html>

View file

@ -1,86 +0,0 @@
{
"id": "/",
"scope": "/",
"name": "fostr",
"display": "standalone",
"start_url": "/",
"short_name": "starter",
"theme_color": "#E1477E",
"description": "This is a fostr app",
"orientation": "any",
"background_color": "#E1477E",
"related_applications": [],
"prefer_related_applications": false,
"display_override": ["window-controls-overlay"],
"launch_handler": {
"client_mode": "focus-existing"
},
"icons": [
{
"src": "assets/icons/512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "assets/icons/192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "assets/icons/48x48.png",
"sizes": "48x48",
"type": "image/png"
},
{
"src": "assets/icons/24x24.png",
"sizes": "24x24",
"type": "image/png"
}
],
"screenshots": [
{
"src": "assets/screenshots/screen.png",
"sizes": "1617x1012",
"type": "image/png"
}
],
"features": [
"Cross Platform",
"fast",
"simple"
],
"categories": [
"social"
],
"shortcuts": [
{
"name": "Open About",
"short_name": "About",
"description": "Open the about page",
"url": "/about",
"icons": [{ "src": "assets/icons/192x192.png", "sizes": "192x192" }]
}
],
"widgets": [
{
"name": "Starter Widget",
"tag": "starterWidget",
"ms_ac_template": "widget/ac.json",
"data": "widget/data.json",
"description": "A simple widget example from pwa-starter.",
"screenshots": [
{
"src": "assets/screenshots/widget-screen.png",
"sizes": "500x500",
"label": "Widget screenshot"
}
],
"icons": [
{
"src": "assets/icons/48x48.png",
"sizes": "48x48"
}
]
}
]
}

View file

@ -1,6 +0,0 @@
{
"navigationFallback": {
"rewrite": "index.html",
"exclude": ["*.{css,js,mjs,ts,png,gif,ico,jpg,svg,json,woff2,ttf}"]
}
}

View file

@ -1,2 +0,0 @@
importScripts("https://storage.googleapis.com/workbox-cdn/releases/7.3.0/workbox-sw.js");self.addEventListener("widgetinstall",t=>{t.waitUntil(e(t))});self.addEventListener("widgetresume",t=>{t.waitUntil(e(t))});self.addEventListener("widgetclick",t=>{t.action=="updateName"&&t.waitUntil(s(t))});self.addEventListener("widgetuninstall",t=>{});const e=async t=>{const a=t.widget.definition,i={template:JSON.stringify(await(await fetch(a.msAcTemplate)).json()),data:JSON.stringify(await(await fetch(a.data)).json())};await self.widgets.updateByInstanceId(t.instanceId,i)},s=async t=>{const a=t.data.json().name,i=t.widget.definition,n={template:JSON.stringify(await(await fetch(i.msAcTemplate)).json()),data:JSON.stringify({name:a})};await self.widgets.updateByInstanceId(t.instanceId,n)};workbox.precaching.precacheAndRoute([{"revision":"fe970448513b18d0c37b4b671ca94a06","url":"assets/icons/192x192.png"},{"revision":"5b1c195400df0f16c3f6cb2aa4665622","url":"assets/icons/24x24.png"},{"revision":"c3f013b68eae92565e8337ee50472cdc","url":"assets/icons/48x48.png"},{"revision":"e2a009c0f6aaa11d03a8e0728869b583","url":"assets/icons/512x512.png"},{"revision":"d1e55c63934b5f79f366bf723605bc26","url":"assets/icons/casto_kingdom.png"},{"revision":"a80ffffb8d3a555b25a0f19a0a9878f1","url":"assets/icons/icon_192.png"},{"revision":"04fa5b0f5827d885b678f0d131406aab","url":"assets/icons/icon_24.png"},{"revision":"3dbd388868265c8498acfe676ef27811","url":"assets/icons/icon_48.png"},{"revision":"414aacb61ad294e75f267554db7e9368","url":"assets/icons/icon_512.png"},{"revision":"67906584562cfe06b57d99c15a470a8d","url":"assets/img/default_pfp.png"},{"revision":"ee332fabed8b56f4aae86902c18146a2","url":"assets/readme/build-output.png"},{"revision":"be7f5a61d03a119f2e297d15df7dfab2","url":"assets/readme/codespace-button.png"},{"revision":"969196419238157c3d673545c2daed32","url":"assets/readme/copy-starter.png"},{"revision":"65897273261e49c6c9148df875e13e4d","url":"assets/readme/git-clone.png"},{"revision":"cd10ec0be4be4b10195986d075904542","url":"assets/readme/intro.png"},{"revision":"1b40f28a11a334cc90e6802070b9eae1","url":"assets/readme/local-button.png"},{"revision":"184b8b88c43aa759948968f69251200e","url":"assets/readme/new-repo-from-starter.png"},{"revision":"a5a5ef96823c312160626df383d0925d","url":"assets/readme/pwa-running.png"},{"revision":"480b506d1a2a832131525fd4d0fd7478","url":"assets/readme/pwa-starter-overview.png"},{"revision":"865934771530a3b8f9e10aae8d0423d9","url":"assets/readme/static-web-app-slash.png"},{"revision":"2d67dfcbdc3c8c6f8b5fefcd0016021f","url":"assets/readme/use-this-template.png"},{"revision":"fc3d41a79144a5bb00a423b78ec73c1f","url":"assets/readme/vscode-in-browser.png"},{"revision":"808ac8889e4c17f3f83fd9235f1aa28d","url":"assets/screenshots/screen.png"},{"revision":"bd2d64ab3b867df1017d43ec20b73cca","url":"assets/screenshots/widget-screen.png"},{"revision":null,"url":"code/app-about-BGKiJfUr.js"},{"revision":null,"url":"code/app-write-DLE2Mxxa.js"},{"revision":null,"url":"code/index-CBloBB_n.js"},{"revision":null,"url":"code/index-COc6jZBc.js"},{"revision":null,"url":"code/index-DkYRBo1e.css"},{"revision":"f5266149bb7950e2721802c8a674af23","url":"index.html"},{"revision":"288b15a933c65f7ad15d8d4567d18212","url":"manifest.json"},{"revision":"b1a4910d7cbaa47fcc8f72ecb2028ff1","url":"staticwebapp.config.json"},{"revision":"a7591c595c4a231a1f67943e58f3d6eb","url":"widget/ac.json"},{"revision":"a70ebb50d5f5ab6a37f24795e292547d","url":"widget/data.json"}]||[]);
//# sourceMappingURL=sw.js.map

View file

@ -1 +0,0 @@
{"version":3,"sources":["../public/sw.js"],"names":["event","updateWidget","updateName","widgetDefinition","payload","name"],"mappings":"AAAA,cACI,yEACJ,EAMA,KAAK,iBAAiB,gBAAkB,GAAU,CAC9C,EAAM,UAAU,EAAa,CAAK,CAAC,CACvC,CAAC,EAGD,KAAK,iBAAiB,eAAiB,GAAU,CAC7C,EAAM,UAAU,EAAa,CAAK,CAAC,CACvC,CAAC,EAID,KAAK,iBAAiB,cAAgB,GAAU,CAC5C,EAAM,QAAU,cAChB,EAAM,UAAU,EAAW,CAAK,CAAC,CAErC,CAAC,EAID,KAAK,iBAAiB,kBAAoB,GAAU,CAAA,CAAE,EAEtD,MAAM,EAAe,MAAO,GAAU,CAElC,MAAM,EAAmB,EAAM,OAAO,WAGhC,EAAU,CACZ,SAAU,KAAK,UAAU,MAAO,MAAM,MAAM,EAAiB,YAAY,GAAG,MAAM,EAClF,KAAM,KAAK,UAAU,MAAO,MAAM,MAAM,EAAiB,IAAI,GAAG,MAAM,CACzE,EAGD,MAAM,KAAK,QAAQ,mBAAmB,EAAM,WAAY,CAAO,CACnE,EAEM,EAAa,MAAO,GAAU,CAChC,MAAM,EAAO,EAAM,KAAK,KAAM,EAAC,KAGzB,EAAmB,EAAM,OAAO,WAGhC,EAAU,CACZ,SAAU,KAAK,UAAU,MAAO,MAAM,MAAM,EAAiB,YAAY,GAAG,MAAM,EAClF,KAAM,KAAK,UAAU,CAAC,KAAA,CAAI,CAAC,CAC9B,EAGD,MAAM,KAAK,QAAQ,mBAAmB,EAAM,WAAY,CAAO,CACnE,EAEA,QAAQ,WAAW,iBAAiB,+mFAAK,eAAiB,CAAA,CAAE","file":"sw.js","sourcesContent":["importScripts(\n 'https://storage.googleapis.com/workbox-cdn/releases/7.3.0/workbox-sw.js'\n);\n\n// This is your Service Worker, you can put any of your custom Service Worker\n// code in this file, above the `precacheAndRoute` line.\n\n// When widget is installed/pinned, push initial state.\nself.addEventListener('widgetinstall', (event) => {\n event.waitUntil(updateWidget(event));\n});\n\n// When widget is shown, update content to ensure it is up-to-date.\nself.addEventListener('widgetresume', (event) => {\n event.waitUntil(updateWidget(event));\n});\n\n// When the user clicks an element with an associated Action.Execute,\n// handle according to the 'verb' in event.action.\nself.addEventListener('widgetclick', (event) => {\nif (event.action == \"updateName\") {\n event.waitUntil(updateName(event));\n}\n});\n\n// When the widget is uninstalled/unpinned, clean up any unnecessary\n// periodic sync or widget-related state.\nself.addEventListener('widgetuninstall', (event) => {});\n\nconst updateWidget = async (event) => {\n// The widget definition represents the fields specified in the manifest.\n const widgetDefinition = event.widget.definition;\n\n // Fetch the template and data defined in the manifest to generate the payload.\n const payload = {\n template: JSON.stringify(await (await fetch(widgetDefinition.msAcTemplate)).json()),\n data: JSON.stringify(await (await fetch(widgetDefinition.data)).json()),\n };\n\n // Push payload to widget.\n await self.widgets.updateByInstanceId(event.instanceId, payload);\n}\n\nconst updateName = async (event) => {\n const name = event.data.json().name;\n\n // The widget definition represents the fields specified in the manifest.\n const widgetDefinition = event.widget.definition;\n\n // Fetch the template and data defined in the manifest to generate the payload.\n const payload = {\n template: JSON.stringify(await (await fetch(widgetDefinition.msAcTemplate)).json()),\n data: JSON.stringify({name}),\n };\n\n // Push payload to widget.\n await self.widgets.updateByInstanceId(event.instanceId, payload);\n}\n\nworkbox.precaching.precacheAndRoute(self.__WB_MANIFEST || []);"]}

View file

@ -1,24 +0,0 @@
{
"type": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"text": "Hello ${$root.name}!",
"wrap": true,
"horizontalAlignment": "Center",
"size": "ExtraLarge"
},
{
"type": "Input.Text",
"placeholder": "Name",
"id": "name",
"inlineAction": {
"type": "Action.Execute",
"verb": "updateName",
"title": "Submit"
}
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.6"
}

View file

@ -1,3 +0,0 @@
{
"name": "Widget"
}

View file

@ -24,11 +24,13 @@
<meta name="pwa-starter-template-identity" content="pwa-starter"/>
<!-- Imports an icon to represent the document. -->
<link rel="icon" href="/assets/icons/icon_24.png" type="image/png" />
<link rel="icon" href="/assets/icons/keyblade.png" type="image/png" />
<!-- Imports the manifest to represent the web application. A web app must have a manifest to be a PWA. -->
<link rel="manifest" href="manifest.json" />
<!-- light mode and dark mode CSS -->
<link rel="stylesheet" media="(prefers-color-scheme:light)"
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.16.0/dist/themes/light.css">

BIN
public/assets/icons/Keyblade.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -1,12 +1,12 @@
{
"id": "/",
"id": "https://miguelalmodo.com/dist2",
"scope": "/",
"name": "fostr",
"display": "standalone",
"start_url": "/",
"short_name": "starter",
"short_name": "micro app",
"theme_color": "#E1477E",
"description": "This is a fostr app",
"description": "This is a miggymofongo project",
"orientation": "any",
"background_color": "#E1477E",
"related_applications": [],

View file

@ -16,6 +16,10 @@ export class AppAbout extends LitElement {
`
]
connectedCallback(): void {
super.connectedCallback();
console.log('AppAbout added to the DOM')
}
render() {
return html`
<app-header ?enableBack="${true}"></app-header>

View file

@ -55,10 +55,10 @@ Profile Picture Container
*/
.profile-picture-container {
display: grid;
grid-template-columns: 400px 200px;
align-itens: center;
grid-template-columns: 350px 150px;
grid-template-rows: 250px;
grid-column-gap: 15px;
margin-bottom: 12px;
}
@ -68,10 +68,11 @@ Profile Picture Container
.profile-pic {
grid-area: 1/1;
margin-top: 25px;
margin-top: 5px;
margin-left: 100px;
margin-bottom: 10px;
border-radius: 50%;
margin-bottom: 5px;
border-radius: 50%; /* this makes the profile picture round */
}
.personal-msg {
@ -207,9 +208,9 @@ Profile Picture Container
</div>
<div class="nav-button-container">
<sl-button href="${resolveRouterPath('about')}" variant="primary">About this Web App</sl-button>
<sl-button href="${resolveRouterPath('about')}" variant="primary">About</sl-button>
<sl-button href="${resolveRouterPath('note-wall')}" variant="primary">Feed</sl-button>
<sl-button href="${resolveRouterPath('write')}" variant="primary">Compose a Note</sl-button>
<sl-button href="${resolveRouterPath('write')}" variant="primary">Compose</sl-button>
</div>
${'share' in navigator
? html`<sl-button slot="footer" variant="default" @click="${this.share}">

View file

@ -1,5 +1,5 @@
import { LitElement, css, html } from 'lit';
import { property, customElement, query } from 'lit/decorators.js';
import { customElement, query } from 'lit/decorators.js';
import '@shoelace-style/shoelace/dist/components/card/card.js';
@ -7,18 +7,18 @@ import '@shoelace-style/shoelace/dist/components/button/button.js';
import { styles } from '../../styles/shared-styles';
import {EditorState} from 'prosemirror-state'
import { Transaction } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { Schema } from 'prosemirror-model';
import { MenuBar } from './write-menu';
import { keymap } from 'prosemirror-keymap';
import { toggleMark } from 'prosemirror-commands';
import {undo, redo, history} from 'prosemirror-history'
import { baseKeymap } from 'prosemirror-commands';
import {createMenuPlugin} from '../../components/menu-plugin'
export const customSchema = new Schema({
@ -80,8 +80,12 @@ export const customSchema = new Schema({
},
});
// Create the menu plugin
const menuPlugin = createMenuPlugin(customSchema);
// function for error reports
function errorReport(message: string): void {
console.error(message);
}
// Commands
function insertStar(state: EditorState, dispatch?: (tr: Transaction) => void): boolean {
@ -102,6 +106,10 @@ function insertStar(state: EditorState, dispatch?: (tr: Transaction) => void): b
return true;
}
/* this command will prompt you for a url, which will
apply link mark to your selection and make your selection a hyperlink */
function toggleLink(state: EditorState, dispatch?: (tr: Transaction) => void): boolean {
let {doc, selection} = state
if (selection.empty) return false
@ -114,10 +122,18 @@ function toggleLink(state: EditorState, dispatch?: (tr: Transaction) => void): b
}
// Keymap
/* starting to build a function that inserts an <hr> line break
function insertHR(state: EditorState, dispatch?: (tr: Transaction) => void): boolean {
const type = customSchema.nodes.boring_paragraph
return false
} */
// custom keymap to apply to state
const customKeymap = keymap({
'Ctrl-Space': insertStar,
"Ctrl-b": (state, dispatch) => {
'Ctrl-b': (state, dispatch) => {
console.log("Ctrl-b pressed, toggling shouting mark...");
return toggleMark(customSchema.marks.shouting)(state, dispatch);
},
@ -126,87 +142,170 @@ const customKeymap = keymap({
return toggleLink(state, dispatch);
},
});
const menuItems = [
{
label: 'Bold',
command: (state, dispatch, view) => {
// Example command logic for bold
console.log('Bold clicked');
return true; // Return true if active, false otherwise
},
},
{
label: 'Italic',
command: (state, dispatch, view) => {
console.log('Italic clicked');
return true;
},
},
];
@customElement('app-write')
export class AppWrite extends LitElement {
@property({ type: String }) placeholder: string = 'Compose your note...';
@property({ type: String }) public value?: string;
// Reference to the ProseMirror container
@query('#editor') editorContainer!: HTMLElement;
// references the class name of the container that prosemirror's editor
// will render to
@query('.ProseMirror') editorContainer!: HTMLElement;
private editorView!: EditorView
static styles = [
styles, css`
:host {
display: block;
styles,
css`
.ProseMirror {
position: relative;
white-space: pre-wrap;
word-wrap: break-word;
outline: none;
background-color: white;
color: black;
border-radius: 4px;
padding: 10px;
min-height: 200px; /* Ensure there's enough height to type */
}
.ProseMirror:empty::before {
content: "Start typing here...";
color: black;
pointer-events: none; /* Ensures the placeholder doesn't block typing */
}
shouting {
all: unset; /* Remove inherited or conflicting styles */
font-weight: bold;
text-transform: uppercase;
color: red;
}
`
];
.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 */
}
protected async firstUpdated() {
console.log("Welcome to the compose page");
await this.updateComplete;
this.initializeEditor()
.ProseMirror pre {
white-space: pre-wrap;
}
}
private initializeEditor() {
if (!this.editorContainer) {
console.error('Editor container not here');
return
.ProseMirror li {
position: relative;
}
.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;
}
`
];
protected async firstUpdated() {
console.log("Welcome to the compose page");
await this.updateComplete;
console.log('Rendered HTML:', this.shadowRoot?.innerHTML);
this.initializeEditor()
if (!this.editorContainer) {
errorReport('Editor container not here');
return;
}
}
private initializeEditor() {
if (!this.editorContainer) {
errorReport('Editor container not here');
return;
}
/* this is a schema of initial content to pre-populate the */
const initialContent = customSchema.nodes.doc.createAndFill([
customSchema.nodes.paragraph.create(
null,
customSchema.text("Welcome to the editor! Try typing here.")
),
customSchema.nodes.paragraph.create(
null,
customSchema.text("Press Ctrl+Space to insert a ⭐.")
),
customSchema.nodes.paragraph.create(
null,
customSchema.text("Highlight text and press Ctrl+B to SHOUT it!")
),
]);
null,
customSchema.text("Welcome to the editor! Start typing here.")
)]);
if (!initialContent) {
console.error("failed to create initial document")
return;
}
if (!initialContent) {
console.error("failed to create initial document")
return;
}
const state = EditorState.create({
doc: initialContent,
plugins: [
menuPlugin,
history(),
keymap({
'Mod-z': undo,
'Mod-y': redo,
}),
keymap(baseKeymap),
// Add the plus-button plugin here
customKeymap
],
});
const state = EditorState.create({
doc: initialContent,
plugins: [
history(),
keymap({
'Mod-z': undo,
'Mod-y': redo,
}),
customKeymap,
keymap(baseKeymap),
],
});
@ -219,20 +318,19 @@ private initializeEditor() {
let newState = view.state.apply(transaction)
view.updateState(newState)
}
});
this.editorView = view;
console.log(state.plugins);
});
console.log('editor initialized')
}
/* connectedCallback(): void {
connectedCallback(): void {
super.connectedCallback();
if(!this.editorView) {
this.editorView;
}
} */
console.log('AppWrite added to the DOM')
}
disconnectedCallback(): void {
super.disconnectedCallback();
@ -249,29 +347,10 @@ private initializeEditor() {
<app-header ?enableBack="${true}"></app-header>
<main>
<div id="welcomeBar">
<sl-card id="welcomeCard">
<div slot="header">
<h2>Editor Demo</h2>
</div>
<div class="ProseMirror"></div>
<h3>Instructions</h3>
<p>Click inside the white text area to highlight with your cursor.
You can type and press enter to insert a new block below.</p>
<br>
<p>Try to highlight a piece of text, then CTRL+X to cut and
CTRL+V to paste.</p><br>
<p>Press Ctrl+Space to add a yellow star to the document. </p>
<p>Press Ctrl+B over a highlighted selection to add a "shouting"
mark and format it all caps and red.</p>
</sl-card>
<sl-card>
<div id="editor" class="ProseMirror"></div>
</sl-card>
</main>
`}
}
`;
}
}

View file

@ -0,0 +1,101 @@
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)

View file

@ -0,0 +1,80 @@
import { css } from "lit"
export const editorStyles = css` {
.ProseMirror {
background: white;
color: black;
background-clip: padding-box;
padding: 5px 0;
position: relative;
word-wrap: break-word;
white-space: pre-wrap;
-webkit-font-variant-ligatures: none;
font-variant-ligatures: none;
padding: 1rem;
line-height: 1.2;
outline: none;
font-family: var(
--markdown-editor-typography-font-family,
var(--mdc-typography-font-family, Montserrat, sans-serif)
);
font-size: var(
--markdown-editor-typography-font-size,
var(--mdc-typography-subtitle1-font-size, 1rem)
);
font-weight: var(
--markdown-editor-typography-font-weight,
var(--mdc-typography-subtitle1-font-weight, 400)
);
letter-spacing: var(
--markdown-editor-typography-letter-spacing,
var(--mdc-typography-subtitle1-letter-spacing, 0.009375em)
);
}
.ProseMirror pre {
white-space: pre-wrap;
}
.ProseMirror a {
color: var(--markdown-editor-typography-anchor-color, -webkit-link);
text-decoration: var(--markdown-editor-typography-anchor-text-decoration);
}
.ProseMirror-focused .ProseMirror-gapcursor {
display: block;
}
shouting {
all: unset; /* Remove inherited or conflicting styles */
font-weight: bold;
text-transform: uppercase;
color: red;
}
.boring {
background: grey;
}
.plus {
position: absolute;
padding: 8px;
background-color: #4CAF50;
color: red;
border: none;
cursor: pointer;
}
.plus:hover {
background-color: #45a049;
}
`

View file

@ -46,7 +46,7 @@ export const router = new Router({
plugins: [
lazy(() => import('./pages/app-write/app-write.js')),
],
render: () => html`<app-write><app-write>`
render: () => html`<app-write></app-write>`
},
]
});

View file

@ -3,468 +3,48 @@ import { css } from 'lit';
// these styles can be imported from any component
// for an example of how to use this, check /pages/about-about.ts
export const styles = css`
@media(min-width: 1000px) {
sl-card {
max-width: 70vw;
}
}
main {
margin-top: 70px;
margin-top: 100px;
padding: 12px;
}
.ProseMirror {
position: relative;
word-wrap: break-word;
white-space: pre-wrap;
-webkit-font-variant-ligatures: none;
font-variant-ligatures: none;
padding: 1rem;
line-height: 1.2;
outline: none;
font-family: var(
--markdown-editor-typography-font-family,
var(--mdc-typography-font-family, Montserrat, sans-serif)
);
font-size: var(
--markdown-editor-typography-font-size,
var(--mdc-typography-subtitle1-font-size, 1rem)
);
font-weight: var(
--markdown-editor-typography-font-weight,
var(--mdc-typography-subtitle1-font-weight, 400)
);
letter-spacing: var(
--markdown-editor-typography-letter-spacing,
var(--mdc-typography-subtitle1-letter-spacing, 0.009375em)
);
}
.ProseMirror pre {
white-space: pre-wrap;
}
.ProseMirror li {
position: relative;
}
.ProseMirror p:first-child,
.ProseMirror h1:first-child,
.ProseMirror h2:first-child,
.ProseMirror h3:first-child,
.ProseMirror h4:first-child,
.ProseMirror h5:first-child,
.ProseMirror h6:first-child {
margin-top: 10px;
}
.ProseMirror a {
color: var(--markdown-editor-typography-anchor-color, -webkit-link);
text-decoration: var(--markdown-editor-typography-anchor-text-decoration);
}
.ProseMirror p {
margin-bottom: 1em;
}
.ProseMirror-hideselection {
caret-color: transparent;
}
.ProseMirror-hideselection *::selection,
.ProseMirror-hideselection *::-moz-selection {
background: transparent;
}
.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;
}
.ProseMirror-textblock-dropdown {
min-width: 3em;
}
.ProseMirror-menu {
margin: 0 -4px;
line-height: 1;
}
.ProseMirror-tooltip .ProseMirror-menu {
width: -webkit-fit-content;
width: fit-content;
white-space: pre;
}
.ProseMirror-menuitem {
margin: 0.25rem 0.25rem 0.25rem 0;
display: flex;
justify-content: center;
align-items: center;
border-radius: 4px;
overflow: hidden;
}
.ProseMirror-menuitem:hover {
background-color: #f5f5f5;
}
.ProseMirror-menuseparator {
margin: 0 8px;
}
.ProseMirror-menu-dropdown,
.ProseMirror-menu-dropdown-menu {
font-size: 90%;
white-space: nowrap;
}
.ProseMirror-menu-dropdown {
vertical-align: 1px;
cursor: pointer;
position: relative;
padding-right: 15px;
}
.ProseMirror-menu-dropdown-wrap {
padding: 1px 0 1px 4px;
display: inline-block;
position: relative;
}
.ProseMirror-menu-dropdown:after {
content: '';
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid currentColor;
opacity: 0.6;
position: absolute;
right: 4px;
top: calc(50% - 2px);
}
.ProseMirror-menu-dropdown-menu,
.ProseMirror-menu-submenu {
position: absolute;
background: white;
color: #666;
border: 1px solid #aaa;
padding: 2px;
}
.ProseMirror-menu-dropdown-menu {
z-index: 1;
min-width: 6em;
}
.ProseMirror-menu-dropdown-item {
cursor: pointer;
}
.ProseMirror-menu-dropdown-item:hover {
background: #f2f2f2;
}
.ProseMirror-menu-dropdown-item > div {
padding: 0.375rem 0.5rem;
}
.ProseMirror-menu-submenu-wrap {
position: relative;
margin-right: -4px;
}
.ProseMirror-menu-submenu-label:after {
content: '';
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
border-left: 4px solid currentColor;
opacity: 0.6;
position: absolute;
right: 4px;
top: calc(50% - 4px);
}
.ProseMirror-menu-submenu {
display: none;
min-width: 4em;
left: 100%;
top: -3px;
}
.ProseMirror-menu-active {
background: #eee;
border-radius: 4px;
color: black;
}
.ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu,
.ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu {
display: block;
}
.ProseMirror-menubar {
display: flex;
flex-wrap: wrap;
position: relative;
background: white;
min-height: 1em;
overflow: visible;
z-index: 2;
top: 0;
left: 0;
right: 0;
color: #666;
padding: 1px 6px;
border-top-left-radius: inherit;
border-top-right-radius: inherit;
border-bottom: 1px solid
var(--markdown-editor-outline-idle-border-color, rgba(0, 0, 0, 0.38));
box-sizing: border-box;
-moz-box-sizing: border-box;
}
.ProseMirror-icon {
display: inline-block;
line-height: 0.8;
vertical-align: -2px;
/* Compensate for padding */
padding: 2px 8px;
cursor: pointer;
}
.ProseMirror-menu-disabled {
color: rgba(0,0,0,0.37);
background-color: rgba(0,0,0,0.12);
cursor: not-allowed;
}
.ProseMirror-menu-disabled.ProseMirror-icon {
cursor: not-allowed;
}
.ProseMirror-icon svg {
fill: currentColor;
height: 1em;
}
.ProseMirror-icon span {
vertical-align: text-top;
}
.ProseMirror-gapcursor {
display: none;
pointer-events: none;
position: absolute;
}
.ProseMirror-gapcursor:after {
content: '';
display: block;
position: absolute;
top: -2px;
width: 20px;
border-top: 1px solid black;
animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
}
@keyframes ProseMirror-cursor-blink {
to {
visibility: hidden;
}
}
.ProseMirror-focused .ProseMirror-gapcursor {
display: block;
}
/* Add space around the hr to make clicking it easier */
.ProseMirror-example-setup-style hr {
padding: 2px 10px;
border: none;
margin: 1em 0;
}
.ProseMirror-example-setup-style hr:after {
content: '';
display: block;
height: 1px;
background-color: silver;
line-height: 2px;
}
.ProseMirror ul,
.ProseMirror ol {
padding-left: 30px;
}
.ProseMirror blockquote {
padding-left: 1em;
border-left: 3px solid #eee;
margin-left: 0;
margin-right: 0;
}
.ProseMirror-example-setup-style img {
cursor: default;
}
.ProseMirror-invalid {
background: #ffc;
border: 1px solid #cc7;
border-radius: 4px;
padding: 5px 10px;
position: absolute;
min-width: 10em;
}
.ProseMirror h1.title.empty-node::before,
.ProseMirror h2.instructional-prompt.empty-node::before,
.ProseMirror h3.mechanical-promp.empty-node::before {
content: 'Enter title here...';
}
.ProseMirror div.passage-subtitle.empty-node:first-child::before {
content: 'Enter subtitle here...';
}
.ProseMirror div.passage-author.empty-node:first-child::before,
.ProseMirror div.passage-cast-title.empty-node:first-child::before,
.ProseMirror div.passage-act-title.empty-node:first-child::before,
.ProseMirror div.passage-scene-title.empty-node:first-child::before,
.ProseMirror div.passage-verse.empty-node:first-child::before,
.ProseMirror div.passage-footnotes.empty-node:first-child::before,
.ProseMirror div.paragraph.empty-node:first-child::before {
content: 'Enter text here...';
}
div[contenteditable]:focus h1.title.empty-node::before,
div[contenteditable]:focus h2.instructional-prompt.empty-node::before,
div[contenteditable]:focus h3.mechanical-promp.empty-node::before,
div[contenteditable]:focus
div.passage-subtitle.empty-node:first-child::before,
div[contenteditable]:focus div.passage-author.empty-node:first-child::before,
div[contenteditable]:focus
div.passage-cast-title.empty-node:first-child::before,
div[contenteditable]:focus
div.passage-act-title.empty-node:first-child::before,
div[contenteditable]:focus
div.passage-scene-title.empty-node:first-child::before,
div[contenteditable]:focus div.passage-verse.empty-node:first-child::before,
div[contenteditable]:focus
div.passage-footnotes.empty-node:first-child::before,
div[contenteditable]:focus div.paragraph.empty-node:first-child::before {
content: '';
}
.ProseMirror .empty-node::before {
position: absolute;
color: #aaa;
cursor: text;
font-style: italic;
}
#editor, .editor {
background: white;
color: black;
background-clip: padding-box;
padding: 5px 0;
}
#editor[disabled] .ProseMirror-menubar {
display: none!important;
}
drop-down-editor rich-text .inline-component-button,
expand-collapse rich-text .inline-component-button,
flip-reveal rich-text .inline-component-button,
hint-list rich-text .inline-component-button,
option-list rich-text .inline-component-button,
plankton-passage rich-text .inline-component-button {
pointer-events: none;
color: lightgray;
}
#ProseMirror-icon-collection path {
fill-rule: evenodd;
}
#welcomeBar {
#welcomeBar {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
#welcomeCard,
#welcomeCard,
#infoCard {
padding: 18px;
padding-top: 0px;
}
sl-card::part(footer) {
sl-card::part(footer) {
display: flex;
justify-content: flex-end;
}
@media(min-width: 750px) {
@media(min-width: 750px) {
sl-card {
width: 70vw;
}
}
@media (horizontal-viewport-segments: 2) {
@media(min-width: 1000px) {
sl-card {
max-width: 70vw;
}
}
@media (horizontal-viewport-segments: 2) {
#welcomeBar {
flex-direction: row;
align-items: flex-start;
justify-content: space-between;
}
@media (horizontal-viewport-segments: 2) {
#welcomeBar {
flex-direction: row;
align-items: flex-start;
justify-content: space-between;
}
}
.boring {
background: grey;
}
.plus {
position: absolute;
padding: 8px;
background-color: #4CAF50;
color: red;
border: none;
cursor: pointer;
}
.plus:hover {
background-color: #45a049;
}
.ProseMirror-textblock-dropdown {
min-width: 3em;
}
}`
`