Implementing alignment

I am trying to implement text-alignment - left/right/center

I’ve done the following:

css:

.ProseMirror .left { text-align: left; }
.ProseMirror .right { text-align: right; }
.ProseMirror .center { text-align: center; }

schema:

align: {
	attrs: { alignment: { default: "left" } },
	content: "block+",
	group: "block",
	defining: true,
	parseDOM: [{ 
		tag: "p",
		getAttrs(dom) {
			return {
				alignment: dom.getAttribute("class")==="center" ? "center" : (dom.getAttribute("class")==="right" ? "right" : "left"),
			}
		}			
	}],
	toDOM(node) { return ["p", { class: node.attrs.alignment }, 0] }
},

code: (adapted from http://pboysen.github.io/)

function alignWrap(state, dispatch, alignment) {
	function wrapTr(tr, $from, $to, nodeType, attrs) {
	    let range = $from.blockRange($to), wrapping = range && findWrapping(range, nodeType, attrs)
	    return wrapping? tr.wrap(range, wrapping) : false
	}
	function liftTr(tr, $from, $to) {
	  let range = $from.blockRange($to), target = range && liftTarget(range)
	  return target == null ? false : tr.lift(range, target)
	}
	function r(p) {
		return state.doc.resolve(p);
	}

	let {from, to, $from, $to} = state.selection, isLeft = alignment == "left"
	let depth =$from.depth, tr
	if (depth > 0 && $from.node(depth-1).type == schema.nodes.align) {
		tr=liftTr(state.tr, r($from.start(depth)), r($from.end(depth)))
		if (!isLeft) {
			state=state.apply(tr)
			tr = wrapTr(state.tr, r($from.start(depth)), r($from.end(depth)), schema.nodes.align, { alignment: alignment})
		}
	} else if (!isLeft) {  //left is default and doesn't need wrapper
		let $end = from == to ? $from: $to
		tr = wrapTr(state.tr, r($from.start(depth)), r($end.end(depth)), schema.nodes.align, { alignment: alignment})
	}
	if (tr) {
		tr=tr.scrollIntoView()
		if (dispatch) dispatch(tr);
	}
	return !!tr;
}

function alignItem(options) {
	let passedOptions = {
		run(state, dispatch) {
			return alignWrap(state, dispatch, options.attrs.alignment)
		},
		select(state) {
			return alignWrap(state, undefined, options.attrs.alignment)
		}
	}
	for (let prop in options) passedOptions[prop] = options[prop]
	return new MenuItem(passedOptions)
}

let alignMenuItems=[];
['left', 'center', 'right'].forEach(alignment => {
	alignMenuItems.push(alignItem({
		title: "Align "+alignment,
		icon: icons[alignment],
		attrs: { alignment: alignment }
	}))
})

All and all it’s mostly working but I still have issues

  • is the use of classes really necessary, I tried to set text-align directly in the schema but was then not sure how to implement parseDOM ?

  • when I create for example a centerd block and then type enter twice I’m back to left … how can I achieve including newlines in the aligned block ?

  • if I change the alignment of one of the centered lines to left, only that lines is now left aligned … how can I get the entire aligned block to change alignment ? I tried to use div instead of p but got the same effect

  • is this in general a good solution ? is there a better way to implement this or any other comments on the code ? (this is a basic editor feature and would be great if there was API support or an official solution example/gist)

thx!

1 Like

I would also be interested in this feature. It seems something that the community wants.

It would be great to hear @marijn’s official input on how to best implement alignment.

I’d make it an attribute on textblock nodes, as opposed to a wrapping node.

@marijn that was a good recommendation thank you! @kofifus did you get yours working? I think mine is working well

@ marijn’s suggestion is right.

I was able to get this working by using custom schema for the paragraph node (and other similar block nodes).

6 Likes

@hedgerwang Any reason for the data-indent instead of using text-indent as a CSS style?