From 042ffb4e3b15af881672db5044e1b196a2f2feb8 Mon Sep 17 00:00:00 2001 From: Trevor Parscal Date: Thu, 31 May 2012 16:50:16 -0700 Subject: [PATCH 1/5] Added foundation for new converter * Added converters to all relevant node implementations * Added new annotation objects with their own factory Change-Id: I9870d6d5eac45083929d74d2e58917d0939ca917 --- VisualEditor.php | 11 ++- demos/ve/index.php | 11 ++- modules/ve2/ce/ve.ce.NodeFactory.js | 6 +- .../dm/annotations/ve.dm.LinkAnnotation.js | 46 ++++++++++ .../dm/annotations/ve.dm.ObjectAnnotation.js | 43 ++++++++++ .../annotations/ve.dm.TextStyleAnnotation.js | 62 ++++++++++++++ modules/ve2/dm/nodes/ve.dm.AlienBlockNode.js | 3 + modules/ve2/dm/nodes/ve.dm.AlienInlineNode.js | 3 + .../dm/nodes/ve.dm.DefinitionListItemNode.js | 21 +++++ .../ve2/dm/nodes/ve.dm.DefinitionListNode.js | 27 ++++++ modules/ve2/dm/nodes/ve.dm.DocumentNode.js | 5 ++ modules/ve2/dm/nodes/ve.dm.HeadingNode.js | 35 ++++++++ modules/ve2/dm/nodes/ve.dm.ImageNode.js | 21 +++++ modules/ve2/dm/nodes/ve.dm.ListItemNode.js | 21 +++++ modules/ve2/dm/nodes/ve.dm.ListNode.js | 27 ++++++ modules/ve2/dm/nodes/ve.dm.ParagraphNode.js | 21 +++++ .../ve2/dm/nodes/ve.dm.PreformattedNode.js | 21 +++++ modules/ve2/dm/nodes/ve.dm.TableCellNode.js | 21 +++++ modules/ve2/dm/nodes/ve.dm.TableNode.js | 21 +++++ modules/ve2/dm/nodes/ve.dm.TableRowNode.js | 21 +++++ modules/ve2/dm/nodes/ve.dm.TextNode.js | 3 + modules/ve2/dm/ve.dm.Annotation.js | 8 ++ modules/ve2/dm/ve.dm.AnnotationFactory.js | 19 +++++ modules/ve2/dm/ve.dm.Converter.js | 83 +++++++++++++++++++ modules/ve2/dm/ve.dm.NodeFactory.js | 20 ++--- modules/ve2/dm/ve.dm.js | 12 +++ .../ve2/{ve.NodeFactory.js => ve.Factory.js} | 41 +++++---- tests/ve2/ce/ve.ce.NodeFactory.test.js | 4 +- tests/ve2/dm/ve.dm.BranchNode.test.js | 2 + tests/ve2/dm/ve.dm.LeafNode.test.js | 2 + tests/ve2/dm/ve.dm.Node.test.js | 2 + tests/ve2/dm/ve.dm.NodeFactory.test.js | 12 ++- tests/ve2/index.html | 13 ++- tests/ve2/ve.Factory.test.js | 44 ++++++++++ tests/ve2/ve.NodeFactory.test.js | 42 ---------- 35 files changed, 671 insertions(+), 83 deletions(-) create mode 100644 modules/ve2/dm/annotations/ve.dm.LinkAnnotation.js create mode 100644 modules/ve2/dm/annotations/ve.dm.ObjectAnnotation.js create mode 100644 modules/ve2/dm/annotations/ve.dm.TextStyleAnnotation.js create mode 100644 modules/ve2/dm/ve.dm.Annotation.js create mode 100644 modules/ve2/dm/ve.dm.AnnotationFactory.js create mode 100644 modules/ve2/dm/ve.dm.Converter.js rename modules/ve2/{ve.NodeFactory.js => ve.Factory.js} (59%) create mode 100644 tests/ve2/ve.Factory.test.js delete mode 100644 tests/ve2/ve.NodeFactory.test.js diff --git a/VisualEditor.php b/VisualEditor.php index 859ef761cc..c0647ba234 100644 --- a/VisualEditor.php +++ b/VisualEditor.php @@ -97,10 +97,10 @@ $wgResourceModules += array( // ve 'jquery/jquery.json.js', 've2/ve.js', - 've2/ve.NodeFactory.js', + 've2/ve.EventEmitter.js', + 've2/ve.Factory.js', 've2/ve.Position.js', 've2/ve.Range.js', - 've2/ve.EventEmitter.js', 've2/ve.Node.js', 've2/ve.BranchNode.js', 've2/ve.LeafNode.js', @@ -110,14 +110,17 @@ $wgResourceModules += array( // dm 've2/dm/ve.dm.js', 've2/dm/ve.dm.NodeFactory.js', + 've2/dm/ve.dm.AnnotationFactory.js', 've2/dm/ve.dm.Node.js', 've2/dm/ve.dm.BranchNode.js', 've2/dm/ve.dm.LeafNode.js', + 've2/dm/ve.dm.Annotation.js', 've2/dm/ve.dm.TransactionProcessor.js', 've2/dm/ve.dm.Transaction.js', 've2/dm/ve.dm.Surface.js', 've2/dm/ve.dm.Document.js', 've2/dm/ve.dm.DocumentSynchronizer.js', + 've2/dm/ve.dm.Converter.js', 've2/dm/ve.dm.HTMLConverter.js', 've2/dm/nodes/ve.dm.AlienInlineNode.js', @@ -136,6 +139,10 @@ $wgResourceModules += array( 've2/dm/nodes/ve.dm.TableRowNode.js', 've2/dm/nodes/ve.dm.TextNode.js', + 've2/dm/annotations/ve.dm.LinkAnnotation.js', + 've2/dm/annotations/ve.dm.ObjectAnnotation.js', + 've2/dm/annotations/ve.dm.TextStyleAnnotation.js', + 've/dm/serializers/ve.dm.AnnotationSerializer.js', 've/dm/serializers/ve.dm.HtmlSerializer.js', 've/dm/serializers/ve.dm.JsonSerializer.js', diff --git a/demos/ve/index.php b/demos/ve/index.php index a86df6c68f..a3a62ee881 100644 --- a/demos/ve/index.php +++ b/demos/ve/index.php @@ -89,10 +89,10 @@ include( '../../modules/sandbox/base.php' ); - + + - @@ -102,9 +102,11 @@ include( '../../modules/sandbox/base.php' ); + + @@ -112,6 +114,7 @@ include( '../../modules/sandbox/base.php' ); + @@ -130,6 +133,10 @@ include( '../../modules/sandbox/base.php' ); + + + + diff --git a/modules/ve2/ce/ve.ce.NodeFactory.js b/modules/ve2/ce/ve.ce.NodeFactory.js index 03df832c11..d458898873 100644 --- a/modules/ve2/ce/ve.ce.NodeFactory.js +++ b/modules/ve2/ce/ve.ce.NodeFactory.js @@ -2,12 +2,12 @@ * ContentEditable node factory. * * @class - * @extends {ve.NodeFactory} + * @extends {ve.Factory} * @constructor */ ve.ce.NodeFactory = function() { // Inheritance - ve.NodeFactory.call( this ); + ve.Factory.call( this ); }; /* Methods */ @@ -28,7 +28,7 @@ ve.ce.NodeFactory.prototype.canNodeBeSplit = function( type ) { /* Inheritance */ -ve.extendClass( ve.ce.NodeFactory, ve.NodeFactory ); +ve.extendClass( ve.ce.NodeFactory, ve.Factory ); /* Initialization */ diff --git a/modules/ve2/dm/annotations/ve.dm.LinkAnnotation.js b/modules/ve2/dm/annotations/ve.dm.LinkAnnotation.js new file mode 100644 index 0000000000..820870bb89 --- /dev/null +++ b/modules/ve2/dm/annotations/ve.dm.LinkAnnotation.js @@ -0,0 +1,46 @@ +/** + * DataModel annotation for a link. + * + * @class + * @constructor + * @extends {ve.dm.Annotation} + */ +ve.dm.LinkAnnotation = function() { + // Inheritance + ve.dm.Annotation.call( this ); +}; + +/* Static Members */ + +/** + * Converters. + * + * @see {ve.dm.Converter} + * @static + * @member + */ +ve.dm.LinkAnnotation.converters = { + 'tags': 'a', + 'html': { + 'convert': function( subType, annotation ) { + return annotation.type && + ve.dm.createHtmlElement( 'a', { 'data-type': 'link/' + subType } ); + } + }, + 'data': { + 'convert': function( tag, element ) { + // FIXME: the parser currently doesn't output this data this way + // Internal links get 'linkType': 'internal' in the data-mw-rt attrib, while external + // links currently get nothing + return { 'type': 'link/' + ( element.getAttribute( 'data-type' ) || 'unknown' ) }; + } + } +}; + +/* Registration */ + +ve.dm.annotationFactory.register( 'link', ve.dm.LinkAnnotation ); + +/* Inheritance */ + +ve.extendClass( ve.dm.LinkAnnotation, ve.dm.Annotation ); diff --git a/modules/ve2/dm/annotations/ve.dm.ObjectAnnotation.js b/modules/ve2/dm/annotations/ve.dm.ObjectAnnotation.js new file mode 100644 index 0000000000..4c15404830 --- /dev/null +++ b/modules/ve2/dm/annotations/ve.dm.ObjectAnnotation.js @@ -0,0 +1,43 @@ +/** + * DataModel annotation for a object. + * + * @class + * @constructor + * @extends {ve.dm.Annotation} + */ +ve.dm.ObjectAnnotation = function() { + // Inheritance + ve.dm.Annotation.call( this ); +}; + +/* Static Members */ + +/** + * Converters. + * + * @see {ve.dm.Converter} + * @static + * @member + */ +ve.dm.ObjectAnnotation.converters = { + 'tags': ['template', 'hook'], + 'html': { + 'convert': function( subType, annotation ) { + return annotation.type && + ve.dm.createHtmlElement( 'div', { 'data-type': subType } ); + } + }, + 'data': { + 'convert': function( tag, element ) { + return { 'type': 'object/' + ( element.getAttribute( 'data-type' ) || 'unknown' ) }; + } + } +}; + +/* Registration */ + +ve.dm.annotationFactory.register( 'object', ve.dm.ObjectAnnotation ); + +/* Inheritance */ + +ve.extendClass( ve.dm.ObjectAnnotation, ve.dm.Annotation ); diff --git a/modules/ve2/dm/annotations/ve.dm.TextStyleAnnotation.js b/modules/ve2/dm/annotations/ve.dm.TextStyleAnnotation.js new file mode 100644 index 0000000000..92de47ec8e --- /dev/null +++ b/modules/ve2/dm/annotations/ve.dm.TextStyleAnnotation.js @@ -0,0 +1,62 @@ +/** + * DataModel annotation for a text style. + * + * @class + * @constructor + * @extends {ve.dm.Annotation} + */ +ve.dm.TextStyleAnnotation = function() { + // Inheritance + ve.dm.Annotation.call( this ); +}; + +/* Static Members */ + +/** + * Converters. + * + * @see {ve.dm.Converter} + * @static + * @member + */ +ve.dm.TextStyleAnnotation.converters = { + 'tags': ['i', 'b', 'u', 's', 'small', 'big', 'span'], + 'html': { + 'convert': function( subType, annotation ) { + return annotation.type && ve.dm.createHtmlElement( ( { + 'italic': 'i', + 'bold': 'b', + 'underline': 'u', + 'strike': 's', + 'small': 'small', + 'big': 'big', + 'span': 'span' + // TODO: Add other supported HTML inline elements to this list + } )[subType] ); + } + }, + 'data': { + 'convert': function( tag, element ) { + return { + 'type': 'textStyle/' + ( { + 'i': 'italic', + 'b': 'bold', + 'u': 'underline', + 's': 'strike', + 'small': 'small', + 'big': 'big', + 'span': 'span' + // TODO: Add other supported HTML inline elements to this list + } )[tag] + }; + } + } +}; + +/* Registration */ + +ve.dm.annotationFactory.register( 'textStyle', ve.dm.TextStyleAnnotation ); + +/* Inheritance */ + +ve.extendClass( ve.dm.TextStyleAnnotation, ve.dm.Annotation ); diff --git a/modules/ve2/dm/nodes/ve.dm.AlienBlockNode.js b/modules/ve2/dm/nodes/ve.dm.AlienBlockNode.js index 122bd7fc8d..48305b6aef 100644 --- a/modules/ve2/dm/nodes/ve.dm.AlienBlockNode.js +++ b/modules/ve2/dm/nodes/ve.dm.AlienBlockNode.js @@ -29,6 +29,9 @@ ve.dm.AlienBlock.rules = { 'parentNodeTypes': null }; +// This is a special node, no converter registration is required +ve.dm.AlienBlock.converters = null; + /* Registration */ ve.dm.nodeFactory.register( 'alienBlock', ve.dm.AlienBlock ); diff --git a/modules/ve2/dm/nodes/ve.dm.AlienInlineNode.js b/modules/ve2/dm/nodes/ve.dm.AlienInlineNode.js index 60f0fa9117..3dd78b2f25 100644 --- a/modules/ve2/dm/nodes/ve.dm.AlienInlineNode.js +++ b/modules/ve2/dm/nodes/ve.dm.AlienInlineNode.js @@ -29,6 +29,9 @@ ve.dm.AlienInline.rules = { 'parentNodeTypes': null }; +// This is a special node, no converter registration is required +ve.dm.AlienInline.converters = null; + /* Registration */ ve.dm.nodeFactory.register( 'alienInline', ve.dm.AlienInline ); diff --git a/modules/ve2/dm/nodes/ve.dm.DefinitionListItemNode.js b/modules/ve2/dm/nodes/ve.dm.DefinitionListItemNode.js index 24f3bfae70..acc4acb5e0 100644 --- a/modules/ve2/dm/nodes/ve.dm.DefinitionListItemNode.js +++ b/modules/ve2/dm/nodes/ve.dm.DefinitionListItemNode.js @@ -29,6 +29,27 @@ ve.dm.DefinitionListItemNode.rules = { 'parentNodeTypes': ['definitionList'] }; +/** + * Node converters. + * + * @see {ve.dm.Converter} + * @static + * @member + */ +ve.dm.DefinitionListItemNode.converters = { + 'tags': 'dl', + 'html': { + 'convert': function( type, element ) { + return ve.dm.createHtmlElement( 'dl' ); + } + }, + 'data': { + 'convert': function( tag, element ) { + return { 'type': 'definitionList' }; + } + } +}; + /* Registration */ ve.dm.nodeFactory.register( 'definitionListItem', ve.dm.DefinitionListItemNode ); diff --git a/modules/ve2/dm/nodes/ve.dm.DefinitionListNode.js b/modules/ve2/dm/nodes/ve.dm.DefinitionListNode.js index cffdaee786..4d5f8ea532 100644 --- a/modules/ve2/dm/nodes/ve.dm.DefinitionListNode.js +++ b/modules/ve2/dm/nodes/ve.dm.DefinitionListNode.js @@ -29,6 +29,33 @@ ve.dm.DefinitionListNode.rules = { 'parentNodeTypes': null }; +/** + * Node converters. + * + * @see {ve.dm.Converter} + * @static + * @member + */ +ve.dm.DefinitionListNode.converters = { + 'tags': ['dt', 'dd'], + 'html': { + 'convert': function( type, element ) { + return element.attributes && ( { + 'term': ve.dm.createHtmlElement( 'dt' ), + 'definition': ve.dm.createHtmlElement( 'dd' ) + } )[element.attributes['style']]; + } + }, + 'data': { + 'convert': function( tag, element ) { + return ( { + 'dt': { 'type': 'definitionList', 'attributes': { 'style': 'term' } }, + 'dd': { 'type': 'definitionList', 'attributes': { 'style': 'definition' } } + } )[tag]; + } + } +}; + /* Registration */ ve.dm.nodeFactory.register( 'definitionList', ve.dm.DefinitionListNode ); diff --git a/modules/ve2/dm/nodes/ve.dm.DocumentNode.js b/modules/ve2/dm/nodes/ve.dm.DocumentNode.js index 44bb49671e..8373b86389 100644 --- a/modules/ve2/dm/nodes/ve.dm.DocumentNode.js +++ b/modules/ve2/dm/nodes/ve.dm.DocumentNode.js @@ -29,10 +29,15 @@ ve.dm.DocumentNode.rules = { 'parentNodeTypes': [] }; +// This is a special node, no converter registration is required +ve.dm.DocumentNode.converters = null; + /* Registration */ ve.dm.nodeFactory.register( 'document', ve.dm.DocumentNode ); +// This is a special node, no converter registration is required + /* Inheritance */ ve.extendClass( ve.dm.DocumentNode, ve.dm.BranchNode ); diff --git a/modules/ve2/dm/nodes/ve.dm.HeadingNode.js b/modules/ve2/dm/nodes/ve.dm.HeadingNode.js index 78bfa87abb..34398bd263 100644 --- a/modules/ve2/dm/nodes/ve.dm.HeadingNode.js +++ b/modules/ve2/dm/nodes/ve.dm.HeadingNode.js @@ -29,6 +29,41 @@ ve.dm.HeadingNode.rules = { 'parentNodeTypes': null }; +/** + * Node converters. + * + * @see {ve.dm.Converter} + * @static + * @member + */ +ve.dm.HeadingNode.converters = { + 'tags': ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], + 'html': { + 'convert': function( type, element ) { + return element.attributes && ( { + 1: ve.dm.createHtmlElement( 'h1' ), + 2: ve.dm.createHtmlElement( 'h2' ), + 3: ve.dm.createHtmlElement( 'h3' ), + 4: ve.dm.createHtmlElement( 'h4' ), + 5: ve.dm.createHtmlElement( 'h5' ), + 6: ve.dm.createHtmlElement( 'h6' ) + } )[element.attributes['level']]; + } + }, + 'data': { + 'convert': function( tag, element ) { + return ( { + 'h1': { 'type': 'heading', 'attributes': { 'level': 1 } }, + 'h2': { 'type': 'heading', 'attributes': { 'level': 2 } }, + 'h3': { 'type': 'heading', 'attributes': { 'level': 3 } }, + 'h4': { 'type': 'heading', 'attributes': { 'level': 4 } }, + 'h5': { 'type': 'heading', 'attributes': { 'level': 5 } }, + 'h6': { 'type': 'heading', 'attributes': { 'level': 6 } } + } )[tag]; + } + } +}; + /* Registration */ ve.dm.nodeFactory.register( 'heading', ve.dm.HeadingNode ); diff --git a/modules/ve2/dm/nodes/ve.dm.ImageNode.js b/modules/ve2/dm/nodes/ve.dm.ImageNode.js index 92bd71c22a..c488192b8c 100644 --- a/modules/ve2/dm/nodes/ve.dm.ImageNode.js +++ b/modules/ve2/dm/nodes/ve.dm.ImageNode.js @@ -29,6 +29,27 @@ ve.dm.ImageNode.rules = { 'parentNodeTypes': null }; +/** + * Node converters. + * + * @see {ve.dm.Converter} + * @static + * @member + */ +ve.dm.ImageNode.converters = { + 'tags': 'img', + 'html': { + 'convert': function( type, element ) { + return ve.dm.createHtmlElement( 'img' ); + } + }, + 'data': { + 'convert': function( tag, element ) { + return { 'type': 'image' }; + } + } +}; + /* Registration */ ve.dm.nodeFactory.register( 'image', ve.dm.ImageNode ); diff --git a/modules/ve2/dm/nodes/ve.dm.ListItemNode.js b/modules/ve2/dm/nodes/ve.dm.ListItemNode.js index be3d705176..7ab09d7c35 100644 --- a/modules/ve2/dm/nodes/ve.dm.ListItemNode.js +++ b/modules/ve2/dm/nodes/ve.dm.ListItemNode.js @@ -29,6 +29,27 @@ ve.dm.ListItemNode.rules = { 'parentNodeTypes': ['list'] }; +/** + * Node converters. + * + * @see {ve.dm.Converter} + * @static + * @member + */ +ve.dm.ListItemNode.converters = { + 'tags': 'li', + 'html': { + 'convert': function( type, element ) { + return ve.dm.createHtmlElement( 'li' ); + } + }, + 'data': { + 'convert': function( tag, element ) { + return { 'type': 'listItem' }; + } + } +}; + /* Registration */ ve.dm.nodeFactory.register( 'listItem', ve.dm.ListItemNode ); diff --git a/modules/ve2/dm/nodes/ve.dm.ListNode.js b/modules/ve2/dm/nodes/ve.dm.ListNode.js index bb54be834a..3f12736b1c 100644 --- a/modules/ve2/dm/nodes/ve.dm.ListNode.js +++ b/modules/ve2/dm/nodes/ve.dm.ListNode.js @@ -29,6 +29,33 @@ ve.dm.ListNode.rules = { 'parentNodeTypes': null }; +/** + * Node converters. + * + * @see {ve.dm.Converter} + * @static + * @member + */ +ve.dm.ListNode.converters = { + 'tags': ['ul', 'ol'], + 'html': { + 'convert': function( type, element ) { + return element.attributes && ( { + 'bullet': ve.dm.createHtmlElement( 'ul' ), + 'number': ve.dm.createHtmlElement( 'ol' ) + } )[element.attributes['style']]; + } + }, + 'data': { + 'convert': function( tag, element ) { + return ( { + 'ul': { 'type': 'list', 'attributes': { 'style': 'bullet' } }, + 'ol': { 'type': 'list', 'attributes': { 'style': 'number' } } + } )[tag]; + } + } +}; + /* Registration */ ve.dm.nodeFactory.register( 'list', ve.dm.ListNode ); diff --git a/modules/ve2/dm/nodes/ve.dm.ParagraphNode.js b/modules/ve2/dm/nodes/ve.dm.ParagraphNode.js index ece151f76c..994b9c4e82 100644 --- a/modules/ve2/dm/nodes/ve.dm.ParagraphNode.js +++ b/modules/ve2/dm/nodes/ve.dm.ParagraphNode.js @@ -29,6 +29,27 @@ ve.dm.ParagraphNode.rules = { 'parentNodeTypes': null }; +/** + * Node converters. + * + * @see {ve.dm.Converter} + * @static + * @member + */ +ve.dm.ParagraphNode.converters = { + 'tags': 'p', + 'html': { + 'convert': function( type, element ) { + return ve.dm.createHtmlElement( 'p' ); + } + }, + 'data': { + 'convert': function( tag, element ) { + return { 'type': 'paragraph' }; + } + } +}; + /* Registration */ ve.dm.nodeFactory.register( 'paragraph', ve.dm.ParagraphNode ); diff --git a/modules/ve2/dm/nodes/ve.dm.PreformattedNode.js b/modules/ve2/dm/nodes/ve.dm.PreformattedNode.js index ce57a36060..8e9f8d3467 100644 --- a/modules/ve2/dm/nodes/ve.dm.PreformattedNode.js +++ b/modules/ve2/dm/nodes/ve.dm.PreformattedNode.js @@ -29,6 +29,27 @@ ve.dm.PreformattedNode.rules = { 'parentNodeTypes': null }; +/** + * Node converters. + * + * @see {ve.dm.Converter} + * @static + * @member + */ +ve.dm.PreformattedNode.converters = { + 'tags': 'pre', + 'html': { + 'convert': function( type, element ) { + return ve.dm.createHtmlElement( 'pre' ); + } + }, + 'data': { + 'convert': function( tag, element ) { + return { 'type': 'preformatted' }; + } + } +}; + /* Registration */ ve.dm.nodeFactory.register( 'preformatted', ve.dm.PreformattedNode ); diff --git a/modules/ve2/dm/nodes/ve.dm.TableCellNode.js b/modules/ve2/dm/nodes/ve.dm.TableCellNode.js index c574a8f2ad..cff64973d1 100644 --- a/modules/ve2/dm/nodes/ve.dm.TableCellNode.js +++ b/modules/ve2/dm/nodes/ve.dm.TableCellNode.js @@ -29,6 +29,27 @@ ve.dm.TableCellNode.rules = { 'parentNodeTypes': ['tableRow'] }; +/** + * Node converters. + * + * @see {ve.dm.Converter} + * @static + * @member + */ +ve.dm.TableCellNode.converters = { + 'tags': 'td', + 'html': { + 'convert': function( type, element ) { + return ve.dm.createHtmlElement( 'td' ); + } + }, + 'data': { + 'convert': function( tag, element ) { + return { 'type': 'tableCell' }; + } + } +}; + /* Registration */ ve.dm.nodeFactory.register( 'tableCell', ve.dm.TableCellNode ); diff --git a/modules/ve2/dm/nodes/ve.dm.TableNode.js b/modules/ve2/dm/nodes/ve.dm.TableNode.js index 7c5a844eab..9971679a97 100644 --- a/modules/ve2/dm/nodes/ve.dm.TableNode.js +++ b/modules/ve2/dm/nodes/ve.dm.TableNode.js @@ -29,6 +29,27 @@ ve.dm.TableNode.rules = { 'parentNodeTypes': null }; +/** + * Node converters. + * + * @see {ve.dm.Converter} + * @static + * @member + */ +ve.dm.TableNode.converters = { + 'tags': 'table', + 'html': { + 'convert': function( type, element ) { + return ve.dm.createHtmlElement( 'table' ); + } + }, + 'data': { + 'convert': function( tag, element ) { + return { 'type': 'table' }; + } + } +}; + /* Registration */ ve.dm.nodeFactory.register( 'table', ve.dm.TableNode ); diff --git a/modules/ve2/dm/nodes/ve.dm.TableRowNode.js b/modules/ve2/dm/nodes/ve.dm.TableRowNode.js index e365ea9b5b..357f0c2459 100644 --- a/modules/ve2/dm/nodes/ve.dm.TableRowNode.js +++ b/modules/ve2/dm/nodes/ve.dm.TableRowNode.js @@ -29,6 +29,27 @@ ve.dm.TableRowNode.rules = { 'parentNodeTypes': ['table'] }; +/** + * Node converters. + * + * @see {ve.dm.Converter} + * @static + * @member + */ +ve.dm.TableRowNode.converters = { + 'tags': 'tr', + 'html': { + 'convert': function( type, element ) { + return ve.dm.createHtmlElement( 'tr' ); + } + }, + 'data': { + 'convert': function( tag, element ) { + return { 'type': 'tableRow' }; + } + } +}; + /* Registration */ ve.dm.nodeFactory.register( 'tableRow', ve.dm.TableRowNode ); diff --git a/modules/ve2/dm/nodes/ve.dm.TextNode.js b/modules/ve2/dm/nodes/ve.dm.TextNode.js index 053d60a787..29b1eb0ec3 100644 --- a/modules/ve2/dm/nodes/ve.dm.TextNode.js +++ b/modules/ve2/dm/nodes/ve.dm.TextNode.js @@ -28,6 +28,9 @@ ve.dm.TextNode.rules = { 'parentNodeTypes': null }; +// This is a special node, no converter registration is required +ve.dm.TextNode.converters = null; + /* Registration */ ve.dm.nodeFactory.register( 'text', ve.dm.TextNode ); diff --git a/modules/ve2/dm/ve.dm.Annotation.js b/modules/ve2/dm/ve.dm.Annotation.js new file mode 100644 index 0000000000..e2103c7bb8 --- /dev/null +++ b/modules/ve2/dm/ve.dm.Annotation.js @@ -0,0 +1,8 @@ +/** + * Generic DataModel annotation. + * + * @class + * @constructor + */ +ve.dm.Annotation = function() { +}; diff --git a/modules/ve2/dm/ve.dm.AnnotationFactory.js b/modules/ve2/dm/ve.dm.AnnotationFactory.js new file mode 100644 index 0000000000..d9104f3eb4 --- /dev/null +++ b/modules/ve2/dm/ve.dm.AnnotationFactory.js @@ -0,0 +1,19 @@ +/** + * DataModel annotation factory. + * + * @class + * @extends {ve.Factory} + * @constructor + */ +ve.dm.AnnotationFactory = function() { + // Inheritance + ve.Factory.call( this ); +}; + +/* Inheritance */ + +ve.extendClass( ve.dm.AnnotationFactory, ve.Factory ); + +/* Initialization */ + +ve.dm.annotationFactory = new ve.dm.AnnotationFactory(); diff --git a/modules/ve2/dm/ve.dm.Converter.js b/modules/ve2/dm/ve.dm.Converter.js new file mode 100644 index 0000000000..da43181394 --- /dev/null +++ b/modules/ve2/dm/ve.dm.Converter.js @@ -0,0 +1,83 @@ +/** + * Converter between HTML DOM and VisualEditor linear data. + * + * @class + * @constructor + * @param {Object} options Conversion options + */ +ve.dm.Converter = function( nodeFactory, annotationFactory ) { + // Properties + this.nodeFactory = nodeFactory; + this.annotationFactory = annotationFactory; + this.elements = { 'html': {}, 'data': {}, 'types': {} }; + this.annotations = { 'html': {}, 'data': {} }; + + // Events + this.nodeFactory.addListenerMethod( this, 'register', 'onNodeRegister' ); + this.annotationFactory.addListenerMethod( this, 'register', 'onAnnotationRegister' ); +}; + +/* Methods */ + +/** + * Responds to register events from the node factory. + * + * If a node is special; such as document, alienInline, alienBlock and text; it's converters data + * should be set to null, as to distinguish it from a new node type that someone has simply + * forgotten to implement converters for. + * + * @method + * @param {String} type Node type + * @param {Function} constructor Node constructor + * @throws 'Missing conversion data in node implementation of {type}' + */ +ve.dm.Converter.prototype.onNodeRegister = function( type, constructor ) { + if ( constructor.converters === undefined ) { + throw 'Missing conversion data in node implementation of ' + type; + } else if ( constructor.converters !== null ) { + var tags = constructor.converters.tags, + html = constructor.converters.html, + data = constructor.converters.data; + // Convert tags to an array if needed + if ( !ve.isArray( tags ) ) { + tags = [tags]; + } + // Registration + this.elements.html[type] = html.convert; + for ( var i = 0; i < tags.length; i++ ) { + this.elements.data[tags[i]] = data.convert; + this.elements.types[tags[i]] = type; + } + } +}; + +/** + * Responds to register events from the annotation factory. + * + * @method + * @param {String} type Base annotation type + * @param {Function} constructor Annotation constructor + * @throws 'Missing conversion data in annotation implementation of {type}' + */ +ve.dm.Converter.prototype.onAnnotationRegister = function( type, constructor ) { + if ( constructor.converters === undefined ) { + throw 'Missing conversion data in annotation implementation of ' + type; + } else if ( constructor.converters !== null ) { + var tags = constructor.converters.tags, + html = constructor.converters.html, + data = constructor.converters.data; + // Convert tags to an array if needed + if ( !ve.isArray( tags ) ) { + tags = [tags]; + } + // Registration + this.annotations.html[type] = html.convert; + for ( var i = 0; i < tags.length; i++ ) { + this.annotations.data[tags[i]] = data.convert; + } + } +}; + +/* Initialization */ + +ve.dm.converter = new ve.dm.Converter( ve.dm.nodeFactory, ve.dm.annotationFactory ); diff --git a/modules/ve2/dm/ve.dm.NodeFactory.js b/modules/ve2/dm/ve.dm.NodeFactory.js index cb230fee6f..e0f61cff82 100644 --- a/modules/ve2/dm/ve.dm.NodeFactory.js +++ b/modules/ve2/dm/ve.dm.NodeFactory.js @@ -2,12 +2,12 @@ * DataModel node factory. * * @class - * @extends {ve.NodeFactory} + * @extends {ve.Factory} * @constructor */ ve.dm.NodeFactory = function() { // Inheritance - ve.NodeFactory.call( this ); + ve.Factory.call( this ); }; /* Methods */ @@ -18,7 +18,7 @@ ve.dm.NodeFactory = function() { * @method * @param {String} type Node type * @returns {String[]|null} List of node types allowed as children or null if any type is allowed - * @throws 'Unknown node type' + * @throws 'Unknown node type: {type}' */ ve.dm.NodeFactory.prototype.getChildNodeTypes = function( type ) { if ( type in this.registry ) { @@ -33,7 +33,7 @@ ve.dm.NodeFactory.prototype.getChildNodeTypes = function( type ) { * @method * @param {String} type Node type * @returns {String[]|null} List of node types allowed as parents or null if any type is allowed - * @throws 'Unknown node type' + * @throws 'Unknown node type: {type}' */ ve.dm.NodeFactory.prototype.getParentNodeTypes = function( type ) { if ( type in this.registry ) { @@ -48,7 +48,7 @@ ve.dm.NodeFactory.prototype.getParentNodeTypes = function( type ) { * @method * @param {String} type Node type * @returns {Boolean} The node can have children - * @throws 'Unknown node type' + * @throws 'Unknown node type: {type}' */ ve.dm.NodeFactory.prototype.canNodeHaveChildren = function( type ) { if ( type in this.registry ) { @@ -66,7 +66,7 @@ ve.dm.NodeFactory.prototype.canNodeHaveChildren = function( type ) { * @method * @param {String} type Node type * @returns {Boolean} The node can have grandchildren - * @throws 'Unknown node type' + * @throws 'Unknown node type: {type}' */ ve.dm.NodeFactory.prototype.canNodeHaveGrandchildren = function( type ) { if ( type in this.registry ) { @@ -83,7 +83,7 @@ ve.dm.NodeFactory.prototype.canNodeHaveGrandchildren = function( type ) { * @method * @param {String} type Node type * @returns {Boolean} Whether the node has a wrapping element - * @throws 'Unknown node type' + * @throws 'Unknown node type: {type}' */ ve.dm.NodeFactory.prototype.isNodeWrapped = function( type ) { if ( type in this.registry ) { @@ -98,7 +98,7 @@ ve.dm.NodeFactory.prototype.isNodeWrapped = function( type ) { * @method * @param {String} type Node type * @returns {Boolean} The node contains content - * @throws 'Unknown node type' + * @throws 'Unknown node type: {type}' */ ve.dm.NodeFactory.prototype.canNodeContainContent = function( type ) { if ( type in this.registry ) { @@ -113,7 +113,7 @@ ve.dm.NodeFactory.prototype.canNodeContainContent = function( type ) { * @method * @param {String} type Node type * @returns {Boolean} The node is content - * @throws 'Unknown node type' + * @throws 'Unknown node type: {type}' */ ve.dm.NodeFactory.prototype.isNodeContent = function( type ) { if ( type in this.registry ) { @@ -124,7 +124,7 @@ ve.dm.NodeFactory.prototype.isNodeContent = function( type ) { /* Inheritance */ -ve.extendClass( ve.dm.NodeFactory, ve.NodeFactory ); +ve.extendClass( ve.dm.NodeFactory, ve.Factory ); /* Initialization */ diff --git a/modules/ve2/dm/ve.dm.js b/modules/ve2/dm/ve.dm.js index 26f2b5277c..0a26b4d57a 100644 --- a/modules/ve2/dm/ve.dm.js +++ b/modules/ve2/dm/ve.dm.js @@ -5,4 +5,16 @@ */ ve.dm = { //'nodeFactory': Initialized in ve.dm.NodeFactory.js + //'converter': Initialized in ve.dm.Converter.js +}; + +ve.dm.createHtmlElement = function( type, attributes, doc ) { + if ( doc === undefined ) { + doc = document; + } + var element = doc.createElement( type ); + for ( var key in attributes ) { + element.setAttribute( key, attributes[key] ); + } + return element; }; diff --git a/modules/ve2/ve.NodeFactory.js b/modules/ve2/ve.Factory.js similarity index 59% rename from modules/ve2/ve.NodeFactory.js rename to modules/ve2/ve.Factory.js index fb8d5963e5..10c263b8ff 100644 --- a/modules/ve2/ve.NodeFactory.js +++ b/modules/ve2/ve.Factory.js @@ -1,35 +1,42 @@ /** - * Generic node factory. + * Generic object factory. * * @class + * @abstract * @constructor + * @extends {ve.EventEmitter} */ -ve.NodeFactory = function() { +ve.Factory = function() { + // Inheritance + ve.EventEmitter.call( this ); + + // Properties this.registry = []; }; /* Methods */ /** - * Register a node type with the factory. + * Register a constructor with the factory. * * Arguments will be passed through directly to the constructor. - * @see {ve.NodeFactory.prototype.create} + * @see {ve.Factory.prototype.create} * * @method - * @param {String} type Node type - * @param {Function} constructor Node constructor subclassing ve.Node + * @param {String} type Object type + * @param {Function} constructor Constructor to use when creating object * @throws 'Constructor must be a function, cannot be a string' */ -ve.NodeFactory.prototype.register = function( type, constructor ) { +ve.Factory.prototype.register = function( type, constructor ) { if ( typeof constructor !== 'function' ) { throw 'Constructor must be a function, cannot be a ' + typeof constructor; } this.registry[type] = constructor; + this.emit( 'register', type, constructor ); }; /** - * Create a node based on a type. + * Create an object based on a type. * * Type is used to look up the constructor to use, while all additional arguments are passed to the * constructor directly, so leaving one out will pass an undefined to the constructor. @@ -40,25 +47,29 @@ ve.NodeFactory.prototype.register = function( type, constructor ) { * in adding more. * * @method - * @param {String} type Node type + * @param {String} type Object type * @param {Mixed} [...] Up to 2 additional arguments to pass through to the constructor - * @returns {ve.Node} The new node object - * @throws 'Unknown node type' + * @returns {Object} The new object + * @throws 'Unknown object type' */ -ve.NodeFactory.prototype.create = function( type, a, b ) { +ve.Factory.prototype.create = function( type, a, b ) { if ( type in this.registry ) { return new this.registry[type]( a, b ); } - throw 'Unknown node type: ' + type; + throw 'Unknown object type: ' + type; }; /** * Gets a constructor for a given type. * * @method - * @param {String} type Node type + * @param {String} type Object type * @returns {Function|undefined} Constructor for type */ -ve.NodeFactory.prototype.lookup = function( type ) { +ve.Factory.prototype.lookup = function( type ) { return this.registry[type]; }; + +/* Inheritance */ + +ve.extendClass( ve.Factory, ve.EventEmitter ); diff --git a/tests/ve2/ce/ve.ce.NodeFactory.test.js b/tests/ve2/ce/ve.ce.NodeFactory.test.js index b1dd0629e4..03497d8217 100644 --- a/tests/ve2/ce/ve.ce.NodeFactory.test.js +++ b/tests/ve2/ce/ve.ce.NodeFactory.test.js @@ -16,10 +16,10 @@ ve.ce.NodeFactoryNodeStub.rules = { test( 'canNodeBeSplit', 2, function() { var factory = new ve.ce.NodeFactory(); raises( function() { - factory.create( 'node-factory-node-stub', 23, { 'bar': 'baz' } ); + factory.canNodeBeSplit( 'node-factory-node-stub' ); }, /^Unknown node type: node-factory-node-stub$/, - 'throws an exception when getting allowed child nodes of a node of an unregistered type' + 'throws an exception when getting split rules for a node of an unregistered type' ); factory.register( 'node-factory-node-stub', ve.ce.NodeFactoryNodeStub ); strictEqual( diff --git a/tests/ve2/dm/ve.dm.BranchNode.test.js b/tests/ve2/dm/ve.dm.BranchNode.test.js index e7992b2aa7..58d3641a09 100644 --- a/tests/ve2/dm/ve.dm.BranchNode.test.js +++ b/tests/ve2/dm/ve.dm.BranchNode.test.js @@ -14,6 +14,8 @@ ve.dm.BranchNodeStub.rules = { 'childNodeTypes': null }; +ve.dm.BranchNodeStub.converters = null; + ve.extendClass( ve.dm.BranchNodeStub, ve.dm.BranchNode ); ve.dm.nodeFactory.register( 'branch-stub', ve.dm.BranchNodeStub ); diff --git a/tests/ve2/dm/ve.dm.LeafNode.test.js b/tests/ve2/dm/ve.dm.LeafNode.test.js index ae29cf8ddd..15d98e43ab 100644 --- a/tests/ve2/dm/ve.dm.LeafNode.test.js +++ b/tests/ve2/dm/ve.dm.LeafNode.test.js @@ -14,6 +14,8 @@ ve.dm.LeafNodeStub.rules = { 'childNodeTypes': [] }; +ve.dm.LeafNodeStub.converters = null; + ve.extendClass( ve.dm.LeafNodeStub, ve.dm.LeafNode ); ve.dm.nodeFactory.register( 'leaf-stub', ve.dm.LeafNodeStub ); diff --git a/tests/ve2/dm/ve.dm.Node.test.js b/tests/ve2/dm/ve.dm.Node.test.js index 7af12764bc..a2843afdca 100644 --- a/tests/ve2/dm/ve.dm.Node.test.js +++ b/tests/ve2/dm/ve.dm.Node.test.js @@ -14,6 +14,8 @@ ve.dm.NodeStub.rules = { 'childNodeTypes': [] }; +ve.dm.NodeStub.converters = null; + ve.extendClass( ve.dm.NodeStub, ve.dm.Node ); ve.dm.nodeFactory.register( 'stub', ve.dm.NodeStub ); diff --git a/tests/ve2/dm/ve.dm.NodeFactory.test.js b/tests/ve2/dm/ve.dm.NodeFactory.test.js index 2b2755f359..a6fd3489bd 100644 --- a/tests/ve2/dm/ve.dm.NodeFactory.test.js +++ b/tests/ve2/dm/ve.dm.NodeFactory.test.js @@ -15,12 +15,16 @@ ve.dm.NodeFactoryNodeStub.rules = { 'parentNodeTypes': null }; +ve.dm.NodeFactoryNodeStub.converters = null; + +ve.dm.NodeFactoryNodeStub.converters = null; + /* Tests */ test( 'getChildNodeTypes', 2, function() { var factory = new ve.dm.NodeFactory(); raises( function() { - factory.create( 'node-factory-node-stub', 23, { 'bar': 'baz' } ); + factory.getChildNodeTypes( 'node-factory-node-stub', 23, { 'bar': 'baz' } ); }, /^Unknown node type: node-factory-node-stub$/, 'throws an exception when getting allowed child nodes of a node of an unregistered type' @@ -36,7 +40,7 @@ test( 'getChildNodeTypes', 2, function() { test( 'getParentNodeTypes', 2, function() { var factory = new ve.dm.NodeFactory(); raises( function() { - factory.create( 'node-factory-node-stub', 23, { 'bar': 'baz' } ); + factory.getParentNodeTypes( 'node-factory-node-stub', 23, { 'bar': 'baz' } ); }, /^Unknown node type: node-factory-node-stub$/, 'throws an exception when getting allowed parent nodes of a node of an unregistered type' @@ -52,7 +56,7 @@ test( 'getParentNodeTypes', 2, function() { test( 'canNodeHaveChildren', 2, function() { var factory = new ve.dm.NodeFactory(); raises( function() { - factory.create( 'node-factory-node-stub', 23, { 'bar': 'baz' } ); + factory.canNodeHaveChildren( 'node-factory-node-stub', 23, { 'bar': 'baz' } ); }, /^Unknown node type: node-factory-node-stub$/, 'throws an exception when checking if a node of an unregistered type can have children' @@ -68,7 +72,7 @@ test( 'canNodeHaveChildren', 2, function() { test( 'canNodeHaveGrandchildren', 2, function() { var factory = new ve.dm.NodeFactory(); raises( function() { - factory.create( 'node-factory-node-stub', 23, { 'bar': 'baz' } ); + factory.canNodeHaveGrandchildren( 'node-factory-node-stub', 23, { 'bar': 'baz' } ); }, /^Unknown node type: node-factory-node-stub$/, 'throws an exception when checking if a node of an unregistered type can have grandchildren' diff --git a/tests/ve2/index.html b/tests/ve2/index.html index 88c900afc8..a6431db157 100644 --- a/tests/ve2/index.html +++ b/tests/ve2/index.html @@ -23,26 +23,33 @@ - + - + + + + + + + + @@ -91,7 +98,7 @@ - + diff --git a/tests/ve2/ve.Factory.test.js b/tests/ve2/ve.Factory.test.js new file mode 100644 index 0000000000..35810fcd85 --- /dev/null +++ b/tests/ve2/ve.Factory.test.js @@ -0,0 +1,44 @@ +module( 've.Factory' ); + +/* Stubs */ + +ve.FactoryObjectStub = function( a, b ) { + this.a = a; + this.b = b; +}; + +/* Tests */ + +test( 'register', 1, function() { + var factory = new ve.Factory(); + raises( + function() { + factory.register( 'factory-object-stub', 'not-a-function' ); + }, + /^Constructor must be a function, cannot be a string$/, + 'throws an exception when trying to register a non-function value as a constructor' + ); +} ); + +test( 'create', 2, function() { + var factory = new ve.Factory(); + raises( + function() { + factory.create( 'factory-object-stub', 23, { 'bar': 'baz' } ); + }, + /^Unknown object type: factory-object-stub$/, + 'throws an exception when trying to create a object of an unregistered type' + ); + factory.register( 'factory-object-stub', ve.FactoryObjectStub ); + deepEqual( + factory.create( 'factory-object-stub', 16, { 'baz': 'quux' } ), + new ve.FactoryObjectStub( 16, { 'baz': 'quux' } ), + 'creates objects of a registered type and passes through arguments' + ); +} ); + +test( 'lookup', 1, function() { + var factory = new ve.Factory(); + factory.register( 'factory-object-stub', ve.FactoryObjectStub ); + strictEqual( factory.lookup( 'factory-object-stub' ), ve.FactoryObjectStub ); +} ); diff --git a/tests/ve2/ve.NodeFactory.test.js b/tests/ve2/ve.NodeFactory.test.js deleted file mode 100644 index ff0a935e9e..0000000000 --- a/tests/ve2/ve.NodeFactory.test.js +++ /dev/null @@ -1,42 +0,0 @@ -module( 've.NodeFactory' ); - -/* Stubs */ - -ve.NodeFactoryNodeStub = function( a, b ) { - this.a = a; - this.b = b; -}; - -/* Tests */ - -test( 'register', 1, function() { - var factory = new ve.NodeFactory(); - raises( function() { - factory.register( 'node-factory-node-stub', 'not-a-function' ); - }, - /^Constructor must be a function, cannot be a string$/, - 'throws an exception when trying to register a non-function value as a constructor' - ); -} ); - -test( 'create', 2, function() { - var factory = new ve.NodeFactory(); - raises( function() { - factory.create( 'node-factory-node-stub', 23, { 'bar': 'baz' } ); - }, - /^Unknown node type: node-factory-node-stub$/, - 'throws an exception when trying to create a node of an unregistered type' - ); - factory.register( 'node-factory-node-stub', ve.NodeFactoryNodeStub ); - deepEqual( - factory.create( 'node-factory-node-stub', 16, { 'baz': 'quux' } ), - new ve.NodeFactoryNodeStub( 16, { 'baz': 'quux' } ), - 'creates nodes of a registered type and passes through arguments' - ); -} ); - -test( 'lookup', 1, function() { - var factory = new ve.NodeFactory(); - factory.register( 'node-factory-node-stub', ve.NodeFactoryNodeStub ); - strictEqual( factory.lookup( 'node-factory-node-stub' ), ve.NodeFactoryNodeStub ); -} ); From 7d96b8426f1875fab04aa2425426ce74995e4610 Mon Sep 17 00:00:00 2001 From: Rob Moen Date: Thu, 31 May 2012 16:54:53 -0700 Subject: [PATCH 2/5] Created save dialog in core integration Stash content element styles and remove transitions Working towards a cleaner saving experience Change-Id: Ic67022456f46b2bef56a9b0ccfcf93c3283573c4 --- VisualEditor.hooks.php | 5 +- VisualEditor.php | 2 +- modules/core/ve.Core.css | 98 ++++++++++++++++++++++- modules/core/ve.Core.js | 166 ++++++++++++++++++++++++++++++++------- 4 files changed, 237 insertions(+), 34 deletions(-) diff --git a/VisualEditor.hooks.php b/VisualEditor.hooks.php index d557c1630c..5db4e4901c 100644 --- a/VisualEditor.hooks.php +++ b/VisualEditor.hooks.php @@ -33,10 +33,9 @@ class VisualEditorHooks { return true; } /** - * Allow edits to the namespace only by admins - * Code used from Extension:NamespaceProtection + * */ - public static function canUserEditPage( &$title, &$user, $action, &$result ){ + public static function namespaceProtection( &$title, &$user, $action, &$result ){ global $wgUser, $wgNamespaceProtection; if ( array_key_exists( $title->mNamespace, $wgNamespaceProtection ) ) { diff --git a/VisualEditor.php b/VisualEditor.php index c0647ba234..4334be6598 100644 --- a/VisualEditor.php +++ b/VisualEditor.php @@ -243,7 +243,7 @@ $wgAPIModules['ve-parsoid'] = 'ApiVisualEditor'; // Integration Hooks $wgAutoloadClasses['VisualEditorHooks'] = $dir . 'VisualEditor.hooks.php'; $wgHooks['BeforePageDisplay'][] = 'VisualEditorHooks::onPageDisplay'; -$wgHooks['userCan'][] = 'VisualEditorHooks::canUserEditPage'; +$wgHooks['userCan'][] = 'VisualEditorHooks::namespaceProtection'; // API for retrieving wikidom parse results $wgAutoloadClasses['ApiQueryParseTree'] = $dir . 'api/ApiQueryParseTree.php'; diff --git a/modules/core/ve.Core.css b/modules/core/ve.Core.css index 931c242cd6..94076825e3 100644 --- a/modules/core/ve.Core.css +++ b/modules/core/ve.Core.css @@ -15,13 +15,13 @@ padding: 0.25em; height: 22px; margin-right: 0.125em; + border: solid 1px transparent; } .ve-action-button:before { content: " "; position: absolute; display: block; height: 22px; - width: 22px; } .ve-action-button:hover { border-color: #eeeeee; @@ -29,9 +29,101 @@ .ve-action-button:active, .ve-action-button-down { border-color: #dddddd; - background-position: top left; - background-repeat: repeat-x; -webkit-box-shadow: inset 0px 1px 4px 0px rgba(0, 0, 0, 0.07); -moz-box-shadow: inset 0px 1px 4px 0px rgba(0, 0, 0, 0.07); box-shadow: inset 0px 1px 4px 0px rgba(0, 0, 0, 0.07); +} + +/* Save dialog styles */ +#ve-saveDialog { + top: 0px; + right: 2.5em; + width: 35em; +} + +#ve-saveDialog > .ve-dialog-divider { + border-top:1px solid #dddddd; + padding: 10px 0; + font-size: 12px; +} + +#ve-saveDialog br { + clear: both; +} + +#ve-saveDialog > .ve-dialog-divider > .ve-dialog-left { + float: left; +} + +#ve-saveDialog > .ve-dialog-divider > input[type='text'] { + width: 96%; + font-size: 12px; + padding: 4px; + margin-bottom: 10px; +} + +#ve-saveDialog > .ve-dialog-divider > input[type='checkbox'] { + margin-right: 5px; +} + +.ve-closeBtn { + width: 22px; + background-image: url(../ve2/ui/styles/images/close.png); + background-position: center center; + background-repeat: no-repeat; +} +/* mini save button */ +.ve-saveBtn { + right: .025em; + width: 22px; + background-image: url(../ve2/ui/styles/images/close.png); + background-position: center center; + background-repeat: no-repeat; +} +/* inspector styles */ +.es-inspector-savebutton { + padding-right: 24px; + border:1px solid transparent; + border-radius: 0.125em; + -webkit-border-radius: 0.125em; + -moz-border-radius: 0.125em; + -o-border-radius: 0.125em; + /* need new button */ + /* @embed */ + background-image: url(../ve2/ui/styles/images/save.png); + background-position: center right; + background-repeat: no-repeat; +} +.doSaveBtn { + position: absolute; + border: 1px solid rgb(196,229,154); + margin-top: 10px; + right: 10px; + font-size: 12px; + padding: 5px 10px; + background-image: url(../ve2/ui/styles/images/close.png); + background-position: center right; + background-repeat: no-repeat; + /* Fancy CSS background */ + background-image: linear-gradient(bottom, rgb(195,229,154) 0%, rgb(240,251,225) 100%); + background-image: -o-linear-gradient(bottom, rgb(195,229,154) 0%, rgb(240,251,225) 100%); + background-image: -moz-linear-gradient(bottom, rgb(195,229,154) 0%, rgb(240,251,225) 100%); + background-image: -webkit-linear-gradient(bottom, rgb(195,229,154) 0%, rgb(240,251,225) 100%); + background-image: -ms-linear-gradient(bottom, rgb(195,229,154) 0%, rgb(240,251,225) 100%); + background-image: -webkit-gradient( + linear, + left bottom, + left top, + color-stop(0, rgb(195,229,154)), + color-stop(1, rgb(240,251,225)) + ); +} +.doSaveBtn > div.doSaveBtnIcon { + height:2em; + width:2em; + float:right; + background: transparent; + background-image: url(../ve2/ui/styles/images/accept.png); + background-position: center right; + background-repeat: no-repeat; } \ No newline at end of file diff --git a/modules/core/ve.Core.js b/modules/core/ve.Core.js index 94c8cee346..26b46dc556 100644 --- a/modules/core/ve.Core.js +++ b/modules/core/ve.Core.js @@ -8,19 +8,10 @@ pageName = mw.config.get( 'wgPageName' ), validNamespace = mw.config.get('wgCanonicalNamespace') === 'VisualEditor' ? true: false; - this.$content = $('#content'); - // Store content padding so that it can be restored. - this.contentPadding = this.$content.css('padding'); this.mainEditor = null; - - this.$spinner = $('
') - .attr({ - 'id': 've-loader-spinner', - 'class': 'mw-ajax-loader' - }).css({ - 'height': this.$content.height() + 'px', - 'width': (this.$content.width() -20 ) + 'px' - }); + this.$content = $('#content'); + // modify / stash content styles + this.prepareContentStyles(); // On VisualEditor namespace ? if ( validNamespace ) { @@ -80,39 +71,39 @@ } } }; - $editor = $('
'); + this.$editor = $('
'); this.$spinner.hide(); - this.$content - .css('padding', '0px 0px 0px 1px') - .append( $editor ); + + this.$content.css({ + 'padding':'0px 0px 0px 1px' + }).append( this.$editor ); this.mainEditor = new ve.Surface( '#ve-editor', $html[0], options ); - $editor.find('.es-panes') + this.$editor.find('.es-panes') .css('padding', this.contentPadding ); // Save BTN - $editor.find('.es-modes') + this.$editor.find('.es-modes') .append( $('
') - .attr('class', 've-action-button') + .attr('class', 've-action-button es-inspector-savebutton') .text('Save') .click(function(){ - // show save dialog - _this.save(); - _this.showSaveDialog(); + // show/hide dialog + _this.$dialog.toggle(); }) ).append( $('
') - .attr('class', 've-action-button') - .text('X') + .attr('class', 've-action-button ve-closeBtn') .click(function(){ // back to read mode _this.cleanup(); _this.mainEditor = null; }) ); + this.initSaveDialog(); } }; @@ -177,9 +168,9 @@ var _this = this; _this.showSpinner(); // Save - _this.getParsoidWikitextAndSave( function( content ){ // cleanup + _this.$dialog.toggle(); _this.cleanup(); // load saved page _this.$content @@ -187,13 +178,107 @@ }); }; - veCore.prototype.showSaveDialog = function(){ + veCore.prototype.initSaveDialog = function(){ + var _this = this; + this.$dialog = + $('
') + .attr({ + 'id': 've-saveDialog', + 'class': 'es-inspector' + }).append( + $('
') + .attr('class', 'es-inspector-title') + .text('Save your changes') + ).append( + $('
') + .attr('class', 'es-inspector-button ve-saveBtn') + .click(function(){ + _this.$dialog.toggle(); + }) + ).append( + $('
') + .attr('class', 've-dialog-divider') + .append( + $('
') + .text("Describe what you changed") + ).append( + $('') + .attr({ + 'type':'text' + }) + ).append( + $('
') + ).append( + $('
') + .attr('class', 've-dialog-left') + .append( + $('') + .attr({ + 'type': 'checkbox', + 'name': 'chkMinorEdit', + 'id': 'chkMinorEdit' + }) + ).append( + $('