The easy part is getting the correct numbers from the InternalList
and generating the ordered list HTML. The tricky part is connecting
up the events to make sure the renumberings/list generations are
triggered when required.
InternalList can emit an update event on document transaction, which
triggers the renumbering/relisting if any references have been
added or deleted during that transaction.
ve.ce.MWReferenceListNode also listens to changes on the
InternalListNode (i.e. changes to the contents of the references)
and always triggers a rebuild.
Change-Id: I1b48ef5240e433c3b314259aa68cde13841ea98b
Due to the "es5: true" jshint option we enabled, these
warnings were surpressed. I've disabled the option since
we no longer require it. It was enabled in 07c86fc to fix
a bug with jshint. This bug has now been fixed.
Change-Id: I55b7d031eb5581af5f733f050cf2ea98dacb2af6
Also keep items in the order they appear in the document
and grouped by group and key.
Additions and removals are triggered by the new root/unroot events.
Change-Id: Ia3e90ccfdab88f352b89992b90554e5f03ff9952
The only reason it doesn't save correctly right now is because Parsoid
doesn't serialize reference edits correctly.
Change-Id: Ia0f272c07cc28ee829372eb848f23aec99eb92f0
A document slice is a document built from a data slice of an existing
document. It's completely separate from the original document and has
its own store and internalList. The new document's data also contains
the entirety of the original document's internal list. It's possible
to create a document slice of data located inside the internal list,
in which case the resulting document will contain that data twice (one
mutable copy at the top level, and one immutable copy in the internal
list).
ve.dm.Document.js:
* Optionally take an internalList in the constructor. This allows us to
create a document with a clone of an existing internalList rather than
an empty one.
* Add edgeMetadata flag to getFullData()
ve.dm.IndexValueStore.js, ve.dm.InternalList.js:
* Make these classes cloneable
Change-Id: I93e06f764ace16aee9df941b07f8c2bff1a28e2b
Getting & setting the cursor is done with byte offsets
instead of data model offset (characters) so we need to
be able to convert between the two as well as splitting
characters.
TODO: The regex only works on surrogate pairs, not
yet combining accents.
fixupInsertion will combine a combining mark with the
character to its left it it can.
Bug: 48630
Change-Id: I8d936fb15d82f73cd45fac142c540a7950850d55
Right now the internal list is very much HTML-based, so it's not
populated at all when creating a document from data. So hack this in by
specifying what should be in the internal list as a property of the
example document's data array.
Change-Id: I51c7b7b4dcd9fd3333777c1287b7ba544887aa32
* VisualEditor.hooks.php
** export URL to magnify clip as a configuration variable so thumbnail image can render correctly
* ve.ce.Surface.css
** add CSS styling to make image captions look the same way in edit mode as they look in the view mode
* ve.ce.ParagraphNode.js
** add CSS class ve-ce-generated-wrapper if it is a generated wrapper
* ve.ce.MWImageCaptionNode.js
** make image caption rendering match the view mode
Change-Id: I0cd1b25e8f8355e0500aabc90e7c4cdf591545f3
Previous check wouldn't make sense, cause the last offset in the data
could be that one that makes data balanced (and j is increased always
after iteration).
Change-Id: Ie9498d0ac9e3417d09b8b3043bf3281e7dfbf9db
Rather than using namespaced linmod attributes, store the preserved
HTML attributes in the .htmlAttributes property of the linear model
element, in a nested structure to allow for easier treatment of child
nodes. Also added attribute order preservation by storing attributes
as an object plus an array of keys.
ve.ce.Node.js:
* Remove html/* attribute synchronization. Doesn't make sense any more
because these things aren't in the attributes object any more. I don't
think it ever made sense because these attributes were never supposed
to be changed anyway.
ve.ce.View.js:
* Replace renderAttributes() with a simple wrapper around
renderHtmlAttributeList()
ve.dm.Converter.js:
* Add buildHtmlAttributeList() and renderHtmlAttributes() for building
and rendering HTML attribute lists
ve.dm.Model.js:
* Add getter for .htmlAttributes
ve.dm.Node.js:
* Drop .htmlAttributes on clone, and remove logic dropping html/*
ve.ui.MWCategoryWidget.js:
* Remove html/0/about hack, was already unnecessary and now doesn't
work any more
tests/:
* UPDATE ALL THE TESTS
Change-Id: I620573afd70d36ade6b80413075b6e1f4a435abe
Escapes }} and | by wrapping them in <nowiki> tags, and steps around
<nowiki> tags so as to not double-wrap things.
Change-Id: Ia50906524c1fcf55c9f390a114856bc7f20b2d3a
In stopWrapping we assign any left over whitespace to the paragraph
in position 3, however we weren't clearing this whitespace buffer
if an inline content node followed it.
Change-Id: I8b3ee3915044abd6bafda386430bf7f992ca4aa8
The way it operated was evil. It did a depth-first search from the root,
finding the node using reference equality. For documents with deep
structures, this could take a long time. Inez did some profiling and
found it was called tens of millions of times on a complex document.
Kill getOffsetFromNode() and move its functionality to getOffset().
The logic has been completely rewritten: getOffset() now traverses
up from the node rather than down from the root, and pretty much does
the reverse of what getNodeFromOffset() does. This should be much more
efficient even without offset caching in the node objects (which we may
still implement later).
Change-Id: I125f9fa423c40db6472e2c4a7c94214218ba3bc7
For nodes that handle their own children (as well as leaf nodes and
meta items), store the first child's attributes in html/0-0/*, the
second child's attributes in html/0-1/*, the second element's third
child's fourth child's attributes in html/1-2-3/* , etc.
This obsoletes the ad-hoc code that basically did the same thing in
MWInlineImageNode.
Change-Id: If5abd2d5d9c361b359617ff4b0f3d6ba4c9b0142
Store the HTML as a domElements array like everywhere else, rather than
as a string. Also disable HTML attribute preservation because there's
no point doing that when we're already preserving all of the HTML.
Also fixed a misnamed attribute (<li li="foo"> --> <li id="foo">) in
the test case.
Change-Id: I36bf8bade8118e07a75eb6f3a2427a00ef4915d7
It now allows you to specify which attributes to preserve in various
ways rather than just setting true or false.
Removed unused factory methods that exposed the old value.
Change-Id: I914164adcf1f0e48fa3fa85277e68c72dbad393e
For extension-specific types such as mw: , we require that all
types be matched. But we want MWTemplateNode to match
anything with an mw:Object/Template type, even if it also has
other types (like mw:WikiLink/Category in our test case).
Hack this into MWTemplate by matching on /^mw:/ then using
a matchFunction to assert that mw:Object/Template is in the
typeof attribute.
Update the test case. Because it's now a template, there's a bunch
of store stuff involved. Remove the other test case for about
group forcing because it's now a duplicate of this one.
Change-Id: Iacbe952a66d610c19b46bd76b84c50488857ac29
When converting an element that starts an about group with at
least one other element in it, we now only consider models that
support about grouping. This prevents the first node from being
converted to something else and leaving the others hanging.
In practical terms, this means that elements like
<link rel="mw:WikiLink/Category" typeof="mw:Object/Template">
get alienated and pull in the rest of their about group, rather than
being converted to a category or alienMeta or whatever and
leaving the other elements to be converted normally.
Added a test case that asserts this. Really the result should be an
MWtemplate rather than an alien, but that's a separate issue.
Also removed superfluous mustMatchAll checks; we've already
filtered the array by the time we get there.
Change-Id: I522ba4c56d5bc52c7e9aab1e2535385540c1315d
This was broken for both normal elements, where the meta item is
inside the element, and wrapper paragraphs, where the meta item
gets moved outside the wrapper.
Bug: 47712
Change-Id: I42daaf142e548e5b221ff0a52df0ad24ec6a4fd0
Specifically by looking for "data-ve-changed",
"ChangeMarker*" and internal.changed.
Various tests, test counters and unused variables also
affected.
Bug: 45061
Change-Id: Ibd1ee68e0d650979d40574eff9cebded1a28499f
Remove all manual changes to SF ranges as these are not
undoable. Instead change translate range to default to
outer expand and build functionality around that behaviour
never changing.
As translate range is always outer I don't think we need to
check for start and end crossing over?
Added more undo tests to assert these selections are maintained
properly, and added the test case to 'update' for when and undo
point is overwritten.
Insert content now results in a selection over the inserted
content. Most usages were expecting this anyway and were
followed up with an adjustRange(-length,0) which is no longer
necessary.
Noticed that the link inspector case was never being triggered
as word boundary was always expanding to at least one char (mainly
for Hanzi selection). This doesn't make much sense as single
spaces get auto selected so removed this functionality.
Split collapseRange out into collapseRangeToStart and
collapseRangeToEnd as this may be required to get the old
behaviour (range moves to end after insert).
Change-Id: I3dc0b4d00d37bad1ca3076a69b41c5f0b3fa0570
As jQuery hash problems in some cases converting HTML, it is
easier just to store the original DOM elements.
The bulk of this commit is fixing the tests as although we have an
assertion for comparing DOM elements, we don't have one for comparing
objects or arrays which may contain DOM elements.
ve.js
* copyObject & copyArray fixed to run cloneNode(true) on any item
of type Node.
* Added callback function so an object/array can be copied with
modifications.
ve.qunit.js
* Added deepEqualWithDomElements: Using the new copyObect/Array
callback, we can copy the incoming object and convert any nodes
to node summaries, then just run the normal deep equal comparison.
ve.dm.AlienNodes.js
* Instead of storing HMTL we store cloned DOM elements which we can
send straight back to the converter without any processing.
ve.dm.example.js
* Updated tests to expect DOM elements instead of HTML.
ve.dm.Converter.test.js
* Updated tests to use deepEqualWithDomElements
Bug: 47737
Change-Id: I3df8f49b170c31da9610129d53cf8cb65dd5d5f8
In this case, selectNodes() returns the paragraph, which is not a content
node, and so newFromContentBranchConversion() decides to do nothing at all.
This change makes newFromContentBranchConversion() more intelligent about
finding the content branch to operate on, fixing cases such as these
where selectNodes() returns the content branch itself rather than one of
its children.
Bug: 41203
Change-Id: I710fbf184ef5ef84d9c2f5bca2b115e0660f5b8f
fixUpInsertion now returns an object with both data and offset
which allows offset changes.
Within fixUpInsertion we lazy-generate first/lastChildStack which
is a list of parent nodes for which the current node is the first/last
child. Whenever we try to close off a node we check these stacks and
if they are populated we instead use a recursive call to start
fixUpInsertion again but with the offset shifted by 1.
Bug: 46799
Change-Id: Ic51dd03725c11f1f7e279929534ee3afea14d662
Because we have a node for <table>, we also need one for <caption>,
otherwise we'll try to alienate it and fail.
Added the test case as a separate example document so Ed can use it
for his tests.
Removed test case asserting <caption> is alienated.
Change-Id: I3a917db58e6c0eb97899b214b07d01fc8d86b56d
In most places we call .contains we already know the index, so we
can avoid store lookups by using .containsIndex.
Change-Id: I45a9a421473f9bec479ab8ccceceb162b7004c3a
The fixUpStack is actually redundant code and closingStack
and openingStack handle all our cases. It was causing the
insertion to try to correct balance itself in the middle
of inserting two paragraphs, causing the creation on an
empty paragraph between them.
Added a test case for the fix and other cases to make
sure removing fixUpStack hasn't caused problems.
Bug: 46800
Change-Id: I35e54165709ac56e8116359a7c3b487eecf08ff7
To help the selective serialiser we can return the original
HTML for generated content if it is unmodified.
As the output of toDomElements now depends on changes
to the dataElement we now have a 'modify' function in
the some test cases.
We also now have 'storeItems' to assert that the index-value
store is correctly populated and for loading values back
into the store for toDomElements tests.
Also make 'mw' an attribute and remove 'about' property.
Bug: 47394
Change-Id: I2bbb5d2d6a90c4eb87fa129671112c92a9b931e7
Also add detection for whether the browser is actually broken (most are,
but some, like Opera, aren't), treat <textarea> and <listing> in addition
to <pre>, and fix a bug where the function would crash if the <pre> was
empty (because .firstChild was undefined/null).
Change-Id: I541b57e9fd5c9c42d19d0a59f6e29fb43d35c9b6
addSet:
* Instead of indexing items in the store, just union the indexStore arrays
removeSet/removeNotInSet:
* difference or intersect the indexStore arrays
filter:
* push indices into the result set instead of values
simpleArrayUnion/Intersect/Difference have been created as utilities
in ve. They are prefixed 'simple' because they use object keys to
do fast in-array comparisons. This means they are limited to string
values or values which will compare as strings (e.g. numbers).
Change-Id: I079cbdfece4f6d80ec0afd61959913f13217fcb3
By removing the transaction listeners from surface fragments we
no longer have to make sure they are always manually destroyed.
In order to retain the functionality of having fragments update
with transactions elsewhere we keep a pointer to a place in the
new complete history stack in the surface. The complete history
stack records all transactions, even undone ones.
Whenever getRange is called we replay all transactions in the
complete history (in the correct order) since the fragment was
last updated.
Also in this commit:
* Updated Format/IndentationAction to test undo(). This increases
coverage of surface fragment behaviour.
* .range is always accessed by .getRange now, although as an
optimisation we can use the noCopy mode when we a sure the
returned range will not be modified.
* Added undo test to .update (previously .onTransact)
Bug: 47343
Change-Id: I9e9818da1baa8319a3002f6d74fd1aad6732a8f5
This was broken, especially in wrappers.
Changed the wrapping algorithm so that meta items are placed outside
wrappers if possible. On the left-hand side, this is already the case:
we don't open wrappers for meta items. On the right-hand side, this is
accomplished by buffering the meta items and only inserting them when
we encounter either real text (not whitespace) or the end of the wrapper.
If we're interrupted by real text, we insert the meta items with the
unmodified whitespace. If we're interrupted by the end of the wrapper,
we insert the meta items outside of the wrapper with whitespace stripped.
Internally, this is done by stripping the whitespace into the whitespace[0]
of the meta item to its right. Then when we output the meta items, we
either decide to 'restore' the whitespace, or to 'fixup' by also setting
whitespace[3] on the element before the whitespace.
Change-Id: Ibeea2a9906c4aae9fe6d284613edd6ec853ca5e7
So. It turns out that the design of SurfaceFragment is a little -
shall we say - wonky.
One of the best things about ve.dm.SurfaceFragment is its magical
ability to retain the intention of its range, even as transactions
are being processed. This ability is granted by each fragment
listening to the surface's change event, and responding by using
translateRange for each transaction that gets processed. Surface
fragments also have these clever methods that allow you to get a
fragment based on another, which makes adjusting the range easy to do
inline without having to manually store multiple fragments or
modifying the original.
This sounded good, and we seemed to all be convinced it was well
designed. But if you add a console.log( 'hello' ); to the first line
of ve.dm.SurfaceFragment.prototype.onTransact, and then start using
the bold tool on various selections of text, you will find that there
may indeed be a flaw. What you will probably realize is that the
number of times that particular line of code is being called is
disturbingly large, and increases each time you do just about anything
in the editor. What's going on? How did we get here? Read on…
It turns out that fragments are immortal. We create them, they listen
to the surface's transact event, we are done with them, but the
surface keeps on emitting events to the now long forgotten about
fragments. They continue to build up over time, never go out of scope,
and bloat the hell out of our program.
The same ended up being true of toolbars - and each time the context
menu fired up a new one the old one was left in limbo, still
responding to events, still taking up memory, but not being visible to
the user.
All of this immortality was causing strange and difficult to track
down problems. This patch fixes this by introducing a destroy method.
This method unbinds events, allowing the object to finally fall out of
scope and die - and more importantly stop receiving notifications of
changes.
This is a hack, but Ed will no doubt get this situation sorted out
properly by making fragments lazy-evaluate their selections by only
storing an identifier of the most recent transaction they were based
on, see bug 47343.
Change-Id: I18bb986001a44732a7871b9d79dc3015eedfb168
Before, it took an array of objects and translated those to indexes
using the store. Literally every caller outside of the test suite got
an array of indexes from the linear model, translated those to objects,
then passed them into the AnnotationSet constructor which translated
them right back to indexes.
The previous behavior was kind of ridiculous on its face, but the
reason we found it is because Inez was investigating the performance
degradation when bolding a line and found that half of it was due
to the hundreds of ve.getHash() calls caused by this behavior.
Change-Id: I38df8ae9f6392849dacf477ea2f804283c964417
Subbu said that cloning of attributes like data-parsoid or typeof would
cause problems for Parsoid.
Also remove the attributes object if it becomes empty, and do the same
for the internal object.
Bug: 47297
Change-Id: I428becf95c70d0ed8af5b0c408e3966dc47fd8c3
Context-sensitive nodes are HTML elements like <caption> that can only
appear in certain contexts (<table> in this case). This means that
serializing them by throwing them in a <div> and calling .innerHTML
fails, because the browser knows a <caption> can't be in a <div> and
unwraps it. jQuery's .html() function is clever though and knows to wrap
<caption> in a <table> (and has similar rules for other elements).
So use jQuery's .html() rather than manual .innerHTML stuff.
Change-Id: Id7d3eff968b3a2ba345680772f7cc32e3dcdb529
In HTML like <td>Foo <b>Bar</b></td>, the space would be stripped and
registered as trailing whitespace in the <td>, so it wouldn't be visible
in the editor and would be inserted after the </b> on the way out.
Thanks to Subbu for reporting this. This case was kind of ridiculous and
we're lucky the JRuby article contained it. To trigger the bug, you had
to have:
* a table cell
* containing unwrapped content
* consisting of
** some text
** whitespace
** open annotation (bold in my example, link in Subbu's case)
** text
** close annotation
** and nothing else
Change-Id: I2b83f02764b311a32a50956d4c8930a9394e91a4
The first run of getDomFromData() would preserve whitespace just fine,
but it blanked out the .veInternal.whitespace[1] element in certain
cases, contaminating the linear model and making the whitespace data
inconsistent. Subsequent runs of getDomFromData() would then refuse to
serialize that whitespace because the information about it was
inconsistent.
In getDomFromData(), we sometimes unset .veInternal.whitespace[1] (i.e.
set it to undefined) to prevent double processing. Because we're
potentially going to modify .veInternal, don't assign it by reference,
but copy the object.
Added tests asserting that the linear model is unchanged after calling
getDomFromData(), because that function should never modify
linear model data. This test failed in 4 cases (all whitespace-related)
before I added the copyObject() call.
Bug: 43543
Change-Id: Ic4c93510518163894201a693ab50331413715967
ve.dm.Transaction
* Replace operations are now built directly from the
linear model and automatically determine what metadata
replace information they need to include:
** retainMetadata
** replaceMetadata
** insertMetadata
ve.dm.Document
* Metadata array created empty and padded out after data parsing
as we are no longer using Document.spliceData to build it (a new
test checks for correct metadata length)
* spliceData replaced with getMetadataReplace, which instead returns
transactional steps of spliceData (retain, replace, insert)
ve.dm.MetaLinearData
* Add function for merging metadata items together. Only used
once in the code (Document.getMetadataReplace) but useful
for generating test data.
ve.dm.MetaList
* Replace operations with metadata need to calculate new offset
and indices directly, but can't be applied immediately lest they
put a metaItem out of place and affect findItem.
ve.dm.MetaItem
* Add methods to support queued moves as required by MetaList
Test files
* Updated to match new pushReplace API
* Remove any instances of Document.spliceData
* Extra check on sparse metadata array length
* Rewrite spliceData tests as getMetadataReplace tests
* Count expected cases in Transaction(Processor) tests
Bug: 46954
Change-Id: I4edad1c2dd37c723bff2792bab7d694ef17a86dc
* Create MWTemplateBlockNode & MWTemplateInlineNode (in ce and dm)
* Move Alien's 'isInline' code to ve.dm.Node.static.isHybridInline
* Move definition of ce.AlienBlock/InlineNode inside ce.Aline.js file
to match dm.AlienBlock/InlineNode and MWTemplate
* Duplicate AlienBlock/Inline styles for templates
* Create test case for inline templates
* Count test cases in ve.dm.Converter.test.js automatically
Change-Id: Id9bc7f049ea974dd5e7f8b7a66080939e0948bbd
This node stores the rendered in the index-value store, hashed on
a custom hash of the dm (type + mw) which makes it unique it its
parameters.
Bug: 46571
Change-Id: I0ab4c9f7bca207121d5b42e83c821771b6139da8
ve.ce.ImageNode.js
* Moved in generic stuff from MWImageNode
* Added drag end handler (empty, will be used soon)
ve.ce.MWImageNode.js
* Changed to inherit ImageNode
* Moved generic stuff out
ve.dm.ImageNode.js
* Added attribute extraction/preservation for src, width and height
ve.dm.MWImageNode.js
* Changed to inherit ImageNode
* Re-using ImageNode's attribute handling to extract/preserve attributes on both the image and wrapper level
Change-Id: Ied4e1ece24e6804220eac35330790f7084df55de
It's been passed in for a while, but nothing ever used it. As we know
some browsers don't like it when we create elements in the wrong
document, and this ensures we always use the correct document for
createElement().
Change-Id: Ia3d2fabe0516956105ad2b5625ed2f76c015c26e
They worked fine with just one group, but once Rob added a second group
everything came falling down.
* Index the return value of findItem() into the correct array
(list.items for all, list.groups[groupname] for groups)
* Expect null if there is something at the coordinates but it's in the
wrong group
* Keep track of the next index and expect that for forInsertion when
findItem() returned null, as well as at the end of each offset
Change-Id: I76438f583cea5694ce04fa2f4e7e88f8f8f236d1
This changes the annotation API to be the same as the node API, sans
a few boolean flags that don't apply. The APIs were different, but
there was really no good reason why, so this makes things simpler for
API users. It also means we'll be able to factor a bunch of things out
because they're now duplicated between nodes, meta items and annotations.
Linear model annotations are now objects with 'type' and 'attributes'
properties (rather than 'name' and 'data'), for consistency with elements.
They now also contain html/0/* attributes for HTML attribute preservation,
which obsoletes the htmlTagName and htmlAttributes properties.
dm.Annotation subclasses take a reference to such an object and implement
conversion using .static.toDataElement and .static.toDomElements just
like nodes do. The custom .getHash() functions are no longer necessary
because of the way HTML attribute preservation was reimplemented.
CE rendering has been moved out of dm.Annotation (it never made sense to
have CE rendering functions in DM classes, this was bothering me) and into
separate ce.Annotation subclasses. These are very similar to CE nodes in
that they have a this.$ generated based on something in the DM; the main
difference is that nodes listen to events and update themselves, whereas
annotations are static and are simply destroyed and rebuilt when they
change. This change also adds whitelisted HTML attribute rendering for
annotations, as well as class="ve-ce-FooAnnotation" attributes.
Now that annotation classes produce real DOM nodes rather than weird
objects describing HTML tags, we can't generate HTML as a string in
ce.ContentBranchNode anymore. getRenderedContents() has been rewritten
to be much more similar to the way the converter renders annotations;
in fact, significant parts of it were copied from the converter, so that
should be factored out in the future. This change actually fixes an
annotation rendering discrepancy between ce.ContentBranchNode and
dm.Converter; see the diff of ve.ce.ContentBranchNode.test.js.
ve.ce.MWEntityNode.js:
* Remove stray property
ve.dm.MWExternalLinkAnnotation.js:
* Store 'rel' attribute
ve.dm.TextStyleAnnotation.js:
* Put all the conversion logic in the abstract base class
ve.dm.Converter.js:
* Also feed annotations through getDomElementsFromDataElement() and
createDataElement()
ve.dm.Node.js:
* Fix undocumented property
ve.ce.ContentBranchNode.test.js:
* Add descriptive messages for each test case
* Compare DOM trees, not HTML strings
* Compare without all the class="ve-ce-WhateverAnnotation" clutter
ve.ui.LinkInspector.js:
* Replace direct .getHash() calls (evil!) with ve.getHash()
Bug: 46464
Bug: 44808
Change-Id: I31991488579b8cce6d98ed8b29b486ba5ec38cdc
* In matchTypeRegExps(), skip string types
** Didn't break because we currently have mixes of strings and regexes
* Combine types from rel, typeof and property rather than picking one
** Added test case in ve.dm.example that resembles actual Parsoid output
* If the element has extension-specific types, not only restrict type
matching to extension-specific types, but also require that *all* types
present on the element be matched
Change-Id: Iacf3851a0ca9081d2c813b42435484a47cec6230
Created an IndexValueStore class which can store any object and return
an integer index to its hash map.
Linear data is now stored in ve.dm.LinearData instances. Two subclasses
for element and meta data contain methods specific to those data types
(ElementLinearData and MetaLinearData).
The static methods in ve.dm.Document that inspected data at a given
offset are now instance methods of ve.dm.ElementLinearData.
AnnotationSets (which are no longer OrderedHashSets) have been moved
to /dm and also have to be instantiated with a pointer the store.
Bug: 46320
Change-Id: I249a5d48726093d1cb3e36351893f4bff85f52e2
Using element.height was returning 0 if the attribute was empty
when in fact what we mean to store is null (i.e. auto height).
This takes care of the writing of attributes in CE as jQuery
ignores an attribute-set command if the value is null.
Also in this commit I've implemented a basic toDomElements
that outputs the original HTML (code copied from AlienNode).
This stops the code from throwing an exception but will
eventually need to be rewritten to rebuild the HTML from
the attributes stored in the DM.
Bug: 56336
Change-Id: I297a1d0a07e9ebf9d0110fb1cdf266f8415f25b7
Initially just with a Wordbreak module to implement Unicode standard
on 'Default Word Boundaries'. Due to it's standaloneability this has
been written as a separate library. Non-BMP characters are currently
not supported.
Bug: 44085
Change-Id: Ieafa070076f4c36855684f6bc179667e28af2c25
Add .insertMeta() and .removeMeta() methods to insert and remove
metadata through the meta list. For convenience, there is also a
.remove() method in MetaItem that wraps around removeMeta().
Also rename insertItem() and removeItem() to addInsertedItem() and
deleteRemovedItem() to avoid confusion, and make the MetaList
constructor take a dm.Surface rather than a document so we can call
change().
Change-Id: I4b62a3109404cfd56f5de68938e1db908b03e678
Previously these were static functions in TransactionProcessor
which instantiated a TP called .process() on it. These are now
methods of ve.dm.Document.
Also moved the emission of the 'transact' event on the document from
TransactionProcessor to Document itself, and moved the tests asserting
double application is protected against from TP to Document (because
the corresponding code moved as well).
Change-Id: I7c9f22a14accaf0ba1f70d5aa4f0573bb7e677d0
This is just a prototype - if it will be as useful as I expect
then it will get refactored (and probably get a better name +
location).
Change-Id: Ice1a2bd7d498d9d8438c35239216f01bd3db1826
A MetaList is a collection of MetaItems representing all of the
metadata in a ve.dm.Document, and it updates itself live as the
underlying document changes.
Currently this interface is read-only, I'll add mutators next.
Change-Id: If7bfc9563af37e22dcdca9a682d6decc2f6f1872
Rather than meta-things being special kinds of nodes, they are now a
separate class of things (MetaItems) along with Nodes and Annotations.
* Created a generic ve.dm.MetaItem that meta items inherit from.
There will be actual instances of this class as well in the upcoming
meta group code.
* Renamed MetaNode to AlienMetaItem, MWMetaNode to MWMetaItem,
'metaBlock'/'metaInline' to 'alienMeta'
* Created a MetaItemFactory, handle meta items in the ModelRegistry
* Kill ve.dm.Node.static.isMeta, now obsolete
ve.dm.Converter:
* Pass in the MetaItemFactory
* Look up data element types in the ModelRegistry rather than the
NodeFactory, because they can be either nodes or meta items
* Document createDataElement() and make explicit that modelClass can be
either a node or a meta item
* Handle meta items in getDataFromDom()
* In getDomFromData(), check the MetaItemFactory as well as the NodeFactory
Change-Id: I893709c6f3aa00f85c1b905b70f9f4e597bdeada
Replacing [a-zA-Z] with a long unicode expression which encompasses
all the characters in the unicode 'letters' category. Similarly replacing
[0-9] with an expression for 'numbers'.
Bug: 44085
Change-Id: Idd403339caa24769ce08133dda06ab6d4b9d694e
Also in this commit is a minor fix to the regular expression so it
behaves as documented (the hyphen needed escaping).
Bug: 44085
Change-Id: Idc315e2dce79be8f028b5681c60f74e175b9d869
isolateAndUnwrap now unwraps to a level determined by a target type
i.e. the type you are going to convert to.
Also in this commit wrap/unwrap/rewrap have been refactored to use
getLengthDifference. unwrap now takes an inner/outer unwrap depth.
Change-Id: I3c6249de43232a9ef64f498a0aaf66b1c44973f2
Heading and Preformatted nodes have rules that should only
exist under a document node in MediaWiki.
Two new node types have been created as has a new DropdownTool which
uses these. The MW init options have been changed to use the new
DropdownTool.
Bug: 45295
Change-Id: I3f47e1ae1f5c1415bde58a75385e4bf5f4b8fffc
Add a static.name property to ce nodes and make sure both ce
and dm nodes always use the static.name property in constructors
and registration calls.
The result of this is that any given node type should now only
appear once in the code as a string.
Bug: 45701
Change-Id: Ibf31de16ab28ad58209c1443cd74f93dda278998
rewrapAllNodes is effectively the same as unwrap then wrap
except it operates as one transaction as so avoids a potentially
invalid intermediate state.
Added some more comments for unwrapAllNodes and renamed a varaible
for consistency.
Fixed a typo in Transaction.newFromWrap comment.
Bug: 45242
Change-Id: Ie752a788d087055d97c7c6f75f59c6a2680d26c7
Previously, we would translate to the right of an insertion, but for
wrapping transactions that means we end up with something like
<ul><li>|<p>...</p></li></ul>|, which doesn't make any sense. This
change changes this to <ul><li>|<p>...</p>|</li></ul>.
* Add parameter to translateOffset() that toggles the behavior for
the offset before an insertion
* In translateRange(), translate start and end differently
** In some cases this can map them across each other, fix that
Change-Id: Ia2197b08d9f6763be3f2db5a59546ddc3f74a281
We already combine consecutive operations of other types, but didn't do
that for remove. We have to do this, though, because translateOffset()
gets confused otherwise.
Change-Id: I4285a3efd9f2297398f57e2f24adb26adafdf465
This changes the node API to work with multiple elements, so we can
support about groups. Instead of passing in and returning single DOM
elements, we use arrays of DOM elements.
ve.dm.Converter:
* Pass modelRegistry into the constructor
* Remove onNodeRegister handler and its data
* Remove getDataElementFromDomElement() and
getDataAnnotationFromDomElement(). Most logic moved into
getDataFromDom(), some into createDataElement()
* Remove createAlien(), replaced with
createDataElement( ve.dm.AlienNode, ... )
* Replace doAboutGrouping() (which wrapped about groups) with
getAboutGroup() (which returns an array of the nodes in the group)
* Put in a hack so <meta>/<link> elements with an mw: property aren't
alienated
* Remove about group wrapping behavior in favor of just outputting
multiple nodes
ve.dm.AlienNode.js:
* For multi-element aliens, only choose inline if all elements are
inline, not just the first one
ve.dm.example.js:
* Add html/0 stuff for meta nodes
* Fix test case to reflect new alien behavior
Change-Id: I40dcc27430f778bc00a44b91b7d824bfb2718be6
wrapAllNodes was calulating the new selection incorrectly. This has
been fixed and a test added.
unwrapAllNodes takes a depth as its argument and unwraps that many
elements from inside the selection.
Tests for wrap/unwrap apply also now check that applying a wrap
and then its inverse as an unwrap result in the document reverting
to its original state.
Change-Id: I7dcacdfb5894be59ffad69b369d7b32933a25b61
In our test case, offset 12 was mapped to 16, but that should be 11.
The problem here was that the offset right before the removal was
mapped to right after the removal, but that's only valid if we're
dealing with a removal, not when we're dealing with a replacement
where we're both removing and inserting data.
Change-Id: Ibf3c1463c0de009578cd50736f19bae82669ced8
This method will take a selection of siblings and ensure they
are the only chlidren in their first parent which can be placed anywhere
(for example the first parent of a tableCell which can be placed anywhere
is a table, and for a listItem is a list).
The method ensures no redundant empty tags are created, so if
the selection encompasses all siblings then no action is taken.
Also in this commit are two test cases run against ve.dm.example.isolationData.
Change-Id: I783bd5ecd9d43d61f9b2685985409b4d746cbe94
The Parsoid output will also be expected to be a full HTML document. For
backwards compatibility, we allow for the Parsoid output to be a
document fragment as well. We don't send a full document back yet, also
for b/c -- we'll change this later once Parsoid has been updated in
production.
ve.dm.Converter.js:
* Make getDataFromDom() accept a document rather than a node
** Split off the recursion (which does use nodes) into its own function
** For now we just convert the <body>. In the future, we'll want to do
things with the <head> as well
* Pass the document around so we can use it when creating elements
* Make getDomFromData() return a document rather than a <div>
ve.init.mw.Target.js:
* Store a document (this.doc) rather than a DOM node (this.dom)
* Pass around documents rather than DOM nodes
* Detect whether the Parsoid output is an HTML document or a fragment
using a hacky regex
* When submitting to Parsoid, submit the innerHTML of the <body>
ve.init.mw.ViewPageTarget.js:
* s/dom/doc/
* Store body.innerHTML in this.originalHtml
ve.Surface.js:
* s/dom/doc/
demos/ve/index.php:
* Don't wrap HTML in <div>
* Pass HTML document rather than DOM node to ve.Surface
ve.dm.Converter.test.js:
* Construct a document from the test HTML, rather than a <div>
ve.dm.example.js:
* Wrap the HTML in the converter test cases in <body> tags to prevent
misinterpretation (HTML fragments starting with comments, <meta>,
<link> and whitespace are problematic)
Change-Id: I82fdad0a099febc5e658486cbf8becfcdbc85a2d
Have created builders for insertion, removal, and single element replacement.
In adding Document.getMetadata which is nearly identical to Document.getData,
the two functions have been refactored to use a common static method
getDataSlice, with this.data/this.metadata as an argument.
Document.spliceMetadata has been added. It is essentially spliceData with
the data/metadata synchronisation issue.
Metadata cursor position is now tracked in the TransactionProcessor. Cursor
advancement has been moved to a function so the metadata cursor can be reset
every time the data cursor is moved.
There were unhit bugs in the TransactionProcessor run test section, where
the data being loaded into the test documents wasn't always being deep-copied,
and the assert was looking at getData instead of getFulldata (which wouldn't
be able to test metadata changes).
Change-Id: Ieb20ab3e7827bc7ff04148f147da6728eb1eb666
This means that <p data-foo="bar"> will now be converted to a paragraph
with attributes {"html/0/data-foo":"bar"} rather than {"html/foo":"bar"}
This paves the way for multi-element node (about group) handling in the
node API: nodes representing multiple DOM elements will have html/i/attr
to represent an attribute of the i'th DOM element.
Change-Id: Iea52bdccd721942ca708c8f9f47e934524809845
When encountering an inline node (i.e. content node that's not a text
node) within a branch node that's not a content branch node, the
converter should start a wrapper. But it doesn't do this, it only opens
wrappers for text nodes and annotations.
Fixed this in the converter, added a test for it, and fixed an existing
test that asserted the broken behavior.
Change-Id: I6e143e21e68b68f0d85b8772e24a2d3a5d465410
* Introduce context object as specified for
ve.dm.Node.static.toDataElement()
* Remove wrapping variable in favor of context.wrapping
* Remove wrappingIsOurs in favor of context.canCloseWrapper
* Introduce originallyExpectingContent and use it to repopulate
context.expectingContent after closing a wrapper
* Replace most uses of branchHasContent with context.expectingContent
** Except for two cases where we need originallyExpectingContent
These changes fix a case where a metaBlock was generated in an inline
position. Updated the tests to reflect this.
Change-Id: I6baf6053f8a3a0b7d91487f812b9235a7b2b3db1
Continues where Ibb682332a6084e357104183641a104e3ae1e253f left off, adding tests and removing inconsistencies between the behavior of the document constructor, which was adding empty text nodes to empty paragraphs, and correcting other tests which expected empty text nodes to be there as well.
Change-Id: I414d061cdd494b8023f14e944eda2910a4dab0d4
Extension-specific types are RDFa types (or type regexes) that are
registered with the ModelRegistry separately. If an element has a type
that is extension-specific, then that element can only be matched by a
rule that asserts one of its extension-specific types.
For MediaWiki, we would call
ve.dm.modelRegistry.registerExtensionSpecificType(/^mw:/ ) .
So then an element like <span typeof="mw:foobar"> would either match a
rule specifically for mw:foobar, if one exists, or no rule at all; even
the rule for <span> would not match. The consequence of this is that
elements with unrecognized mw:-prefixed RDFa types are alienated.
Change-Id: Ia8ab1fe5dffb9f813689324372a168e8e4a3e0bc
ModelRegistry registers both annotations and nodes, and performs
matching on both at the same time. It also registers annotations with
the AnnotationFactory, and nodes with the NodeFactory.
Change-Id: I5e68e506a0e573cc0afe6304ccea058ffc20d1c8
Add static properties for matching, data<->DOM conversion, and name. Use
matchTagNames, toDataElement and toDOMElement. name isn't used yet.
Change-Id: I5e7df3303bbd65e6968e931b568c23d76003a9a4
* Made method descriptions imperative: "Do this" rather than "Does this"
* Changed use of "this object" to "the object" in method documentation
* Added missing documentation
* Fixed incorrect documentation
* Fixed incorrect debug method names (as in those VeDmClassName tags we add to functions so they make sense when dumped into in the console)
* Normalized use of package names throughout
* Normalized class descriptions
* Removed incorrect @abstract tags
* Added missing @method tags
* Lots of other minor cleanup
Change-Id: I4ea66a2dd107613e2ea3a5f56ff54d675d72957e
Some of these stubs didn't inherit Node at all. Made them all inherit
LeafNode because their rules specify they can't have children.
Change-Id: If4afc8de350f67ee78a41307c426ec2aceeb884f
See CODING.md for how to run it.
Mistakes fixed:
* Warning: Unknown type function
-> Function
* Warning: Unknown type DOMElement
-> HTMLElement
* Warning: Unknown type DOM Node
-> HTMLElement
* Warning: Unknown type Integer
-> Mixed
* Warning: Unknown type Command
-> ve.Command
* Warning: Unknown type any
-> number
* Warning: Unknown type ve.Transaction
-> ve.dm.Transaction
* Warning: Unknown type ve.dm.AnnotationSet
-> ve.AnnotationSet
* Warning: Unknown type false
-> boolean
* Warning: Unknown type ve.dm.AlienNode
ve.dm doesn't have a generic AlienNode like ve.ce
-> Unknown type ve.dm.AlienInlineNode|ve.dm.AlienBlockNode
* Warning: Unknown type ve.ve.Surface
-> ve.ce.Surface
* ve.example.lookupNode:
-> Last @param should be @return
* ve.dm.Transaction.prototype.pushReplace:
-> @param {Array] should be @param {Array}
* Warning: ve.BranchNode.js:27: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
* Warning: ve.LeafNode.js:21: {@link ve.Node#hasChildren} links to non-existing member
-> (removed)
Differences fixed:
* Variadic arguments are like @param {Type...} [name]
instead of @param {Type} [name...]
* Convert all file headers from /** to /*! because JSDuck tries
to parse all /** blocks and fails to parse with all sorts of
errors for "Global property", "Unnamed property", and
"Duplicate property".
Find: \/\*\*([^@]+)(@copyright)
Replace: /*!$1$2
* Indented blocks are considered code examples.
A few methods had documentation with numbered lists that were
indented, which have now been updated to not be intended.
* The free-form text descriptions are parsed with Markdown,
which requires lists to be separated from paragraphs by an
empty line.
And we should use `backticks` instead of {braces} for inline
code in text paragraphs.
* Doc blocks for classes and their constructor have to be
in the correct order (@constructor, @param, @return must be
before @class, @abstract, @extends etc.)
* `@extends Class` must not have Class {wrapped}
* @throws must start with a {Type}
* @example means something else. It is used for an inline demo
iframe, not code block. For that simply indent with spaces.
* @member means something else.
Non-function properties are marked with @property, not @member.
* To create a link to a class or member, in most cases the name
is enough to create a link. E.g. Foo, Foo.bar, Foo.bar#quux,
where a hash stands for "instance member", so Foo.bar#quux,
links to Foo.bar.prototype.quux (the is not supported, as
"prototype" is considered an implementation detail, it only
indexes class name and method name).
If the magic linker doesn't work for some case, the
verbose syntax is {@link #target label}.
* @property can't have sub-properties (nested @param and @return
values are supported, only @static @property can't be nested).
We only have one case of this, which can be worked around by
moving those in a new virtual class. The code is unaltered
(only moved down so that it isn't with the scope of the main
@class block). ve.dm.TransactionProcessor.processors.
New:
* @mixins: Classes mixed into the current class.
* @event: Events that can be emitted by a class. These are also
inherited by subclasses. (+ @param, @return and @preventable).
So ve.Node#event-attach is inherited to ve.dm.BreakNode,
just like @method is.
* @singleton: Plain objects such as ve, ve.dm, ve.ce were missing
documentation causing a tree error. Documented those as a
JSDuck singleton, which they but just weren't documented yet.
NB: Members of @singleton don't need @static (if present,
triggers a compiler warning).
* @chainable: Shorthand for "@return this". We were using
"@return {classname}" which is ambiguous (returns the same
instance or another instance?), @chainable is specifically
for "@return this". Creates proper labels in the generated
HTML pages.
Removed:
* @mixin: (not to be confused with @mixins). Not supported by
JSDuck. Every class is standalone anyway. Where needed marked
them @class + @abstract instead.
Change-Id: I6a7c9e8ee8f995731bc205d666167874eb2ebe23
This happens when the <span> is the start of unwrapped content. The
converter logic to look at the tag name in wrapping mode doesn't kick in
because we're not yet in wrapping mode at that point.
The core issue was that previously, we relied on the document
structure/state to choose between alienBlock and alienInline, and only
used the tag name where the document structure was ambiguous (wrapping).
Changed this to be the other way around: we now rely primarily on the
tag name, and if that doesn't match what we expect based on the document
structure, we work around that if possible. Specifically:
* inline tag in our wrapper --> inline alien
* block tag in our wrapper --> close wrapper, block alien
* inline tag in wrapper that's not ours --> inline alien
* block tag in wrapper that's not ours --> *inline* alien
* inline tag in structural location --> open wrapper, inline alien
* block tag in structural location --> block alien
* inline tag in content location --> inline alien
* block tag in content location --> *inline* alien
only in the fourth and the last case do we need to use the "wrong" alien type to
preserve document validity, and it will always be inline where block was
expected, which should reduce UI issues.
The condensed version of the above, which is used in the code, is:
* If in a non-wrapper content location, use inline
* If in a wrapper that's not ours, use inline
* Otherwise, decide based on tag name
* Open or close wrapper if needed
ve.dm.Converter:
* Replace isInline logic in createAlien() with the above
* Factor out code to start wrapping (was duplicated) into startWrapping()
* Call startWrapping() if createAlien() returns an alienInline and we're
in a structural location
Tests:
* Add test cases with aliens at the start and end of unwrapped content
** The first one failed prior to these changes and now passes, the
second one was already passing
* Fix about group test case, was exhibiting the bug that this commit fixes
Change-Id: I657aa0ff5bc2b57cd48ef8a99c8ca930936c03b8
I noticed this bug on [[List of Presidents of the United States]]. When
there's HTML that looks like "<td>Foo\n<meta/></td>", the converter will
collect the newline in wrappedWhitespace, then attempt to splice it out
and store it in internal data. But instead, it ends up splicing out the
/metaBlock element, which causes strange unbalanced input, which causes
an empty table in the node tree.
Change-Id: I79ed2fa9a834cc42759c7d21250d8842f563d38f