jenkins-bot has submitted this change and it was merged.
Change subject: Context refactor
......................................................................
Context refactor
* Context items are no longer options in a select
* Context is now a group element, it's items are the context items
* Context items are registered with the context item factory
* ModeledFactory is a new mixin for factories of things that have
static modelClasses properties
* If a selected model doesn't have a context item for it, the tool
factory is queried for matching tools and then ToolContextItem
objects are created to provide a fallback
* A context subclass can be configured to display context item
descriptions instead of bodies, and the mobile context does so
Change-Id: Ibca8cf5cb8ee2f0a8f520461cef3af6fa6f2ac4d
---
M .docs/categories.json
M .docs/eg-iframe.html
M build/modules.json
M demos/ve/desktop.html
M demos/ve/mobile.html
A src/ui/contextitems/ve.ui.CommentContextItem.js
A src/ui/contextitems/ve.ui.LanguageContextItem.js
A src/ui/contextitems/ve.ui.LinkContextItem.js
A src/ui/contextitems/ve.ui.ToolContextItem.js
A src/ui/styles/contextitems/ve.ui.CommentContextItem.css
A src/ui/styles/contextitems/ve.ui.LanguageContextItem.css
A src/ui/styles/contextitems/ve.ui.LinkContextItem.css
A src/ui/styles/contextitems/ve.ui.ToolContextItem.css
D src/ui/styles/ve.ui.Context.css
A src/ui/styles/ve.ui.ContextItem.css
M src/ui/styles/ve.ui.DesktopContext.css
A src/ui/styles/ve.ui.MobileContext.css
D src/ui/styles/widgets/ve.ui.MobileContextOptionWidget.css
M src/ui/ve.ui.Context.js
A src/ui/ve.ui.ContextItem.js
A src/ui/ve.ui.ContextItemFactory.js
M src/ui/ve.ui.DesktopContext.js
M src/ui/ve.ui.MobileContext.js
A src/ui/ve.ui.ModeledFactory.js
M src/ui/ve.ui.ToolFactory.js
M src/ui/ve.ui.js
D src/ui/widgets/ve.ui.MobileContextOptionWidget.js
M tests/index.html
28 files changed, 956 insertions(+), 285 deletions(-)
Approvals:
Esanders: Looks good to me, approved
jenkins-bot: Verified
diff --git a/.docs/categories.json b/.docs/categories.json
index 7d54d80..5a6b4e0 100644
--- a/.docs/categories.json
+++ b/.docs/categories.json
@@ -138,6 +138,10 @@
"classes": ["ve.ui.*Command"]
},
{
+ "name": "Context items",
+ "classes": ["ve.ui.*ContextItem"]
+ },
+ {
"name": "Factories",
"classes": ["ve.ui.*Factory"]
},
diff --git a/.docs/eg-iframe.html b/.docs/eg-iframe.html
index c95f847..36c7add 100644
--- a/.docs/eg-iframe.html
+++ b/.docs/eg-iframe.html
@@ -36,7 +36,11 @@
<link rel=stylesheet
href="../src/ui/styles/inspectors/ve.ui.FragmentInspector.css"
class="stylesheet-ve">
<link rel=stylesheet
href="../src/ui/styles/inspectors/ve.ui.LinkInspector.css"
class="stylesheet-ve">
<link rel=stylesheet
href="../src/ui/styles/inspectors/ve.ui.SpecialCharacterInspector.css"
class="stylesheet-ve">
- <link rel=stylesheet href="../src/ui/styles/ve.ui.Context.css"
class="stylesheet-ve">
+ <link rel=stylesheet
href="../src/ui/styles/ve.ui.ContextItem.css" class="stylesheet-ve">
+ <link rel=stylesheet
href="../src/ui/styles/contextitems/ve.ui.CommentContextItem.css"
class="stylesheet-ve">
+ <link rel=stylesheet
href="../src/ui/styles/contextitems/ve.ui.LanguageContextItem.css"
class="stylesheet-ve">
+ <link rel=stylesheet
href="../src/ui/styles/contextitems/ve.ui.LinkContextItem.css"
class="stylesheet-ve">
+ <link rel=stylesheet
href="../src/ui/styles/contextitems/ve.ui.ToolContextItem.css"
class="stylesheet-ve">
<link rel=stylesheet href="../src/ui/styles/ve.ui.Overlay.css"
class="stylesheet-ve">
<link rel=stylesheet href="../src/ui/styles/ve.ui.Surface.css"
class="stylesheet-ve">
<link rel=stylesheet
href="../src/ui/styles/widgets/ve.ui.SurfaceWidget.css" class="stylesheet-ve">
@@ -290,6 +294,13 @@
<script src="../src/ui/ve.ui.Overlay.js"></script>
<script src="../src/ui/ve.ui.Surface.js"></script>
<script src="../src/ui/ve.ui.Context.js"></script>
+ <script src="../src/ui/ve.ui.ModeledFactory.js"></script>
+ <script src="../src/ui/ve.ui.ContextItem.js"></script>
+ <script src="../src/ui/ve.ui.ContextItemFactory.js"></script>
+ <script
src="../src/ui/contextitems/ve.ui.CommentContextItem.js"></script>
+ <script
src="../src/ui/contextitems/ve.ui.LanguageContextItem.js"></script>
+ <script
src="../src/ui/contextitems/ve.ui.LinkContextItem.js"></script>
+ <script
src="../src/ui/contextitems/ve.ui.ToolContextItem.js"></script>
<script src="../src/ui/ve.ui.TableContext.js"></script>
<script src="../src/ui/ve.ui.Tool.js"></script>
<script src="../src/ui/ve.ui.Toolbar.js"></script>
diff --git a/build/modules.json b/build/modules.json
index 9b7126b..f9103fc 100644
--- a/build/modules.json
+++ b/build/modules.json
@@ -317,6 +317,13 @@
"src/ui/ve.ui.Overlay.js",
"src/ui/ve.ui.Surface.js",
"src/ui/ve.ui.Context.js",
+ "src/ui/ve.ui.ModeledFactory.js",
+ "src/ui/ve.ui.ContextItem.js",
+ "src/ui/ve.ui.ContextItemFactory.js",
+ "src/ui/contextitems/ve.ui.CommentContextItem.js",
+ "src/ui/contextitems/ve.ui.LanguageContextItem.js",
+ "src/ui/contextitems/ve.ui.LinkContextItem.js",
+ "src/ui/contextitems/ve.ui.ToolContextItem.js",
"src/ui/ve.ui.TableContext.js",
"src/ui/ve.ui.Tool.js",
"src/ui/ve.ui.Toolbar.js",
@@ -420,7 +427,11 @@
"src/ui/styles/inspectors/ve.ui.FragmentInspector.css",
"src/ui/styles/inspectors/ve.ui.LinkInspector.css",
"src/ui/styles/inspectors/ve.ui.SpecialCharacterInspector.css",
- "src/ui/styles/ve.ui.Context.css",
+ "src/ui/styles/ve.ui.ContextItem.css",
+
"src/ui/styles/contextitems/ve.ui.CommentContextItem.css",
+
"src/ui/styles/contextitems/ve.ui.LanguageContextItem.css",
+ "src/ui/styles/contextitems/ve.ui.LinkContextItem.css",
+ "src/ui/styles/contextitems/ve.ui.ToolContextItem.css",
"src/ui/styles/ve.ui.Overlay.css",
"src/ui/styles/ve.ui.Surface.css",
"src/ui/styles/widgets/ve.ui.SurfaceWidget.css",
@@ -561,12 +572,11 @@
"scripts": [
"src/ui/ve.ui.MobileSurface.js",
"src/ui/ve.ui.MobileContext.js",
- "src/ui/windowmanagers/ve.ui.MobileWindowManager.js",
- "src/ui/widgets/ve.ui.MobileContextOptionWidget.js"
+ "src/ui/windowmanagers/ve.ui.MobileWindowManager.js"
],
"styles": [
- "src/ui/styles/ve.ui.MobileSurface.css",
-
"src/ui/styles/widgets/ve.ui.MobileContextOptionWidget.css"
+ "src/ui/styles/ve.ui.MobileContext.css",
+ "src/ui/styles/ve.ui.MobileSurface.css"
],
"dependencies": [
"visualEditor.core.build"
diff --git a/demos/ve/desktop.html b/demos/ve/desktop.html
index 0b686e8..4400c1d 100644
--- a/demos/ve/desktop.html
+++ b/demos/ve/desktop.html
@@ -50,7 +50,11 @@
<link rel=stylesheet
href="../../src/ui/styles/inspectors/ve.ui.FragmentInspector.css"
class="stylesheet-ve">
<link rel=stylesheet
href="../../src/ui/styles/inspectors/ve.ui.LinkInspector.css"
class="stylesheet-ve">
<link rel=stylesheet
href="../../src/ui/styles/inspectors/ve.ui.SpecialCharacterInspector.css"
class="stylesheet-ve">
- <link rel=stylesheet
href="../../src/ui/styles/ve.ui.Context.css" class="stylesheet-ve">
+ <link rel=stylesheet
href="../../src/ui/styles/ve.ui.ContextItem.css" class="stylesheet-ve">
+ <link rel=stylesheet
href="../../src/ui/styles/contextitems/ve.ui.CommentContextItem.css"
class="stylesheet-ve">
+ <link rel=stylesheet
href="../../src/ui/styles/contextitems/ve.ui.LanguageContextItem.css"
class="stylesheet-ve">
+ <link rel=stylesheet
href="../../src/ui/styles/contextitems/ve.ui.LinkContextItem.css"
class="stylesheet-ve">
+ <link rel=stylesheet
href="../../src/ui/styles/contextitems/ve.ui.ToolContextItem.css"
class="stylesheet-ve">
<link rel=stylesheet
href="../../src/ui/styles/ve.ui.Overlay.css" class="stylesheet-ve">
<link rel=stylesheet
href="../../src/ui/styles/ve.ui.Surface.css" class="stylesheet-ve">
<link rel=stylesheet
href="../../src/ui/styles/widgets/ve.ui.SurfaceWidget.css"
class="stylesheet-ve">
@@ -307,6 +311,13 @@
<script src="../../src/ui/ve.ui.Overlay.js"></script>
<script src="../../src/ui/ve.ui.Surface.js"></script>
<script src="../../src/ui/ve.ui.Context.js"></script>
+ <script src="../../src/ui/ve.ui.ModeledFactory.js"></script>
+ <script src="../../src/ui/ve.ui.ContextItem.js"></script>
+ <script src="../../src/ui/ve.ui.ContextItemFactory.js"></script>
+ <script
src="../../src/ui/contextitems/ve.ui.CommentContextItem.js"></script>
+ <script
src="../../src/ui/contextitems/ve.ui.LanguageContextItem.js"></script>
+ <script
src="../../src/ui/contextitems/ve.ui.LinkContextItem.js"></script>
+ <script
src="../../src/ui/contextitems/ve.ui.ToolContextItem.js"></script>
<script src="../../src/ui/ve.ui.TableContext.js"></script>
<script src="../../src/ui/ve.ui.Tool.js"></script>
<script src="../../src/ui/ve.ui.Toolbar.js"></script>
diff --git a/demos/ve/mobile.html b/demos/ve/mobile.html
index ec72fa0..bba288a 100644
--- a/demos/ve/mobile.html
+++ b/demos/ve/mobile.html
@@ -50,7 +50,11 @@
<link rel=stylesheet
href="../../src/ui/styles/inspectors/ve.ui.FragmentInspector.css"
class="stylesheet-ve">
<link rel=stylesheet
href="../../src/ui/styles/inspectors/ve.ui.LinkInspector.css"
class="stylesheet-ve">
<link rel=stylesheet
href="../../src/ui/styles/inspectors/ve.ui.SpecialCharacterInspector.css"
class="stylesheet-ve">
- <link rel=stylesheet
href="../../src/ui/styles/ve.ui.Context.css" class="stylesheet-ve">
+ <link rel=stylesheet
href="../../src/ui/styles/ve.ui.ContextItem.css" class="stylesheet-ve">
+ <link rel=stylesheet
href="../../src/ui/styles/contextitems/ve.ui.CommentContextItem.css"
class="stylesheet-ve">
+ <link rel=stylesheet
href="../../src/ui/styles/contextitems/ve.ui.LanguageContextItem.css"
class="stylesheet-ve">
+ <link rel=stylesheet
href="../../src/ui/styles/contextitems/ve.ui.LinkContextItem.css"
class="stylesheet-ve">
+ <link rel=stylesheet
href="../../src/ui/styles/contextitems/ve.ui.ToolContextItem.css"
class="stylesheet-ve">
<link rel=stylesheet
href="../../src/ui/styles/ve.ui.Overlay.css" class="stylesheet-ve">
<link rel=stylesheet
href="../../src/ui/styles/ve.ui.Surface.css" class="stylesheet-ve">
<link rel=stylesheet
href="../../src/ui/styles/widgets/ve.ui.SurfaceWidget.css"
class="stylesheet-ve">
@@ -59,8 +63,8 @@
<link rel=stylesheet href="../../src/ui/styles/ve.ui.Icons.css"
class="stylesheet-ve">
<!-- visualEditor.mobile.build -->
+ <link rel=stylesheet
href="../../src/ui/styles/ve.ui.MobileContext.css">
<link rel=stylesheet
href="../../src/ui/styles/ve.ui.MobileSurface.css">
- <link rel=stylesheet
href="../../src/ui/styles/widgets/ve.ui.MobileContextOptionWidget.css">
<!-- visualEditor.theme.mediawiki -->
<link rel=stylesheet
href="../../src/themes/mediawiki/dialogs.css">
@@ -308,6 +312,13 @@
<script src="../../src/ui/ve.ui.Overlay.js"></script>
<script src="../../src/ui/ve.ui.Surface.js"></script>
<script src="../../src/ui/ve.ui.Context.js"></script>
+ <script src="../../src/ui/ve.ui.ModeledFactory.js"></script>
+ <script src="../../src/ui/ve.ui.ContextItem.js"></script>
+ <script src="../../src/ui/ve.ui.ContextItemFactory.js"></script>
+ <script
src="../../src/ui/contextitems/ve.ui.CommentContextItem.js"></script>
+ <script
src="../../src/ui/contextitems/ve.ui.LanguageContextItem.js"></script>
+ <script
src="../../src/ui/contextitems/ve.ui.LinkContextItem.js"></script>
+ <script
src="../../src/ui/contextitems/ve.ui.ToolContextItem.js"></script>
<script src="../../src/ui/ve.ui.TableContext.js"></script>
<script src="../../src/ui/ve.ui.Tool.js"></script>
<script src="../../src/ui/ve.ui.Toolbar.js"></script>
@@ -387,7 +398,6 @@
<script src="../../src/ui/ve.ui.MobileSurface.js"></script>
<script src="../../src/ui/ve.ui.MobileContext.js"></script>
<script
src="../../src/ui/windowmanagers/ve.ui.MobileWindowManager.js"></script>
- <script
src="../../src/ui/widgets/ve.ui.MobileContextOptionWidget.js"></script>
<!-- visualEditor.standalone.demo -->
<script src="../../demos/ve/demo.js"></script>
diff --git a/src/ui/contextitems/ve.ui.CommentContextItem.js
b/src/ui/contextitems/ve.ui.CommentContextItem.js
new file mode 100644
index 0000000..01b32f7
--- /dev/null
+++ b/src/ui/contextitems/ve.ui.CommentContextItem.js
@@ -0,0 +1,53 @@
+/*!
+ * VisualEditor CommentContextItem class.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+/**
+ * Context item for a comment.
+ *
+ * @extends ve.ui.ContextItem
+ *
+ * @param {ve.ui.Context} context Context item is in
+ * @param {ve.dm.Model} model Model item is related to
+ * @param {Object} config Configuration options
+ */
+ve.ui.CommentContextItem = function VeCommentContextItem( context, model,
config ) {
+ // Parent constructor
+ ve.ui.CommentContextItem.super.call( this, context, model, config );
+
+ // Initialization
+ this.$element.addClass( 've-ui-commentContextItem' );
+};
+
+/* Inheritance */
+
+OO.inheritClass( ve.ui.CommentContextItem, ve.ui.ContextItem );
+
+/* Static Properties */
+
+ve.ui.CommentContextItem.static.name = 'comment';
+
+ve.ui.CommentContextItem.static.icon = 'comment';
+
+ve.ui.CommentContextItem.static.label = OO.ui.deferMsg(
'visualeditor-commentinspector-title' );
+
+ve.ui.CommentContextItem.static.modelClasses = [ ve.dm.CommentNode ];
+
+ve.ui.CommentContextItem.static.embeddable = false;
+
+ve.ui.CommentContextItem.static.commandName = 'comment';
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+ve.ui.CommentContextItem.prototype.getDescription = function () {
+ return this.model.getAttribute( 'text' ).trim();
+};
+
+/* Registration */
+
+ve.ui.contextItemFactory.register( ve.ui.CommentContextItem );
diff --git a/src/ui/contextitems/ve.ui.LanguageContextItem.js
b/src/ui/contextitems/ve.ui.LanguageContextItem.js
new file mode 100644
index 0000000..3f07e44
--- /dev/null
+++ b/src/ui/contextitems/ve.ui.LanguageContextItem.js
@@ -0,0 +1,53 @@
+/*!
+ * VisualEditor LanguageContextItem class.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+/**
+ * Context item for a language.
+ *
+ * @extends ve.ui.ContextItem
+ *
+ * @param {ve.ui.Context} context Context item is in
+ * @param {ve.dm.Model} model Model item is related to
+ * @param {Object} config Configuration options
+ */
+ve.ui.LanguageContextItem = function VeLanguageContextItem( context, model,
config ) {
+ // Parent constructor
+ ve.ui.LanguageContextItem.super.call( this, context, model, config );
+
+ // Initialization
+ this.$element.addClass( 've-ui-languageContextItem' );
+};
+
+/* Inheritance */
+
+OO.inheritClass( ve.ui.LanguageContextItem, ve.ui.ContextItem );
+
+/* Static Properties */
+
+ve.ui.LanguageContextItem.static.name = 'language';
+
+ve.ui.LanguageContextItem.static.icon = 'language';
+
+ve.ui.LanguageContextItem.static.label = OO.ui.deferMsg(
'visualeditor-languageinspector-title' );
+
+ve.ui.LanguageContextItem.static.modelClasses = [ ve.dm.LanguageAnnotation ];
+
+ve.ui.LanguageContextItem.static.embeddable = false;
+
+ve.ui.LanguageContextItem.static.commandName = 'language';
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+ve.ui.LanguageContextItem.prototype.getDescription = function () {
+ return ve.ce.LanguageAnnotation.static.getDescription( this.model );
+};
+
+/* Registration */
+
+ve.ui.contextItemFactory.register( ve.ui.LanguageContextItem );
diff --git a/src/ui/contextitems/ve.ui.LinkContextItem.js
b/src/ui/contextitems/ve.ui.LinkContextItem.js
new file mode 100644
index 0000000..39d98d2
--- /dev/null
+++ b/src/ui/contextitems/ve.ui.LinkContextItem.js
@@ -0,0 +1,67 @@
+/*!
+ * VisualEditor LinkContextItem class.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+/**
+ * Context item for a link.
+ *
+ * @extends ve.ui.ContextItem
+ *
+ * @param {ve.ui.Context} context Context item is in
+ * @param {ve.dm.Model} model Model item is related to
+ * @param {Object} config Configuration options
+ */
+ve.ui.LinkContextItem = function VeLinkContextItem( context, model, config ) {
+ // Parent constructor
+ ve.ui.LinkContextItem.super.call( this, context, model, config );
+
+ // Initialization
+ this.$element.addClass( 've-ui-linkContextItem' );
+};
+
+/* Inheritance */
+
+OO.inheritClass( ve.ui.LinkContextItem, ve.ui.ContextItem );
+
+/* Static Properties */
+
+ve.ui.LinkContextItem.static.name = 'link';
+
+ve.ui.LinkContextItem.static.icon = 'link';
+
+ve.ui.LinkContextItem.static.label = OO.ui.deferMsg(
'visualeditor-linkinspector-title' );
+
+ve.ui.LinkContextItem.static.modelClasses = [ ve.dm.LinkAnnotation ];
+
+ve.ui.LinkContextItem.static.embeddable = false;
+
+ve.ui.LinkContextItem.static.commandName = 'link';
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+ve.ui.LinkContextItem.prototype.getDescription = function () {
+ return this.model.getHref();
+};
+
+/**
+ * @inheritdoc
+ */
+ve.ui.LinkContextItem.prototype.renderBody = function () {
+ this.$body.empty().append(
+ $( '<a>' )
+ .text( this.getDescription() )
+ .attr( {
+ href: this.model.getHref(),
+ target: '_blank'
+ } )
+ );
+};
+
+/* Registration */
+
+ve.ui.contextItemFactory.register( ve.ui.LinkContextItem );
diff --git a/src/ui/contextitems/ve.ui.ToolContextItem.js
b/src/ui/contextitems/ve.ui.ToolContextItem.js
new file mode 100644
index 0000000..677694b
--- /dev/null
+++ b/src/ui/contextitems/ve.ui.ToolContextItem.js
@@ -0,0 +1,58 @@
+/*!
+ * VisualEditor ToolContextItem class.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+/**
+ * Context item for a tool.
+ *
+ * @extends ve.ui.ContextItem
+ *
+ * @param {ve.ui.Context} context Context item is in
+ * @param {ve.dm.Model} model Model the item is related to
+ * @param {Function} tool Tool class the item is based on
+ * @param {Object} config Configuration options
+ */
+ve.ui.ToolContextItem = function VeToolContextItem( context, model, tool,
config ) {
+ // Parent constructor
+ ve.ui.ToolContextItem.super.call( this, context, model, config );
+
+ // Properties
+ this.tool = tool;
+
+ // Initialization
+ this.setIcon( tool.static.icon );
+ this.setLabel( tool.static.title );
+ this.$element.addClass( 've-ui-toolContextItem' );
+};
+
+/* Inheritance */
+
+OO.inheritClass( ve.ui.ToolContextItem, ve.ui.ContextItem );
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+ve.ui.ToolContextItem.prototype.getCommand = function () {
+ return ve.ui.commandRegistry.lookup( this.tool.static.commandName );
+};
+
+/**
+ * Get a description of the model.
+ *
+ * @return {string} Description of model
+ */
+ve.ui.ToolContextItem.prototype.getDescription = function () {
+ var description = '';
+
+ if ( this.model instanceof ve.dm.Annotation ) {
+ description = ve.ce.annotationFactory.getDescription(
this.model );
+ } else if ( this.model instanceof ve.dm.Node ) {
+ description = ve.ce.nodeFactory.getDescription( this.model );
+ }
+
+ return description;
+};
diff --git a/src/ui/styles/contextitems/ve.ui.CommentContextItem.css
b/src/ui/styles/contextitems/ve.ui.CommentContextItem.css
new file mode 100644
index 0000000..04eb263
--- /dev/null
+++ b/src/ui/styles/contextitems/ve.ui.CommentContextItem.css
@@ -0,0 +1,16 @@
+/*!
+ * VisualEditor UserInterface CommentContextItem styles.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+.ve-ui-commentContextItem .ve-ui-contextItem-body {
+ padding: 0.5em 0.5em 0 0.5em;
+ margin: 0 0.5em 1em 0.5em;
+ overflow: auto;
+ max-height: 10em;
+ font-family: 'Courier New', 'Courier', monospace;
+ white-space: pre-wrap;
+ line-height: 1em;
+ color: #555;
+}
diff --git a/src/ui/styles/contextitems/ve.ui.LanguageContextItem.css
b/src/ui/styles/contextitems/ve.ui.LanguageContextItem.css
new file mode 100644
index 0000000..4ca3976
--- /dev/null
+++ b/src/ui/styles/contextitems/ve.ui.LanguageContextItem.css
@@ -0,0 +1,12 @@
+/*!
+ * VisualEditor UserInterface LanguageContextItem styles.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+.ve-ui-languageContextItem .ve-ui-contextItem-body {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: #555;
+}
diff --git a/src/ui/styles/contextitems/ve.ui.LinkContextItem.css
b/src/ui/styles/contextitems/ve.ui.LinkContextItem.css
new file mode 100644
index 0000000..264c585
--- /dev/null
+++ b/src/ui/styles/contextitems/ve.ui.LinkContextItem.css
@@ -0,0 +1,11 @@
+/*!
+ * VisualEditor UserInterface LinkContextItem styles.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+.ve-ui-linkContextItem .ve-ui-contextItem-body {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
diff --git a/src/ui/styles/contextitems/ve.ui.ToolContextItem.css
b/src/ui/styles/contextitems/ve.ui.ToolContextItem.css
new file mode 100644
index 0000000..893310d
--- /dev/null
+++ b/src/ui/styles/contextitems/ve.ui.ToolContextItem.css
@@ -0,0 +1,11 @@
+/*!
+ * VisualEditor UserInterface ToolContextItem styles.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+.ve-ui-toolContextItem .ve-ui-contextItem-body {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
diff --git a/src/ui/styles/ve.ui.Context.css b/src/ui/styles/ve.ui.Context.css
deleted file mode 100644
index eebe9c2..0000000
--- a/src/ui/styles/ve.ui.Context.css
+++ /dev/null
@@ -1,10 +0,0 @@
-/*!
- * VisualEditor UserInterface Context styles.
- *
- * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
- */
-
-/* Hide context by default, only show programmatically */
-.ve-ui-context {
- visibility: hidden;
-}
\ No newline at end of file
diff --git a/src/ui/styles/ve.ui.ContextItem.css
b/src/ui/styles/ve.ui.ContextItem.css
new file mode 100644
index 0000000..1347de5
--- /dev/null
+++ b/src/ui/styles/ve.ui.ContextItem.css
@@ -0,0 +1,84 @@
+/*!
+ * VisualEditor UserInterface ContextItem styles.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+.ve-ui-contextItem {
+ /* Disable selection to avoid stealing focus from the surface */
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+}
+
+.ve-ui-contextItem-head {
+ display: table;
+ width: 100%;
+}
+
+.ve-ui-contextItem-title {
+ display: table-cell;
+ width: 1%;
+ text-align: left;
+ padding: 0.5em 1em;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.ve-ui-contextItem-icon {
+ display: inline-block;
+ vertical-align: middle;
+ width: 2em;
+ height: 2em;
+ background-position: center center;
+ background-repeat: no-repeat;
+}
+
+.ve-ui-contextItem-label {
+ min-width: 4em;
+ display: inline-block;
+ vertical-align: middle;
+ margin-left: 0.5em;
+}
+
+.ve-ui-contextItem-info {
+ display: table-cell;
+ position: relative;
+ width: 99%;
+}
+
+.ve-ui-contextItem-description {
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ bottom: 0;
+ line-height: 3.6em;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: #888;
+}
+
+.ve-ui-contextItem-actions {
+ display: table-cell;
+ width: 1%;
+ text-align: right;
+ padding: 0.5em;
+}
+
+.ve-ui-contextItem-editButton {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.ve-ui-contextItem-body {
+ padding: 0 1em 0.5em 1em;
+}
+
+.ve-ui-contextItem + .ve-ui-contextItem {
+ border-top: 1px solid #eee;
+}
diff --git a/src/ui/styles/ve.ui.DesktopContext.css
b/src/ui/styles/ve.ui.DesktopContext.css
index 6fc0a1d..0af77bc 100644
--- a/src/ui/styles/ve.ui.DesktopContext.css
+++ b/src/ui/styles/ve.ui.DesktopContext.css
@@ -8,9 +8,14 @@
position: absolute;
}
+.ve-ui-desktopContext .ve-ui-contextItem-description {
+ display: none;
+}
+
.ve-ui-desktopContext-menu {
position: absolute;
font-size: 0.8em;
+ width: 300px;
}
.ve-ui-desktopContext-menu .oo-ui-toolbar-bar {
diff --git a/src/ui/styles/ve.ui.MobileContext.css
b/src/ui/styles/ve.ui.MobileContext.css
new file mode 100644
index 0000000..0c6a34a
--- /dev/null
+++ b/src/ui/styles/ve.ui.MobileContext.css
@@ -0,0 +1,17 @@
+/*!
+ * VisualEditor UserInterface MobileSurface styles.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+.ve-ui-mobileContext {
+ font-size: 0.875em;
+}
+
+.ve-ui-mobileContext .ve-ui-contextItem-body {
+ display: none;
+}
+
+.ve-ui-mobileContext-menu {
+ border-top: 1px solid #eee;
+}
\ No newline at end of file
diff --git a/src/ui/styles/widgets/ve.ui.MobileContextOptionWidget.css
b/src/ui/styles/widgets/ve.ui.MobileContextOptionWidget.css
deleted file mode 100644
index 488410c..0000000
--- a/src/ui/styles/widgets/ve.ui.MobileContextOptionWidget.css
+++ /dev/null
@@ -1,22 +0,0 @@
-/*!
- * VisualEditor UserInterface MobileContextOptionWidget styles.
- *
- * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
- */
-
-.ve-ui-mobileContextOptionWidget .oo-ui-labelElement-label {
- max-width: none;
- text-overflow: clip;
-}
-
-.ve-ui-mobileContextOptionWidget-label-primary {
- display: block;
- text-overflow: ellipsis;
- overflow: hidden;
-}
-
-.ve-ui-mobileContextOptionWidget-label-secondary {
- float: right;
- margin-left: 1em;
- color: #347bff;
-}
diff --git a/src/ui/ve.ui.Context.js b/src/ui/ve.ui.Context.js
index 15c8276..a02efa3 100644
--- a/src/ui/ve.ui.Context.js
+++ b/src/ui/ve.ui.Context.js
@@ -10,6 +10,7 @@
* @class
* @abstract
* @extends OO.ui.Element
+ * @mixins OO.ui.GroupElement
*
* @constructor
* @param {ve.ui.Surface} surface
@@ -17,14 +18,17 @@
*/
ve.ui.Context = function VeUiContext( surface, config ) {
// Parent constructor
- OO.ui.Element.call( this, config );
+ ve.ui.Context.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.GroupElement.call( this, config );
// Properties
this.surface = surface;
this.visible = false;
+ this.choosing = false;
this.inspector = null;
this.inspectors = this.createInspectorWindowManager();
- this.menu = new ve.ui.ContextSelectWidget( { $: this.$ } );
this.lastSelectedNode = null;
this.afterContextChangeTimeout = null;
this.afterContextChangeHandler = this.afterContextChange.bind( this );
@@ -33,22 +37,38 @@
// Events
this.surface.getModel().connect( this, { contextChange:
'onContextChange' } );
this.inspectors.connect( this, { opening: 'onInspectorOpening' } );
- this.menu.connect( this, { choose: 'onContextItemChoose' } );
// Initialization
// Hide element using a class, not this.toggle, as child implementations
// of toggle may require the instance to be fully constructed before
running.
+ this.$group.addClass( 've-ui-context-menu' );
this.$element
- .addClass( 've-ui-context oo-ui-element-hidden' );
- this.menu.toggle( false );
+ .addClass( 've-ui-context oo-ui-element-hidden' )
+ .append( this.$group );
this.inspectors.$element.addClass( 've-ui-context-inspectors' );
};
/* Inheritance */
OO.inheritClass( ve.ui.Context, OO.ui.Element );
+OO.mixinClass( ve.ui.Context, OO.ui.GroupElement );
+
+/* Static Property */
+
+/**
+ * Instruct items to provide only a basic rendering.
+ *
+ * @static
+ * @inheritable
+ * @property {boolean}
+ */
+ve.ui.Context.static.basicRendering = false;
/* Methods */
+
+ve.ui.Context.prototype.shouldUseBasicRendering = function () {
+ return this.constructor.static.basicRendering;
+};
/**
* Handle context change event.
@@ -75,8 +95,8 @@
this.afterContextChangeTimeout = setTimeout(
this.afterContextChangeHandler );
}
}
- // Purge available tools cache
- this.availableTools = null;
+ // Purge related items cache
+ this.relatedSources = null;
};
/**
@@ -89,17 +109,21 @@
this.afterContextChangeTimeout = null;
if ( this.isVisible() ) {
- if ( this.menu.isVisible() ) {
+ if ( !this.isEmpty() ) {
if ( this.isInspectable() ) {
// Change state: menu -> menu
- this.populateMenu();
+ this.teardownMenuItems();
+ this.setupMenuItems();
this.updateDimensionsDebounced();
} else {
// Change state: menu -> closed
- this.menu.toggle( false );
+ this.toggleMenu( false );
this.toggle( false );
}
- } else if ( this.inspector && ( !selectedNode || ( selectedNode
!== this.lastSelectedNode ) ) ) {
+ } else if (
+ this.inspector &&
+ ( !selectedNode || ( selectedNode !==
this.lastSelectedNode ) )
+ ) {
// Change state: inspector -> (closed|menu)
// Unless there is a selectedNode that hasn't changed
(e.g. your inspector is editing a node)
this.inspector.close();
@@ -107,8 +131,7 @@
} else {
if ( this.isInspectable() ) {
// Change state: closed -> menu
- this.menu.toggle( true );
- this.populateMenu();
+ this.toggleMenu( true );
this.toggle( true );
}
}
@@ -139,9 +162,9 @@
opening
.progress( function ( data ) {
if ( data.state === 'setup' ) {
- if ( context.menu.isVisible() ) {
+ if ( !context.isEmpty() ) {
// Change state: menu -> inspector
- context.menu.toggle( false );
+ context.toggleMenu( false );
} else if ( !context.isVisible() ) {
// Change state: closed -> inspector
context.toggle( true );
@@ -152,7 +175,7 @@
.always( function ( opened ) {
opened.always( function ( closed ) {
closed.always( function () {
- var inspectable =
!!context.getAvailableTools().length;
+ var inspectable =
context.isInspectable();
context.inspector = null;
@@ -161,8 +184,7 @@
if ( inspectable ) {
// Change state: inspector ->
menu
- context.menu.toggle( true );
- context.populateMenu();
+ context.toggleMenu( true );
context.updateDimensionsDebounced();
} else {
// Change state: inspector ->
closed
@@ -176,17 +198,6 @@
} );
} );
} );
-};
-
-/**
- * Handle context item choose events.
- *
- * @param {ve.ui.ContextOptionWidget} item Chosen item
- */
-ve.ui.Context.prototype.onContextItemChoose = function ( item ) {
- if ( item ) {
- item.getCommand().execute( this.surface );
- }
};
/**
@@ -204,7 +215,7 @@
* @return {boolean} Content is inspectable
*/
ve.ui.Context.prototype.isInspectable = function () {
- return !!this.getAvailableTools().length;
+ return !!this.getRelatedSources().length;
};
/**
@@ -212,35 +223,63 @@
*
* @return {boolean} Content is inspectable
*/
-ve.ui.Context.prototype.hasInspector = function () {
- var i, availableTools = this.getAvailableTools();
- for ( i = availableTools.length - 1; i >= 0; i-- ) {
- if ( availableTools[i].tool.prototype instanceof
ve.ui.InspectorTool ) {
- return true;
+ve.ui.Context.prototype.isEmbeddable = function () {
+ var i, len,
+ sources = this.getRelatedSources();
+
+ for ( i = 0, len = sources.length; i < len; i++ ) {
+ if ( !sources[i].embedable ) {
+ return false;
}
}
- return false;
+
+ return true;
};
/**
- * Get available tools.
+ * Get related item sources.
*
* Result is cached, and cleared when the model or selection changes.
*
- * @returns {Object[]} List of objects containing `tool` and `model`
properties, representing each
- * compatible tool and the node or annotation it is compatible with
+ * @returns {Object[]} List of objects containing `type`, `name` and `model`
properties,
+ * representing each compatible type (either `item` or `tool`), symbolic
name of the item or tool
+ * and the model the item or tool is compatible with
*/
-ve.ui.Context.prototype.getAvailableTools = function () {
- if ( !this.availableTools ) {
+ve.ui.Context.prototype.getRelatedSources = function () {
+ var i, len, toolClass, items, tools, models,
+ selectedModels =
this.surface.getModel().getFragment().getSelectedModels();
+
+ if ( !this.relatedSources ) {
+ this.relatedSources = [];
if ( this.surface.getModel().getSelection() instanceof
ve.dm.LinearSelection ) {
- this.availableTools =
ve.ui.toolFactory.getToolsForFragment(
- this.surface.getModel().getFragment()
- );
- } else {
- this.availableTools = [];
+ models = [];
+ items = ve.ui.contextItemFactory.getRelatedItems(
selectedModels );
+ for ( i = 0, len = items.length; i < len; i++ ) {
+ models.push( items[i].model );
+ this.relatedSources.push( {
+ type: 'item',
+ embedable:
ve.ui.contextItemFactory.isEmbeddable( items[i].name ),
+ name: items[i].name,
+ model: items[i].model
+ } );
+ }
+ tools = ve.ui.toolFactory.getRelatedItems(
selectedModels );
+ for ( i = 0, len = tools.length; i < len; i++ ) {
+ if ( models.indexOf( tools[i].model ) === -1 ) {
+ toolClass = ve.ui.toolFactory.lookup(
tools[i].name );
+ this.relatedSources.push( {
+ type: 'tool',
+ embedable: !toolClass ||
+ !( toolClass.prototype
instanceof ve.ui.InspectorTool ),
+ name: tools[i].name,
+ model: tools[i].model
+ } );
+ }
+ }
}
}
- return this.availableTools;
+
+ return this.relatedSources;
};
/**
@@ -262,15 +301,6 @@
};
/**
- * Get context menu.
- *
- * @return {ve.ui.ContextSelectWidget}
- */
-ve.ui.Context.prototype.getMenu = function () {
- return this.menu;
-};
-
-/**
* Create a inspector window manager.
*
* @method
@@ -283,34 +313,72 @@
};
/**
- * Create a context item widget
+ * Toggle the menu.
*
- * @param {Object} tool Object containing tool and model properties.
- * @return {ve.ui.ContextOptionWidget} Context item widget
+ * @param {boolean} [show] Show the menu, omit to toggle
+ * @chainable
*/
-ve.ui.Context.prototype.createItem = function ( tool ) {
- return new ve.ui.ContextOptionWidget(
- tool.tool, tool.model, { $: this.$, data: tool.tool.static.name
}
- );
+ve.ui.Context.prototype.toggleMenu = function ( show ) {
+ show = show === undefined ? !this.choosing : !!show;
+
+ if ( show !== this.choosing ) {
+ this.choosing = show;
+ this.$element.toggleClass( 've-ui-context-choosing', show );
+ if ( show ) {
+ this.setupMenuItems();
+ } else {
+ this.teardownMenuItems();
+ }
+ }
+
+ return this;
};
/**
- * Update the contents of the menu.
+ * Setup menu items.
*
+ * @protected
* @chainable
*/
-ve.ui.Context.prototype.populateMenu = function () {
- var i, len,
- items = [],
- tools = this.getAvailableTools();
+ve.ui.Context.prototype.setupMenuItems = function () {
+ var i, len, source,
+ sources = this.getRelatedSources(),
+ items = [];
- this.menu.clearItems();
- if ( tools.length ) {
- for ( i = 0, len = tools.length; i < len; i++ ) {
- items.push( this.createItem( tools[i] ) );
+ for ( i = 0, len = sources.length; i < len; i++ ) {
+ source = sources[i];
+ if ( source.type === 'item' ) {
+ items.push( ve.ui.contextItemFactory.create(
+ sources[i].name, this, sources[i].model, { $:
this.$ }
+ ) );
+ } else if ( source.type === 'tool' ) {
+ items.push( new ve.ui.ToolContextItem(
+ this, sources[i].model,
ve.ui.toolFactory.lookup( sources[i].name ), { $: this.$ }
+ ) );
}
- this.menu.addItems( items );
}
+
+ this.addItems( items );
+ for ( i = 0, len = items.length; i < len; i++ ) {
+ items[i].setup();
+ }
+
+ return this;
+};
+
+/**
+ * Teardown menu items.
+ *
+ * @protected
+ * @chainable
+ */
+ve.ui.Context.prototype.teardownMenuItems = function () {
+ var i, len;
+
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ this.items[i].teardown();
+ }
+ this.clearItems();
return this;
};
@@ -347,7 +415,6 @@
// Disconnect events
this.surface.getModel().disconnect( this );
this.inspectors.disconnect( this );
- this.menu.disconnect( this );
// Destroy inspectors WindowManager
this.inspectors.destroy();
diff --git a/src/ui/ve.ui.ContextItem.js b/src/ui/ve.ui.ContextItem.js
new file mode 100644
index 0000000..e07c575
--- /dev/null
+++ b/src/ui/ve.ui.ContextItem.js
@@ -0,0 +1,203 @@
+/*!
+ * VisualEditor UserInterface ContextItem class.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+/**
+ * Item in a context.
+ *
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.IconElement
+ * @mixins OO.ui.LabelElement
+ * @mixins OO.ui.PendingElement
+ *
+ * @constructor
+ * @param {ve.ui.Context} context Context item is in
+ * @param {ve.dm.Model} model Model item is related to
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [basic] Render only basic information
+ */
+ve.ui.ContextItem = function ( context, model, config ) {
+ // Parent constructor
+ ve.ui.ContextItem.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.IconElement.call( this, config );
+ OO.ui.LabelElement.call( this, config );
+ OO.ui.PendingElement.call( this, config );
+
+ // Properties
+ this.context = context;
+ this.model = model;
+ this.$head = $( '<div>' );
+ this.$title = $( '<div>' );
+ this.$actions = $( '<div>' );
+ this.$body = $( '<div>' );
+ this.$info = $( '<div>' );
+ this.$description = $( '<div>' );
+ this.editButton = new OO.ui.ButtonWidget( {
+ label: 'Edit',
+ flags: [ 'progressive' ],
+ classes: [ 've-ui-contextItem-editButton' ]
+ } );
+
+ // Events
+ this.editButton.connect( this, { click: 'onEditButtonClick' } );
+ this.$element.on( 'mousedown', false );
+
+ // Initialization
+ this.$label.addClass( 've-ui-contextItem-label' );
+ this.$icon.addClass( 've-ui-contextItem-icon' );
+ this.$description.addClass( 've-ui-contextItem-description' );
+ this.$info
+ .addClass( 've-ui-contextItem-info' )
+ .append( this.$description );
+ this.$title
+ .addClass( 've-ui-contextItem-title' )
+ .append( this.$icon, this.$label );
+ this.$actions
+ .addClass( 've-ui-contextItem-actions' )
+ .append( this.editButton.$element );
+ this.$head
+ .addClass( 've-ui-contextItem-head' )
+ .append( this.$title, this.$info, this.$actions );
+ this.$body.addClass( 've-ui-contextItem-body' );
+ this.$element
+ .addClass( 've-ui-contextItem' )
+ .toggleClass( 've-ui-contextItem-basic',
this.context.shouldUseBasicRendering() )
+ .append( this.$head, this.$body );
+};
+
+/* Inheritance */
+
+OO.inheritClass( ve.ui.ContextItem, OO.ui.Widget );
+OO.mixinClass( ve.ui.ContextItem, OO.ui.IconElement );
+OO.mixinClass( ve.ui.ContextItem, OO.ui.LabelElement );
+OO.mixinClass( ve.ui.ContextItem, OO.ui.PendingElement );
+
+/* Static Properties */
+
+ve.ui.ContextItem.static.editable = true;
+
+ve.ui.ContextItem.static.embeddable = true;
+
+ve.ui.ContextItem.static.commandName = null;
+
+/**
+ * Annotation or node models this item is related to.
+ *
+ * Used by #isCompatibleWith.
+ *
+ * @static
+ * @property {Function[]}
+ * @inheritable
+ */
+ve.ui.ContextItem.static.modelClasses = [];
+
+/* Methods */
+
+/**
+ * Handle edit button click events.
+ *
+ * @localdoc Executes the command related to #static-commandName on the
context's surface
+ *
+ * @protected
+ */
+ve.ui.ContextItem.prototype.onEditButtonClick = function () {
+ var command = this.getCommand();
+
+ if ( command ) {
+ command.execute( this.context.getSurface() );
+ }
+};
+
+/**
+ * Check if this item is compatible with a given model.
+ *
+ * @static
+ * @inheritable
+ * @param {ve.dm.Model} model Model to check
+ * @return {boolean} Item can be used with model
+ */
+ve.ui.ContextItem.static.isCompatibleWith = function ( model ) {
+ return ve.isInstanceOfAny( model, this.modelClasses );
+};
+
+/**
+ * Check if item is editable.
+ *
+ * @return {boolean} Item is editable
+ */
+ve.ui.ContextItem.prototype.isEditable = function () {
+ return this.constructor.static.editable;
+};
+
+/**
+ * Get the command for this item.
+ *
+ * @return {ve.ui.Command} Command
+ */
+ve.ui.ContextItem.prototype.getCommand = function () {
+ return ve.ui.commandRegistry.lookup(
this.constructor.static.commandName );
+};
+
+/**
+ * Get the description.
+ *
+ * @localdoc Override for custom description content
+ * @return {string} Item description
+ */
+ve.ui.ContextItem.prototype.getDescription = function () {
+ return '';
+};
+
+/**
+ * Render the body.
+ *
+ * @localdoc Renders the result of #getDescription, override for custom body
rendering
+ */
+ve.ui.ContextItem.prototype.renderBody = function () {
+ this.$body.text( this.getDescription() );
+};
+
+/**
+ * Render the description.
+ *
+ * @localdoc Renders the result of #getDescription, override for custom
description rendering
+ */
+ve.ui.ContextItem.prototype.renderDescription = function () {
+ this.$description.text( this.getDescription() );
+};
+
+/**
+ * Setup the item.
+ *
+ * @localdoc Calls #renderDescription if the context suggests basic rendering
or #renderBody if not,
+ * override to start any async rendering common to the body and description
+ * @chainable
+ */
+ve.ui.ContextItem.prototype.setup = function () {
+ this.editButton.toggle( this.isEditable() );
+
+ if ( this.context.shouldUseBasicRendering() ) {
+ this.renderDescription();
+ } else {
+ this.renderBody();
+ }
+
+ return this;
+};
+
+/**
+ * Teardown the item.
+ *
+ * @localdoc Empties the description and body, override to abort any async
rendering
+ * @chainable
+ */
+ve.ui.ContextItem.prototype.teardown = function () {
+ this.$description.empty();
+ this.$body.empty();
+ return this;
+};
diff --git a/src/ui/ve.ui.ContextItemFactory.js
b/src/ui/ve.ui.ContextItemFactory.js
new file mode 100644
index 0000000..0761b87
--- /dev/null
+++ b/src/ui/ve.ui.ContextItemFactory.js
@@ -0,0 +1,46 @@
+/*!
+ * VisualEditor UserInterface ContextItemFactory class.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+/**
+ * Factory for context items.
+ *
+ * @class
+ * @extends OO.Factory
+ * @mixins ve.ui.ModeledFactory
+ *
+ * @constructor
+ */
+ve.ui.ContextItemFactory = function VeUiContextItemFactory() {
+ // Parent constructor
+ ve.ui.ContextItemFactory.super.call( this );
+
+ // Mixin constructors
+ ve.ui.ModeledFactory.call( this );
+};
+
+/* Inheritance */
+
+OO.inheritClass( ve.ui.ContextItemFactory, OO.Factory );
+OO.mixinClass( ve.ui.ContextItemFactory, ve.ui.ModeledFactory );
+
+/* Methods */
+
+/**
+ * Check if an item is embeddable.
+ *
+ * @param {string} name Symbolic item name
+ * @return {boolean} Item is embeddable
+ */
+ve.ui.ContextItemFactory.prototype.isEmbeddable = function ( name ) {
+ if ( Object.prototype.hasOwnProperty.call( this.registry, name ) ) {
+ return !!this.registry[name].static.embeddable;
+ }
+ throw new Error( 'Unrecognized symbolic name: ' + name );
+};
+
+/* Initialization */
+
+ve.ui.contextItemFactory = new ve.ui.ContextItemFactory();
diff --git a/src/ui/ve.ui.DesktopContext.js b/src/ui/ve.ui.DesktopContext.js
index 004b58e..6afee74 100644
--- a/src/ui/ve.ui.DesktopContext.js
+++ b/src/ui/ve.ui.DesktopContext.js
@@ -45,16 +45,9 @@
this.$element
.addClass( 've-ui-desktopContext' )
.append( this.popup.$element );
- this.menu.$element.addClass( 've-ui-desktopContext-menu' );
+ this.$group.addClass( 've-ui-desktopContext-menu' );
this.inspectors.$element.addClass( 've-ui-desktopContext-inspectors' );
- this.popup.$body.append( this.menu.$element, this.inspectors.$element );
-
- // HACK: hide the popup with visibility: hidden; rather than display:
none;, because
- // the popup contains inspector iframes, and applying display: none; to
those causes them to
- // not load in Firefox
- this.popup.$element
- .css( { visibility: 'hidden' } )
- .removeClass( 'oo-ui-element-hidden' );
+ this.popup.$body.append( this.$group, this.inspectors.$element );
};
/* Inheritance */
@@ -81,11 +74,10 @@
*/
ve.ui.DesktopContext.prototype.onSuppress = function () {
this.suppressed = true;
-
if ( this.isVisible() ) {
- if ( this.menu.isVisible() ) {
+ if ( !this.isEmpty() ) {
// Change state: menu -> closed
- this.menu.toggle( false );
+ this.toggleMenu( false );
this.toggle( false );
} else if ( this.inspector ) {
// Change state: inspector -> closed
@@ -98,14 +90,11 @@
* Handle context unsuppression event.
*/
ve.ui.DesktopContext.prototype.onUnsuppress = function () {
- var inspectable = !!this.getAvailableTools().length;
-
this.suppressed = false;
- if ( inspectable ) {
+ if ( this.isInspectable() ) {
// Change state: closed -> menu
- this.menu.toggle( true );
- this.populateMenu();
+ this.toggleMenu( true );
this.toggle( true );
}
};
@@ -166,19 +155,13 @@
return $.Deferred().resolve().promise();
}
- this.visible = show;
this.transitioning = $.Deferred();
promise = this.transitioning.promise();
this.popup.toggle( show );
- // HACK: make the context and popup visibility: hidden; instead of
display: none; because
- // they contain inspector iframes, and applying display: none; to those
causes them to
- // not load in Firefox
- this.$element.add( this.popup.$element )
- .removeClass( 'oo-ui-element-hidden' )
- .css( {
- visibility: show ? 'visible' : 'hidden'
- } );
+
+ // Parent method
+ ve.ui.DesktopContext.super.prototype.toggle.call( this, show );
this.transitioning.resolve();
this.transitioning = null;
@@ -216,9 +199,9 @@
this.popup.toggleAnchor( true );
this.popup.align = 'center';
} else if ( focusedNode && !focusedNode.isContent() ) {
- embeddable = !this.hasInspector() &&
- boundingRect.height > this.menu.$element.outerHeight()
+ 5 &&
- boundingRect.width > this.menu.$element.outerWidth() +
10;
+ embeddable = this.isEmbeddable() &&
+ boundingRect.height > this.$group.outerHeight() + 5 &&
+ boundingRect.width > this.$group.outerWidth() + 10;
this.popup.toggleAnchor( !embeddable );
if ( embeddable ) {
// Embedded context position depends on directionality
@@ -278,7 +261,7 @@
* Resize the popup to match the size of its contents (menu or inspector).
*/
ve.ui.DesktopContext.prototype.setPopupSize = function () {
- var $container = this.inspector ? this.inspector.$frame :
this.menu.$element;
+ var $container = this.inspector ? this.inspector.$frame : this.$group;
// PopupWidget normally is clippable, suppress that to be able to
resize and scroll it into view.
// Needs to be repeated before every call, as it resets itself when the
popup is shown or hidden.
diff --git a/src/ui/ve.ui.MobileContext.js b/src/ui/ve.ui.MobileContext.js
index 3f9cbdf..d0bc494 100644
--- a/src/ui/ve.ui.MobileContext.js
+++ b/src/ui/ve.ui.MobileContext.js
@@ -28,11 +28,8 @@
} );
// Initialization
- this.$element
- .addClass( 've-ui-mobileContext' )
- .append( this.menu.$element );
- this.toggle( true );
- this.menu.$element.addClass( 've-ui-mobileContext-menu' );
+ this.$element.addClass( 've-ui-mobileContext' );
+ this.$group.addClass( 've-ui-mobileContext-menu' );
this.inspectors.$element.addClass( 've-ui-mobileContext-inspectors' );
this.surface.getGlobalOverlay().$element.append(
this.inspectors.$element );
};
@@ -40,6 +37,10 @@
/* Inheritance */
OO.inheritClass( ve.ui.MobileContext, ve.ui.Context );
+
+/* Static Properties */
+
+ve.ui.MobileContext.static.basicRendering = true;
/* Methods */
@@ -51,34 +52,4 @@
factory: ve.ui.windowFactory,
overlay: this.surface.getGlobalOverlay()
} );
-};
-
-/**
- * @inheritdoc
- */
-ve.ui.MobileContext.prototype.createItem = function ( tool ) {
- return new ve.ui.MobileContextOptionWidget(
- tool.tool, tool.model, { $: this.$, data: tool.tool.static.name
}
- );
-};
-
-/**
- * @inheritdoc
- */
-ve.ui.MobileContext.prototype.toggle = function ( show ) {
- var deferred = $.Deferred();
-
- show = show === undefined ? !this.visible : !!show;
- if ( show !== this.visible ) {
- this.visible = show;
- this.$element
- .toggleClass( 'oo-ui-element-hidden', !show );
- setTimeout( function () {
- deferred.resolve();
- }, 300 );
- } else {
- deferred.resolve();
- }
-
- return deferred.promise();
};
diff --git a/src/ui/ve.ui.ModeledFactory.js b/src/ui/ve.ui.ModeledFactory.js
new file mode 100644
index 0000000..03773b6
--- /dev/null
+++ b/src/ui/ve.ui.ModeledFactory.js
@@ -0,0 +1,89 @@
+/*!
+ * VisualEditor UserInterface ModeledFactory class.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
+ */
+
+/**
+ * Mixin for factories whose items associate with specific models.
+ *
+ * Classes registered with the factory should have a static method named
`isCompatibleWith` that
+ * accepts a model and returns a boolean.
+ *
+ * @class
+ *
+ * @constructor
+ */
+ve.ui.ModeledFactory = function VeUiModeledFactory() {};
+
+/* Inheritance */
+
+OO.initClass( ve.ui.ModeledFactory );
+
+/* Methods */
+
+/**
+ * Get a list of symbolic names for classes related to a list of models.
+ *
+ * The lowest compatible item in each inheritance chain will be used.
+ *
+ * @param {Object[]} models Models to find relationships with
+ * @returns {Object[]} List of objects containing `name` and `model`
properties, representing
+ * each compatible class's symbolic name and the model it is compatible with
+ */
+ve.ui.ModeledFactory.prototype.getRelatedItems = function ( models ) {
+ var i, iLen, j, jLen, name, classes, model,
+ registry = this.registry,
+ names = {},
+ matches = [];
+
+ /**
+ * Collect the most specific compatible classes for a model.
+ *
+ * @private
+ * @param {Object} model Model to find compatability with
+ * @returns {Function[]} List of compatible classes
+ */
+ function collect( model ) {
+ var i, len, name, candidate, add,
+ candidates = [];
+
+ for ( name in registry ) {
+ candidate = registry[name];
+ if ( candidate.static.isCompatibleWith( model ) ) {
+ add = true;
+ for ( i = 0, len = candidates.length; i < len;
i++ ) {
+ if ( candidate.prototype instanceof
candidates[i] ) {
+ candidates.splice( i, 1,
candidate );
+ add = false;
+ break;
+ } else if ( candidates[i].prototype
instanceof candidate ) {
+ add = false;
+ break;
+ }
+ }
+ if ( add ) {
+ candidates.push( candidate );
+ }
+ }
+ }
+
+ return candidates;
+ }
+
+ // Collect compatible classes and the models they are specifically
compatible with,
+ // discarding class's with duplicate symbolic names
+ for ( i = 0, iLen = models.length; i < iLen; i++ ) {
+ model = models[i];
+ classes = collect( model );
+ for ( j = 0, jLen = classes.length; j < jLen; j++ ) {
+ name = classes[j].static.name;
+ if ( !names[name] ) {
+ matches.push( { name: name, model: model } );
+ }
+ names[name] = true;
+ }
+ }
+
+ return matches;
+};
diff --git a/src/ui/ve.ui.ToolFactory.js b/src/ui/ve.ui.ToolFactory.js
index dd00a43..71c345a 100644
--- a/src/ui/ve.ui.ToolFactory.js
+++ b/src/ui/ve.ui.ToolFactory.js
@@ -9,84 +9,22 @@
*
* @class
* @extends OO.ui.ToolFactory
+ * @mixins ve.ui.ModeledFactory
*
* @constructor
*/
-ve.ui.ToolFactory = function OoUiToolFactory() {
+ve.ui.ToolFactory = function VeUiToolFactory() {
// Parent constructor
- OO.ui.ToolFactory.call( this );
+ ve.ui.ToolFactory.super.call( this );
+
+ // Mixin constructors
+ ve.ui.ModeledFactory.call( this );
};
/* Inheritance */
OO.inheritClass( ve.ui.ToolFactory, OO.ui.ToolFactory );
-
-/* Methods */
-
-/**
- * Get a list of tools for a fragment.
- *
- * The lowest compatible item in each inheritance chain will be used.
- *
- * @method
- * @param {ve.dm.SurfaceFragment} fragment Fragment to find compatible tools
for
- * @returns {Object[]} List of objects containing `tool` and `model`
properties, representing each
- * compatible tool and the node or annotation it is compatible with
- */
-ve.ui.ToolFactory.prototype.getToolsForFragment = function ( fragment ) {
- var i, iLen, j, jLen, name, tools, model,
- models = fragment.getSelectedModels(),
- names = {},
- matches = [];
-
- // Collect tool/model pairs, unique by tool name
- for ( i = 0, iLen = models.length; i < iLen; i++ ) {
- model = models[i];
- tools = this.collectCompatibleTools( model );
- for ( j = 0, jLen = tools.length; j < jLen; j++ ) {
- name = tools[j].static.name;
- if ( !names[name] ) {
- matches.push( { tool: tools[j], model: model }
);
- }
- names[name] = true;
- }
- }
-
- return matches;
-};
-
-/**
- * Collect the most specific compatible tools for an annotation or node.
- *
- * @param {ve.dm.Annotation|ve.dm.Node} model Annotation or node
- * @returns {Function[]} List of compatible tools
- */
-ve.ui.ToolFactory.prototype.collectCompatibleTools = function ( model ) {
- var i, len, name, candidate, add,
- candidates = [];
-
- for ( name in this.registry ) {
- candidate = this.registry[name];
- if ( candidate.static.isCompatibleWith( model ) ) {
- add = true;
- for ( i = 0, len = candidates.length; i < len; i++ ) {
- if ( candidate.prototype instanceof
candidates[i] ) {
- candidates.splice( i, 1, candidate );
- add = false;
- break;
- } else if ( candidates[i].prototype instanceof
candidate ) {
- add = false;
- break;
- }
- }
- if ( add ) {
- candidates.push( candidate );
- }
- }
- }
-
- return candidates;
-};
+OO.mixinClass( ve.ui.ToolFactory, ve.ui.ModeledFactory );
/* Initialization */
diff --git a/src/ui/ve.ui.js b/src/ui/ve.ui.js
index 26bfad1..d06b4c3 100644
--- a/src/ui/ve.ui.js
+++ b/src/ui/ve.ui.js
@@ -15,6 +15,7 @@
// 'commandRegistry' instantiated in ve.ui.CommandRegistry.js
// 'triggerRegistry' instantiated in ve.ui.TriggerRegistry.js
// 'toolFactory' instantiated in ve.ui.ToolFactory.js
+ // 'contextItemFactory' instantiated in ve.ui.ContextItemFactory.js
// 'dataTransferHandlerFactory' instantiated in
ve.ui.DataTransferHandlerFactory.js
windowFactory: new OO.Factory()
};
diff --git a/src/ui/widgets/ve.ui.MobileContextOptionWidget.js
b/src/ui/widgets/ve.ui.MobileContextOptionWidget.js
deleted file mode 100644
index 3b3eee5..0000000
--- a/src/ui/widgets/ve.ui.MobileContextOptionWidget.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/*!
- * VisualEditor Mobile Context Item widget class.
- *
- * @copyright 2011-2015 VisualEditor Team and others; see
http://ve.mit-license.org
- */
-
-/**
- * Mobile version of context item widget
- *
- * @class
- * @extends ve.ui.ContextOptionWidget
- *
- * @constructor
- * @param {Function} tool
- * @param {ve.dm.Node|ve.dm.Annotation} model
- * @param {Object} [config]
- */
-ve.ui.MobileContextOptionWidget = function VeUiContextOptionWidget() {
- // Parent constructor
- ve.ui.MobileContextOptionWidget.super.apply( this, arguments );
-
- this.$element.addClass( 've-ui-mobileContextOptionWidget' );
- this.setLabel(
- this.$( '<span>' ).addClass(
've-ui-mobileContextOptionWidget-label-secondary' )
- .text( ve.msg(
'visualeditor-contextitemwidget-label-secondary' ) )
- .add(
- this.$( '<span>' ).addClass(
've-ui-mobileContextOptionWidget-label-primary' )
- .text( this.getDescription() )
- )
- );
-};
-
-/* Setup */
-
-OO.inheritClass( ve.ui.MobileContextOptionWidget, ve.ui.ContextOptionWidget );
diff --git a/tests/index.html b/tests/index.html
index 563f554..8356c0e 100644
--- a/tests/index.html
+++ b/tests/index.html
@@ -249,6 +249,13 @@
<script src="../src/ui/ve.ui.Overlay.js"></script>
<script src="../src/ui/ve.ui.Surface.js"></script>
<script src="../src/ui/ve.ui.Context.js"></script>
+ <script src="../src/ui/ve.ui.ModeledFactory.js"></script>
+ <script src="../src/ui/ve.ui.ContextItem.js"></script>
+ <script src="../src/ui/ve.ui.ContextItemFactory.js"></script>
+ <script
src="../src/ui/contextitems/ve.ui.CommentContextItem.js"></script>
+ <script
src="../src/ui/contextitems/ve.ui.LanguageContextItem.js"></script>
+ <script
src="../src/ui/contextitems/ve.ui.LinkContextItem.js"></script>
+ <script
src="../src/ui/contextitems/ve.ui.ToolContextItem.js"></script>
<script src="../src/ui/ve.ui.TableContext.js"></script>
<script src="../src/ui/ve.ui.Tool.js"></script>
<script src="../src/ui/ve.ui.Toolbar.js"></script>
--
To view, visit https://gerrit.wikimedia.org/r/192738
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ibca8cf5cb8ee2f0a8f520461cef3af6fa6f2ac4d
Gerrit-PatchSet: 11
Gerrit-Project: VisualEditor/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Trevor Parscal <[email protected]>
Gerrit-Reviewer: Esanders <[email protected]>
Gerrit-Reviewer: Jforrester <[email protected]>
Gerrit-Reviewer: Krinkle <[email protected]>
Gerrit-Reviewer: Trevor Parscal <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits