started to implement a horizontal rule command
updated customKeymap with a keyboard shortcut to insert hr element updated editor appearance implemented a custom schema filler doc that prefilled a couple of nodes into the editor moved css to component because it wasn't reading from a separate component
This commit is contained in:
parent
d4d85804bd
commit
468a454323
3 changed files with 148 additions and 130 deletions
|
@ -13,14 +13,19 @@ import { Transaction } from 'prosemirror-state';
|
||||||
import { EditorView } from 'prosemirror-view';
|
import { EditorView } from 'prosemirror-view';
|
||||||
import { Schema } from 'prosemirror-model';
|
import { Schema } from 'prosemirror-model';
|
||||||
|
|
||||||
import { MenuBar } from './write-menu';
|
|
||||||
import { keymap } from 'prosemirror-keymap';
|
import { keymap } from 'prosemirror-keymap';
|
||||||
import { toggleMark } from 'prosemirror-commands';
|
import { toggleMark } from 'prosemirror-commands';
|
||||||
import {undo, redo, history} from 'prosemirror-history'
|
import {undo, redo, history} from 'prosemirror-history'
|
||||||
import { baseKeymap } from 'prosemirror-commands';
|
import { baseKeymap } from 'prosemirror-commands';
|
||||||
|
|
||||||
|
|
||||||
|
/* i begin by creating a custom schema. following the guide
|
||||||
|
on prosemirror.net, i created a basic schema with a few
|
||||||
|
types of nodes.
|
||||||
|
|
||||||
|
texts are
|
||||||
|
|
||||||
|
*/
|
||||||
export const customSchema = new Schema({
|
export const customSchema = new Schema({
|
||||||
nodes: {
|
nodes: {
|
||||||
text: {
|
text: {
|
||||||
|
@ -49,7 +54,15 @@ export const customSchema = new Schema({
|
||||||
toDOM() {
|
toDOM() {
|
||||||
return ['p', { class: 'boring' }, 0];
|
return ['p', { class: 'boring' }, 0];
|
||||||
},
|
},
|
||||||
parseDOM: [{ tag: 'p.boring', priority: 60 }],
|
parseDOM: [{ tag: 'p', priority: 60 }],
|
||||||
|
},
|
||||||
|
hr: {
|
||||||
|
group: 'block',
|
||||||
|
selectable: true,
|
||||||
|
parseDOM: [{ tag: 'horizontal_rule' }],
|
||||||
|
toDOM() {
|
||||||
|
return ['hr', { class: 'horizontal-rule'}]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
doc: {
|
doc: {
|
||||||
content: 'block+',
|
content: 'block+',
|
||||||
|
@ -77,9 +90,20 @@ export const customSchema = new Schema({
|
||||||
],
|
],
|
||||||
inclusive: false,
|
inclusive: false,
|
||||||
},
|
},
|
||||||
|
bold: {
|
||||||
|
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// function for error reports
|
// function for error reports
|
||||||
|
|
||||||
function errorReport(message: string): void {
|
function errorReport(message: string): void {
|
||||||
|
@ -87,25 +111,53 @@ export const customSchema = new Schema({
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Commands
|
|
||||||
|
|
||||||
|
/* COMMANDS
|
||||||
|
commands are functions that take an editor state
|
||||||
|
and a dispatch function and returns a boolean
|
||||||
|
to implement editing actions.*/
|
||||||
|
|
||||||
|
|
||||||
|
/**this command inserts a star by your cursor. it sets
|
||||||
|
* type to the star node, and creates one when
|
||||||
|
* dispatch is provided */
|
||||||
function insertStar(state: EditorState, dispatch?: (tr: Transaction) => void): boolean {
|
function insertStar(state: EditorState, dispatch?: (tr: Transaction) => void): boolean {
|
||||||
const type = customSchema.nodes.star;
|
const star = customSchema.nodes.star;
|
||||||
const { $from } = state.selection;
|
const { $from } = state.selection;
|
||||||
|
|
||||||
// If the parent cannot replace with the 'star' node, return false
|
// If the parent cannot replace with the 'star' node, return false
|
||||||
if (!$from.parent.canReplaceWith($from.index(), $from.index(), type)) {
|
if (!$from.parent.canReplaceWith($from.index(), $from.index(), star)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If dispatch is provided, apply the transaction
|
// If dispatch is provided, apply the transaction
|
||||||
if (dispatch) {
|
if (dispatch) {
|
||||||
dispatch(state.tr.replaceSelectionWith(type.create()));
|
dispatch(state.tr.replaceSelectionWith(star.create()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always return true if insertion conditions are met
|
// Always return true if insertion conditions are met
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* function that inserts an <hr> line break
|
||||||
|
*/
|
||||||
|
function insertHR(state: EditorState, dispatch?: (tr: Transaction) => void): boolean {
|
||||||
|
const hr = customSchema.nodes.hr; // Access the HR node from the schema
|
||||||
|
if (!hr) {
|
||||||
|
console.error('HR node is not defined in the schema.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dispatch) {
|
||||||
|
dispatch(state.tr.replaceSelectionWith(hr.create()));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* this command will prompt you for a url, which will
|
/* this command will prompt you for a url, which will
|
||||||
apply link mark to your selection and make your selection a hyperlink */
|
apply link mark to your selection and make your selection a hyperlink */
|
||||||
|
|
||||||
|
@ -122,47 +174,29 @@ function toggleLink(state: EditorState, dispatch?: (tr: Transaction) => void): b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* 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
|
// custom keymap to apply to state
|
||||||
const customKeymap = keymap({
|
const customKeymap = keymap({
|
||||||
'Ctrl-Space': insertStar,
|
'Ctrl-Shift-Space': insertStar,
|
||||||
'Ctrl-b': (state, dispatch) => {
|
'Ctrl-b': (state, dispatch) => {
|
||||||
console.log("Ctrl-b pressed, toggling shouting mark...");
|
console.log("Ctrl-b pressed, toggling shouting mark...");
|
||||||
return toggleMark(customSchema.marks.shouting)(state, dispatch);
|
return toggleMark(customSchema.marks.shouting)(state, dispatch);
|
||||||
},
|
},
|
||||||
'Ctrl-q': (state, dispatch) => {
|
'Ctrl-k': (state, dispatch) => {
|
||||||
console.log("you should have just gotten an alert");
|
console.log("you should have just gotten an alert to place a url into a hyperlink");
|
||||||
return toggleLink(state, dispatch);
|
return toggleLink(state, dispatch);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'Ctrl-Shift-h': insertHR,
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -177,86 +211,60 @@ export class AppWrite extends LitElement {
|
||||||
|
|
||||||
static styles = [
|
static styles = [
|
||||||
styles,
|
styles,
|
||||||
|
|
||||||
css`
|
css`
|
||||||
.ProseMirror {
|
.ProseMirror {
|
||||||
|
background: black;
|
||||||
|
color: white;
|
||||||
|
background-clip: padding-box;
|
||||||
|
padding: 5px 0;
|
||||||
position: relative;
|
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 */
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.ProseMirror {
|
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
white-space: break-spaces;
|
|
||||||
-webkit-font-variant-ligatures: none;
|
-webkit-font-variant-ligatures: none;
|
||||||
font-variant-ligatures: none;
|
font-variant-ligatures: none;
|
||||||
font-feature-settings: "liga" 0; /* the above doesn't seem to work in Edge */
|
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 {
|
.ProseMirror pre {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror li {
|
.ProseMirror a {
|
||||||
position: relative;
|
color: var(--markdown-editor-typography-anchor-color, -webkit-link);
|
||||||
|
text-decoration: var(--markdown-editor-typography-anchor-text-decoration);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror-hideselection *::selection { background: transparent; }
|
.ProseMirror-focused .ProseMirror-gapcursor {
|
||||||
.ProseMirror-hideselection *::-moz-selection { background: transparent; }
|
display: block;
|
||||||
.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 {
|
shouting {
|
||||||
all: unset; /* Remove inherited or conflicting styles */
|
all: unset; /* Remove inherited or conflicting styles */
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: red;
|
color: red; }
|
||||||
}
|
|
||||||
|
|
||||||
|
.boring {
|
||||||
|
background: grey;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -271,6 +279,7 @@ export class AppWrite extends LitElement {
|
||||||
errorReport('Editor container not here');
|
errorReport('Editor container not here');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeEditor() {
|
private initializeEditor() {
|
||||||
|
@ -280,22 +289,29 @@ export class AppWrite extends LitElement {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* this is a schema of initial content to pre-populate the */
|
/* this is a schema of initial content to pre-populate the editor
|
||||||
|
with guidance */
|
||||||
const initialContent = customSchema.nodes.doc.createAndFill([
|
|
||||||
customSchema.nodes.paragraph.create(
|
|
||||||
null,
|
|
||||||
customSchema.text("Welcome to the editor! Start typing here.")
|
|
||||||
)]);
|
|
||||||
|
|
||||||
|
|
||||||
if (!initialContent) {
|
let doc = customSchema.node("doc", null, [
|
||||||
|
customSchema.node("paragraph", null, [customSchema.text("write anything")]),
|
||||||
|
customSchema.node("boring_paragraph", null, [customSchema.text("you can't apply any marks to text in this boring paragraph")])
|
||||||
|
|
||||||
|
|
||||||
|
])
|
||||||
|
|
||||||
|
if (!doc) {
|
||||||
console.error("failed to create initial document")
|
console.error("failed to create initial document")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* here i create a state with doc as the schema,
|
||||||
|
which includes a couple */
|
||||||
|
|
||||||
const state = EditorState.create({
|
const state = EditorState.create({
|
||||||
doc: initialContent,
|
doc: doc,
|
||||||
plugins: [
|
plugins: [
|
||||||
history(),
|
history(),
|
||||||
keymap({
|
keymap({
|
||||||
|
@ -304,11 +320,13 @@ export class AppWrite extends LitElement {
|
||||||
}),
|
}),
|
||||||
customKeymap,
|
customKeymap,
|
||||||
keymap(baseKeymap),
|
keymap(baseKeymap),
|
||||||
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let view = new EditorView(this.editorContainer, {
|
let view = new EditorView(this.editorContainer, {
|
||||||
state,
|
state,
|
||||||
dispatchTransaction(transaction) {
|
dispatchTransaction(transaction) {
|
||||||
|
@ -322,6 +340,7 @@ export class AppWrite extends LitElement {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
console.log(state)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -344,13 +363,13 @@ export class AppWrite extends LitElement {
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<app-header ?enableBack="${true}"></app-header>
|
<main><app-header ?enableBack="${true}"></app-header>
|
||||||
|
|
||||||
|
|
||||||
<main>
|
|
||||||
|
|
||||||
<div class="ProseMirror"></div>
|
<div class="ProseMirror"></div>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,6 +4,7 @@ export const editorStyles = css` {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.ProseMirror {
|
.ProseMirror {
|
||||||
background: white;
|
background: white;
|
||||||
color: black;
|
color: black;
|
||||||
|
@ -52,8 +53,7 @@ shouting {
|
||||||
all: unset; /* Remove inherited or conflicting styles */
|
all: unset; /* Remove inherited or conflicting styles */
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: red;
|
color: red; }
|
||||||
}
|
|
||||||
|
|
||||||
.boring {
|
.boring {
|
||||||
background: grey;
|
background: grey;
|
||||||
|
|
|
@ -45,6 +45,5 @@ export const styles = css`
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}}
|
||||||
|
|
||||||
`
|
`
|
Loading…
Reference in a new issue