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 { 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';
|
||||
|
||||
|
||||
/* 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({
|
||||
nodes: {
|
||||
text: {
|
||||
|
@ -49,7 +54,15 @@ export const customSchema = new Schema({
|
|||
toDOM() {
|
||||
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: {
|
||||
content: 'block+',
|
||||
|
@ -77,9 +90,20 @@ export const customSchema = new Schema({
|
|||
],
|
||||
inclusive: false,
|
||||
},
|
||||
bold: {
|
||||
|
||||
},
|
||||
emphasis: {
|
||||
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// function for error reports
|
||||
|
||||
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 {
|
||||
const type = customSchema.nodes.star;
|
||||
const star = customSchema.nodes.star;
|
||||
const { $from } = state.selection;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// If dispatch is provided, apply the transaction
|
||||
if (dispatch) {
|
||||
dispatch(state.tr.replaceSelectionWith(type.create()));
|
||||
dispatch(state.tr.replaceSelectionWith(star.create()));
|
||||
}
|
||||
|
||||
// Always return true if insertion conditions are met
|
||||
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
|
||||
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
|
||||
const customKeymap = keymap({
|
||||
'Ctrl-Space': insertStar,
|
||||
'Ctrl-Shift-Space': insertStar,
|
||||
'Ctrl-b': (state, dispatch) => {
|
||||
console.log("Ctrl-b pressed, toggling shouting mark...");
|
||||
return toggleMark(customSchema.marks.shouting)(state, dispatch);
|
||||
},
|
||||
'Ctrl-q': (state, dispatch) => {
|
||||
console.log("you should have just gotten an alert");
|
||||
'Ctrl-k': (state, dispatch) => {
|
||||
console.log("you should have just gotten an alert to place a url into a hyperlink");
|
||||
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 = [
|
||||
styles,
|
||||
|
||||
css`
|
||||
.ProseMirror {
|
||||
background: black;
|
||||
color: white;
|
||||
background-clip: padding-box;
|
||||
padding: 5px 0;
|
||||
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;
|
||||
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 */
|
||||
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 a {
|
||||
color: var(--markdown-editor-typography-anchor-color, -webkit-link);
|
||||
text-decoration: var(--markdown-editor-typography-anchor-text-decoration);
|
||||
}
|
||||
|
||||
.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;
|
||||
.ProseMirror-focused .ProseMirror-gapcursor {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
color: red; }
|
||||
|
||||
.boring {
|
||||
background: grey;
|
||||
}
|
||||
`
|
||||
];
|
||||
|
||||
|
@ -271,6 +279,7 @@ export class AppWrite extends LitElement {
|
|||
errorReport('Editor container not here');
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private initializeEditor() {
|
||||
|
@ -280,22 +289,29 @@ export class AppWrite extends LitElement {
|
|||
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! Start typing here.")
|
||||
)]);
|
||||
/* this is a schema of initial content to pre-populate the editor
|
||||
with guidance */
|
||||
|
||||
|
||||
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")
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* here i create a state with doc as the schema,
|
||||
which includes a couple */
|
||||
|
||||
const state = EditorState.create({
|
||||
doc: initialContent,
|
||||
doc: doc,
|
||||
plugins: [
|
||||
history(),
|
||||
keymap({
|
||||
|
@ -304,11 +320,13 @@ export class AppWrite extends LitElement {
|
|||
}),
|
||||
customKeymap,
|
||||
keymap(baseKeymap),
|
||||
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
let view = new EditorView(this.editorContainer, {
|
||||
state,
|
||||
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() {
|
||||
return html`
|
||||
<app-header ?enableBack="${true}"></app-header>
|
||||
<main><app-header ?enableBack="${true}"></app-header>
|
||||
|
||||
|
||||
<main>
|
||||
|
||||
<div class="ProseMirror"></div>
|
||||
|
||||
</main>
|
||||
|
||||
`;
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ export const editorStyles = css` {
|
|||
|
||||
|
||||
|
||||
|
||||
.ProseMirror {
|
||||
background: white;
|
||||
color: black;
|
||||
|
@ -52,8 +53,7 @@ shouting {
|
|||
all: unset; /* Remove inherited or conflicting styles */
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
color: red;
|
||||
}
|
||||
color: red; }
|
||||
|
||||
.boring {
|
||||
background: grey;
|
||||
|
|
|
@ -45,6 +45,5 @@ export const styles = css`
|
|||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
}}
|
||||
`
|
Loading…
Reference in a new issue