op !== operation in all iterations of this loop except the first,
and using information from one operation to apply another one
doesn't work very well.
The fact that undefined cast to a number is NaN rather than 0
is very unhelpful :(
Also fixed some commas that should be semicolons.
Bug: 52238
Change-Id: I4138c023c955f2866881084506e24bb8b6db5a4d
Now Ctrl+\ (Cmd+\ on Mac) will trigger the 'clear annotations' button
on the current context. Ideally we'd also bond to the 'clear' keyboard
button (ASCII 12) but it does not seem possible to do that yet.
Bug: 51507
Change-Id: I300ec1ffa237e51418ec429be39001f820f053ae
== Renamed methods ==
* enableFloating -> enableFloatable
* disableFloating -> disableFloatable
* setPosition -> float
* resetPosition -> unfloat
== Scroll and resize event ==
Timeline for scroll event reduced from about half a dozen
"Recalculate style" and various forced "Paint" down to 0.
New timeline for scroll is clean (for me: from ~35 to ~59 fps):
* 1 Event (scroll)
* 1 Composite Layer
The composite layer action is the browser changing the viewport
to a different portion of the document drawing. Exactly the one
thing a simple scroll should do.
Timeline for resize event is still pretty crowded and low fps,
but it has improved. Further improvement would likely be around
using requestAnimation and going outside ve.ui.Toolbar.
== Changes ==
* New: ve.ui.Toolbar#initialize.
Similar to what surface has. Users of Toolbar should decide
whether to call enableFloatable, append it to the DOM at some
point and then call initialize() once.
* Don't compute offset() every time.
Eliminated by doing it once in #initialize. These 'top' and
'left' offsets do not change.
* Don't compute outerWidth() and $window.width() every time.
Reduced by doing it once in #initialize to compute the 'right'
offset. Updating it only on resize.
* Don't set 'top' every time.
This is already in the stylesheet. It was never set to anything
else so the abstraction for it in #float has been removed.
This also made it obvious that code for "ve-ui-toolbar-bottom"
was unused and left behind. Tha class was only ever being
removed from something (never added).
The one addClass call for it was in a condition that is always
false ("if top > 0").
* Don't set 'left' every time.
Eliminated by doing it once in #float.
* Don't set 'right' every time.
Reduced by no longer doing it on scroll. Done once in #float,
and on resize after computing the new value for it.
* Remove no-op style operations.
Wrapped methods in if-floatable, if-floated etc. to reduce a
fair amount of easily avoided re-paint overhead.
* Avoid double re-paint in mw.ViewPageTarget.
Though we prevent a lot of redundant re-paints now, whenever
we do repaint we want to do it in 1 repaint instead of 2.
ve.ui.Toolbar emits #toolbarPosition, which tells
mw.ViewPageTarget to update toolbarTracker which would read
the new $bar style properties and copy them over to the
$toolbarTracker. However, this read operation forces the browser
to do an immediate re-paint half-way just for $bar.
Browsers only repaint when style properties are changed and
JS has yielded. The exception to this is JS reading style
properties: in that case the browser is forced to do those
deferred repaints directly and reflect the new values.
We can avoid this double repaint by passing the updated values
as data instead of requiring the receiver to read the DOM (and
thus a keep the deferred repaint). Now toolbarTracker can use
them directly whilst the browser hasn't even repainted $bar yet.
== Clean up ==
* Redundant "border-radius: 0". This would reset something, but
it never does. None of the things it inherits from set a
border-radius. There is one subclass where toolbar is used
with a border-radius (".ve-ui-surfaceWidget .ve-ui-toolbar-bar"
sets a border-radius) which overrides this on purpose, so the
default of 0 is redundant.
* Pattern "if ( .. ) addClass() else removeClass()" changed to:
"toggleClass( , .. )"
Bug: 52014
Change-Id: I9be855148962eee068a77fe83e98eb20bbdcfeec
These have been pointing to the same method for a while now,
we can safely remove these obsolete aliases and just use it
as generic copy.
* Each file touched by my editor had its new line at EOF fixed
where absent
* Don't copy an otherwise unused empty object
(ve.dm.Converter)
* Use common ve#copy syntax instead to create a link
(ve.dm.Document, ve.dm.example)
* Remove redundant conditionals for isArray/copyArray/copyObject
(ve.dm.example)
Change-Id: If560e658dc1fb59bf01f702c97e3e82a50a8a255
Objective:
* Make trigger demo work
Changes:
demos/trigger/index.html
* Include new dependencies: oojs, unicodejs
* Update use of ve.Trigger, which is now ve.ui.Trigger
Change-Id: I973375821625629b3dfc3f30f07fc8ec699d24ce
The core changes to postedit in I778b18b that this depends on were
deployed to the cluster as part of 1.22wmf11.
Bug: 39632
Change-Id: Id4a8bc22c09a552ef79670b0d4fc4a70df07ec33
Opera 12 seems to work well enough, but I'm not confident enough to
whitelist it just yet.
Opera 15 is basically Chrome with a different interface, so it should
work perfectly, but it's barely out of beta and untested right now.
Bug: 36000
Change-Id: Ia80a6f53f8c128ef52d0bfde1828fdc132046afb
With our current config, 429587d would have enabled VisualEditor by
default on all wikis, which is not what we want to do. We can make the
-enable preference default to true in the extension if we really want
to, but that requires a change to wmf-config as well.
Change-Id: I95664588e5e4e3d6caed90e1c83accc9434ecd49
This is the language inspector UI engine with ULS core.
The Language Inspector works alongside ULS to choose and change language
blocks in text. The inspector was based on ve.ui.TextInputWidget and
now changed to inherit ve.ui.Widget and display details in a table
instead of an input textbox.
Added jQuery.ULS module:
* Repository: https://github.com/wikimedia/jquery.uls
* Latest Commit 728f112ffc90b03b50c0109487886a2647f12020
* Taken 'src' / 'images' and 'css' folders into modules/jquery.uls
Bug: 47759
Change-Id: I3c9fd6c135c05a54f6c7fc28c5962fc0a6677806
Annotations' attributes might contain DOM elements, which cause infinite
recursion in ve.compare(). Annotation classes can protect against this
by overriding getHashObject() to summarize DOM nodes, but that doesn't
help if that's not respected everywhere.
Instead, compare the hash objects, those are safe. This does not appear
to be a problem in practice, currently, because the nowiki annotation
is experimental, oo.compare() now short-circuits if a === b, and because
of optimizations in openAndCloseAnnotations() which lead to the relevant
compareToForSerialization() code path being taken very rarely.
Bug: 51948
Change-Id: If1bcc3eee4fd14d107db1935d89dcc5516643b53
This only affects debug mode, but things look broken when the
background of every single node is set to transparent (e.g. a
<pre> looks weird with a white instead of grey background).
It also leaves the DOM dirty full of inline styles.
Though setting a grey background isn't guaranteed to be visible
either, and all of these redraws and stuff really slow things
down (we should perhaps only start doing these after the initial
document is painted).. the least we can do is undo it and not
leave it there.
Change-Id: I9abfd46765914828ad8618748be5716a8c6b185c
By using annotation indexes only we can avoid a lot of
ve.getHash calls. This reduces the number of getHash calls
on load of [[:en:Argentina]] from ~60,000 to ~2,000.
Bug: 52013
Change-Id: I0bc9aa8feea5f7e4e90a5fcd829de57cab803c15
We would dirty-diff "</span>\n<!-- comment -->\n<span>" to
"</span>\n\n<!-- comment --><span>", i.e. the second newline made
a bunny-hop to the left over the comment.
The actual bug turned out to involve a double bunny-hop, with
"</span> <!-- comment -->\n<span>" turning into
"</span>\n <!--comment --><span>", i.e. the newline bunny-hops
both the comment and the space.
This happened because outputWrappedMetaItems() didn't take
wrappedWhitespace into account when restoring meta items and
associated whitespace. I hacked a check for wrappedWhitespace into it,
but we should really just rewrite this pile of hacks into a unified
system for queuing and processing both whitespace and metadata.
Change-Id: I4375f4c07983ffec6877d0371aeaa9bf6e65fd6e
To avoid confusion between IV store indexes and the index
within the set, rename them to storeIndex and offset.
Change-Id: Ic7d741bd5d39240d63fdc04a2df45658a64441de
As that method makes expensive ve.compare calls, we can quickly
avoid most cases by testing AnnotationSet#contains first.
On page load this reduces the number of ve.compare calls on
[[:en:Argentina]] from ~6,000,000 to about ~2,000.
Also reduces ve.compare calls per backspace keystroke from
~300 to 2.
We can optimise this further, but this is a good simple start.
Bug: 52013
Change-Id: Ie3b4517fd13383c48acb64b3c4e82051c34e7484
Previously, we'd clone the data but convert it in the context of
the existing dm.Document, whose nodes had pointers to elements in the
old data array, not to the cloned ones. Because dm.MWReferenceNode
has logic like if ( something === dataElement ), this caused the sanity
check conversion to behave slightly differently compared to the real
conversion that happens on save, and so a references corruption
bug went unnoticed.
Change-Id: I79a42ae21f91cb8eb410ae26ea638036db19e217
This not being here caused and causes various unexpected
scenarios to evolve around preferences being falsy (undefined)
or set to 0, and inability to distinguish between a user having
not set the preference, the preference not existing due to cache
or the preference being disabled explictly by the user.
Change-Id: Ie50b63ba5064e85d26dad8b622554bbe809c2634
Pretty straightforward, although we should start thinking about
grouping/hiding 'advanced' formatting options in the toolbar.
Making this button experimental for now until we've come up with
a way to deal with this problem.
Bug: 51590
Change-Id: Ieb1935b742aced4b883d8a194e6cb69be68473d0
Server-side, plugins can register themselves by adding to
$wgVisualEditorPluginModules. This is the recommended way for
MW extensions to extend VE. Client-side, plugins can register
themselves through mw.libs.ve.addPlugin(), which takes a string
(RL module name) or a callback.
When VisualEditor loads, we load the registered plugin modules in
parallel with ext.visualEditor.core. Note that they're loaded in
parallel, not after, and so the plugins should explicitly depend
on ext.visualEditor.core if they use or extend classes in VE core.
Once the modules finish loading and user and site scripts have run,
we execute the registered plugin callbacks. These callbacks can
optionally return a promise. We gather these promises and wait for
all of them to be resolved, then initialize the editor.
This allows Gadgets to extend VE by top-loading a small module that
depends on ext.visualEditor.viewPageTarget.init and calls
mw.libs.ve.addPlugin( 'ext.gadget.bottomHalfGadget' ); , the bottom
half being a hidden Gadget that depends on ext.visualEditor.core and
contains the actual code. The addPlugin() call needs to be in a
top-loading module because otherwise there's no guarantee that the
plugin will be registered before the user clicks edit and VE loads.
User and site scripts can extend VE by simply calling addPlugin()
directly, as mw.libs.ve is already present when user scripts run (since
it's top-loaded) and VE waits for 'user' and 'site' to run before
executing plugins.
If user/site scripts need to load additional JS files, they can load
these with $.getScript() and return the corresponding promise:
mw.libs.ve.addPlugin( function() { return $.getScript( 'URL' ); } );
For a diagram of all this, see
https://www.mediawiki.org/wiki/File:VE-plugin-infrastructure.jpg :)
VisualEditor.php:
* Add $wgVisualEditorPluginModules
VisualEditor.hooks.php:
* Expose $wgVisualEditorPluginModules in JS
ve.init.mw.ViewPageTarget.init.js:
* Add mw.libs.ve.addPlugin function that just stores the registered
values in an array and passes them into the mw.Target when it's
being initialized
ve.init.mw.Target.js:
* Add $wgVisualEditorPluginModules to the set of modules to load when
initializing VE
* Add a Deferred (this.modulesReady) to track module loading
* Add addPlugin() and addPlugins() methods that add to either
this.modules or this.pluginCallbacks
* In load(), instead of mw.loader.load()ing this.modules, use using()
to load this.modules plus user and site, and fire onModulesReady()
when they're loaded
* In onModulesReady(), execute the registered callbacks, gather the
returned promises, wait for all of them to be resolved, then resolve
this.modulesReady
* Fire onReady based on this.modulesReady being resolved, rather than
using a second using() call
Bug: 50514
Change-Id: Ib7d87a17eaac6ecdb8b0803b13840d7ee58902df