Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Annotations classes instead of JS objects #54

Merged
merged 12 commits into from
Jun 25, 2018

Conversation

tim-evans
Copy link
Collaborator

@tim-evans tim-evans commented Jun 14, 2018

This work makes it possible for us to richly extend our annotation model, enabling us to write a rich text editor (see the editor package). Specifically, annotations like paragraph, blockquote, and list require special behavior when newlines are inserted into the document.

To do this in a flexible / extensible way, we need to encode this behavior into classes instead of colocating the logic into the root document.

The highlight of changes is:

1️⃣ The schema is a list of annotations to use to hydrate JSON passed into the document.
2️⃣ Annotations at rest now require vendor prefixes to by hydrated correctly. This is done to ensure that we do not have collisions between annotations with the same name. (cf. a Markdown blockquote to a HTML blockquote)
3️⃣ attributes are required. Although annoying in tests, it removes a lot of defensive code that would otherwise need to be written
4️⃣ Annotations undergo a structured clone when being passed to the query language, which means that altering annotations will no longer have side-effects on the document
5️⃣ The Change object, along with the Insertion and Deletion objects are designed to provide an
initial abstraction layer for us to start organizing document changes in a way that allows for a real-time system to be written on top of AtJSON. Over time, this will become simpler as more common patterns are adapted into AtJSON.
6️⃣ ParseAnnotation is now a concrete annotation, and is a special type of annotation that is always included in the schema list. These annotations are used internally by AtJSON to remove underlying text nodes. (This annotation may be renamed in the future to be more friendly for this use-case).

⚠️ This is an incremental series of pull requests that will culminate in a full deep clean of AtJSON. ⚠️

This simple source provides a look into how we're changing how annotations are created from JSON
This moves the logic for altering annotations into the Annotation class and encodes vendor prefixing into annotations by default
@tim-evans tim-evans requested review from blaine and gmedina June 14, 2018 20:49

export default abstract class BlockAnnotation extends Annotation {
rank() {
return 1000;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@gmedina gmedina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 this looks great!

if (key.indexOf(`-${vendorPrefix}-`) === 0) {
copy[key.slice(`-${vendorPrefix}-`.length)] = unprefixAttribute(vendorPrefix, value, annotation) as AttributeValue | AttributeValue[] | undefined;
}
return copy;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: could this case just call the unprefixAttributes function defined below?


} else {

if (change.end < this.end) {
Copy link
Contributor

@pgoldrbx pgoldrbx Jun 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trivial - could this be combined as else if to reduce conditional nesting?
(just to make things a tad easier to follow)

also maybe I'm reading it wrong but I think you could.

// n.b. the += 0 is just to silence tslint ;-)
} else if (change.start === this.end) {
this.end += 0;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

silencing tslint because it would have complained about an empty block? but do you need this block at all? or is it here just to serve as living documentation? (an else case would also do nothing)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Living documentation here.

this.content = this.content.slice(0, start) + text + this.content.slice(start);
} catch (e) {
// tslint:disable-next-line:no-console
console.error('Failed to insert text', e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: why console instead of throw?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably should throw!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I saw the context for this again. I think we're doing this to guard from non-atjson code from causing apps to blow up. We will revisit this, for sure.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. As a possible follow-up for another PR, maybe own your own logger interface. Then you can control if/when to output and to where.

Copy link
Contributor

@pgoldrbx pgoldrbx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added some very trivial notes

@tim-evans
Copy link
Collaborator Author

@pgoldrbx ❤️ Thanks for the review ❤️

Copy link
Contributor

@blaine blaine left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great!

One question: This has a breaking change on at least deleteText, presumably [add|remove]Annotations() work the same. Is it worth adding backward-compatibility with deleteText and adding a deprecation warning so that we can find the problem spots, or am I over-complicating it? :)

@@ -1,18 +1,142 @@
import { Display } from './schema';
import { Attributes, toJSON, unprefix } from './attributes';
import Change, { AdjacentBoundaryBehaviour, Deletion, Insertion } from './change';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

abstract readonly type: string;
}

export enum AdjacentBoundaryBehaviour {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yasss

import Annotation from './annotation';

export default abstract class InlineAnnotation extends Annotation {
rank() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth making these getters?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! That sounds good!!

@tim-evans
Copy link
Collaborator Author

Not sure if it's worth making it backwards compatible rn, things will be obvious when they break

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants