Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4116952
ve.Document.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
18 KB
Referenced Files
None
Subscribers
None
ve.Document.js
View Options
/*!
* VisualEditor Document class.
*
* @copyright See AUTHORS.txt
*/
/**
* Generic document.
*
* @class
* @mixes OO.EventEmitter
*
* @constructor
* @param {ve.BranchNode} documentNode Document node
*/
ve
.
Document
=
function
VeDocument
(
documentNode
)
{
// Mixin constructors
OO
.
EventEmitter
.
call
(
this
);
// Properties
this
.
documentNode
=
documentNode
;
this
.
documentNode
.
setDocument
(
this
);
};
/* Inheritance */
OO
.
mixinClass
(
ve
.
Document
,
OO
.
EventEmitter
);
/* Events */
/**
* A node has been attached with ve.Node#setDocument . Its descendants are guaranteed
* to be attached too (and the event is emitted for descendants first, and for siblings
* in their order in the children list)
*
* @event ve.Document#nodeAttached
* @param {ve.Node} node The node that has been attached
*/
/**
* A node has been detached with ve.Node#setDocument . Its descendants are guaranteed
* to be detached too (and the event is emitted for descendants first, and for siblings
* in their order in the children list)
*
* @event ve.Document#nodeDetached
* @param {ve.Node} node The node that has been detached
*/
/* Methods */
/**
* Get the root of the document's node tree.
*
* @return {ve.BranchNode} Root of node tree
*/
ve
.
Document
.
prototype
.
getDocumentNode
=
function
()
{
return
this
.
documentNode
;
};
/**
* Get a node at an offset.
*
* @param {number} offset Offset to get node at
* @return {ve.Node|null} Node at offset
*/
ve
.
Document
.
prototype
.
getBranchNodeFromOffset
=
function
(
offset
)
{
let
node
=
this
.
getDocumentNode
().
getNodeFromOffset
(
offset
);
if
(
node
&&
!
node
.
hasChildren
()
)
{
node
=
node
.
getParent
();
}
return
node
;
};
/**
* Gets a list of nodes and the ranges within them that a selection of the document covers.
*
* @param {ve.Range} range Range within document to select nodes
* @param {string} [mode='leaves'] Type of selection to perform:
*
* - `leaves`: Return all leaf nodes in the given range (descends all the way down)
* - `branches`': Return all branch nodes in the given range
* - `covered`: Do not descend into nodes that are entirely covered by the range. The result
* is similar to that of 'leaves' except that if a node is entirely covered, its
* children aren't returned separately.
* - `siblings`: Return a set of adjacent siblings covered by the range (descends as long as the
* range is in a single node)
* @return {Array} List of objects describing nodes in the selection and the ranges therein:
*
* - `node`: Reference to a ve.Node
* - `range`: ve.Range, missing if the entire node is covered
* - `index`: Index of the node in its parent, missing if node has no parent
* - `indexInNode`: If range is a zero-length range between two children of node,
* this is set to the index of the child following range (or to
* `node.children.length + 1` if range is between the last child and
* the end). If range is a zero-length range inside an empty non-content branch node, this is 0.
* Missing in all other cases.
* - `nodeRange`: Range covering the inside of the entire node, not including wrapper
* - `nodeOuterRange`: Range covering the entire node, including wrapper
* - `parentOuterRange`: Outer range of node's parent. Missing if there is no parent
* or if indexInNode is set.
*
* @throws {Error} Invalid mode
* @throws {Error} Invalid start offset
* @throws {Error} Invalid end offset
* @throws {Error} Failed to select any nodes
*/
ve
.
Document
.
prototype
.
selectNodes
=
function
(
range
,
mode
)
{
const
doc
=
this
.
getDocumentNode
(),
start
=
range
.
start
,
end
=
range
.
end
,
stack
=
[
{
// Node we are currently stepping through
// Note each iteration visits a child of node, not node itself
node
:
doc
,
// Index of the child in node we're visiting
index
:
0
,
// First offset inside node
startOffset
:
0
}
];
let
retval
=
[],
currentFrame
=
stack
[
0
],
startFound
=
false
;
mode
=
mode
||
'leaves'
;
if
(
mode
!==
'leaves'
&&
mode
!==
'branches'
&&
mode
!==
'covered'
&&
mode
!==
'siblings'
)
{
throw
new
Error
(
'Invalid mode: '
+
mode
);
}
if
(
start
<
0
||
start
>
doc
.
getLength
()
)
{
throw
new
Error
(
'Invalid start offset: '
+
start
);
}
if
(
end
<
0
||
end
>
doc
.
getLength
()
)
{
throw
new
Error
(
'Invalid end offset: '
+
end
);
}
if
(
!
doc
.
children
||
doc
.
children
.
length
===
0
)
{
// Document has no children. This is weird
const
nodeRange
=
new
ve
.
Range
(
0
,
doc
.
getLength
()
);
return
[
{
node
:
doc
,
range
:
new
ve
.
Range
(
start
,
end
),
index
:
0
,
nodeRange
:
nodeRange
,
nodeOuterRange
:
nodeRange
}
];
}
let
left
=
doc
.
children
[
0
].
isWrapped
()
?
1
:
0
;
do
{
const
node
=
currentFrame
.
node
.
children
[
currentFrame
.
index
];
const
prevNode
=
currentFrame
.
node
.
children
[
currentFrame
.
index
-
1
];
let
nextNode
=
currentFrame
.
node
.
children
[
currentFrame
.
index
+
1
];
const
right
=
left
+
node
.
getLength
();
// Is the start inside node?
const
startInside
=
start
>=
left
&&
start
<=
right
;
// Is the end inside node?
const
endInside
=
end
>=
left
&&
end
<=
right
;
// Does the node have wrapping elements around it
let
isWrapped
=
node
.
isWrapped
();
// Is there an unwrapped node right before this node?
const
isPrevUnwrapped
=
prevNode
?
!
prevNode
.
isWrapped
()
:
false
;
// Is there an unwrapped node right after this node?
const
isNextUnwrapped
=
nextNode
?
!
nextNode
.
isWrapped
()
:
false
;
// Is this node an empty non-content branch node?
const
isEmptyBranch
=
(
node
.
getLength
()
===
0
||
node
.
shouldIgnoreChildren
()
)
&&
!
node
.
isContent
()
&&
!
node
.
canContainContent
();
// Is the start between prevNode's closing and node or between the parent's opening and node?
const
startBetween
=
(
isWrapped
?
start
===
left
-
1
:
start
===
left
)
&&
!
isPrevUnwrapped
;
// Is the end between node and nextNode's opening or between node and the parent's closing?
const
endBetween
=
(
isWrapped
?
end
===
right
+
1
:
end
===
right
)
&&
!
isNextUnwrapped
;
let
parentRange
=
new
ve
.
Range
(
currentFrame
.
startOffset
,
currentFrame
.
startOffset
+
currentFrame
.
node
.
getLength
()
);
let
parentFrame
;
if
(
isWrapped
&&
end
===
left
-
1
&&
currentFrame
.
index
===
0
)
{
// The selection ends here with an empty range at the beginning of the node
// TODO duplicated code
isWrapped
=
currentFrame
.
node
.
isWrapped
();
retval
.
push
(
{
node
:
currentFrame
.
node
,
indexInNode
:
0
,
range
:
new
ve
.
Range
(
end
,
end
),
nodeRange
:
parentRange
,
nodeOuterRange
:
new
ve
.
Range
(
parentRange
.
start
-
isWrapped
,
parentRange
.
end
+
isWrapped
)
}
);
parentFrame
=
stack
[
stack
.
length
-
2
];
if
(
parentFrame
)
{
retval
[
retval
.
length
-
1
].
index
=
parentFrame
.
index
;
}
return
retval
;
}
if
(
start
===
end
&&
(
startBetween
||
endBetween
)
&&
isWrapped
)
{
// Empty range in the parent, outside of any child
isWrapped
=
currentFrame
.
node
.
isWrapped
();
retval
=
[
{
node
:
currentFrame
.
node
,
indexInNode
:
currentFrame
.
index
+
(
endBetween
?
1
:
0
),
range
:
new
ve
.
Range
(
start
,
end
),
nodeRange
:
parentRange
,
nodeOuterRange
:
new
ve
.
Range
(
parentRange
.
start
-
isWrapped
,
parentRange
.
end
+
isWrapped
)
}
];
parentFrame
=
stack
[
stack
.
length
-
2
];
if
(
parentFrame
)
{
retval
[
0
].
index
=
parentFrame
.
index
;
}
return
retval
;
}
else
if
(
startBetween
)
{
// start is between the previous sibling and node
// so the selection covers all or part of node
// Descend if
// - we are in leaves mode, OR
// - we are in covered mode and the end is inside node OR
// - we are in branches mode and node is a branch (can have grandchildren)
// AND
// the node is non-empty and doesn't handle its own children
if
(
(
mode
===
'leaves'
||
(
mode
===
'covered'
&&
endInside
)
||
(
mode
===
'branches'
&&
node
.
canHaveChildrenNotContent
()
)
)
&&
node
.
children
&&
node
.
children
.
length
&&
!
node
.
shouldIgnoreChildren
()
)
{
// Descend into node
currentFrame
=
{
node
:
node
,
index
:
0
,
startOffset
:
left
};
stack
.
push
(
currentFrame
);
startFound
=
true
;
// If the first child of node has an opening, skip over it
if
(
node
.
children
[
0
].
isWrapped
()
)
{
left
++
;
}
continue
;
}
else
if
(
!
endInside
)
{
// All of node is covered
retval
.
push
(
{
node
:
node
,
// No 'range' because the entire node is covered
index
:
currentFrame
.
index
,
nodeRange
:
new
ve
.
Range
(
left
,
right
),
nodeOuterRange
:
new
ve
.
Range
(
left
-
isWrapped
,
right
+
isWrapped
),
parentOuterRange
:
new
ve
.
Range
(
parentRange
.
start
-
currentFrame
.
node
.
isWrapped
(),
parentRange
.
end
+
currentFrame
.
node
.
isWrapped
()
)
}
);
startFound
=
true
;
}
else
{
// Part of node is covered
return
[
{
node
:
node
,
range
:
new
ve
.
Range
(
start
,
end
),
index
:
currentFrame
.
index
,
nodeRange
:
new
ve
.
Range
(
left
,
right
),
nodeOuterRange
:
new
ve
.
Range
(
left
-
isWrapped
,
right
+
isWrapped
),
parentOuterRange
:
new
ve
.
Range
(
parentRange
.
start
-
currentFrame
.
node
.
isWrapped
(),
parentRange
.
end
+
currentFrame
.
node
.
isWrapped
()
)
}
];
}
}
else
if
(
startInside
&&
endInside
)
{
if
(
node
.
children
&&
node
.
children
.
length
&&
(
mode
!==
'branches'
||
node
.
canHaveChildrenNotContent
()
)
)
{
// Descend into node
currentFrame
=
{
node
:
node
,
index
:
0
,
startOffset
:
left
};
stack
.
push
(
currentFrame
);
// If the first child of node has an opening, skip over it
if
(
node
.
children
[
0
].
isWrapped
()
)
{
left
++
;
}
continue
;
}
else
{
// node is a leaf node and the range is entirely inside it
retval
=
[
{
node
:
node
,
range
:
new
ve
.
Range
(
start
,
end
),
index
:
currentFrame
.
index
,
nodeRange
:
new
ve
.
Range
(
left
,
right
),
nodeOuterRange
:
new
ve
.
Range
(
left
-
isWrapped
,
right
+
isWrapped
),
parentOuterRange
:
new
ve
.
Range
(
parentRange
.
start
-
currentFrame
.
node
.
isWrapped
(),
parentRange
.
end
+
currentFrame
.
node
.
isWrapped
()
)
}
];
if
(
isEmptyBranch
)
{
retval
[
0
].
indexInNode
=
0
;
}
return
retval
;
}
}
else
if
(
startInside
)
{
if
(
(
mode
===
'leaves'
||
mode
===
'covered'
||
(
mode
===
'branches'
&&
node
.
canHaveChildrenNotContent
()
)
)
&&
node
.
children
&&
node
.
children
.
length
)
{
// node is a branch node and the start is inside it
// Descend into it
currentFrame
=
{
node
:
node
,
index
:
0
,
startOffset
:
left
};
stack
.
push
(
currentFrame
);
// If the first child of node has an opening, skip over it
if
(
node
.
children
[
0
].
isWrapped
()
)
{
left
++
;
}
continue
;
}
else
{
// node is a leaf node and the start is inside it
// Add to retval and keep going
retval
.
push
(
{
node
:
node
,
range
:
new
ve
.
Range
(
start
,
right
),
index
:
currentFrame
.
index
,
nodeRange
:
new
ve
.
Range
(
left
,
right
),
nodeOuterRange
:
new
ve
.
Range
(
left
-
isWrapped
,
right
+
isWrapped
),
parentOuterRange
:
new
ve
.
Range
(
parentRange
.
start
-
currentFrame
.
node
.
isWrapped
(),
parentRange
.
end
+
currentFrame
.
node
.
isWrapped
()
)
}
);
startFound
=
true
;
}
}
else
if
(
endBetween
)
{
// end is between node and the next sibling
// start is not inside node, so the selection covers
// all of node, then ends
if
(
(
mode
===
'leaves'
||
(
mode
===
'branches'
&&
node
.
canHaveChildrenNotContent
()
)
)
&&
node
.
children
&&
node
.
children
.
length
)
{
// Descend into node
currentFrame
=
{
node
:
node
,
index
:
0
,
startOffset
:
left
};
stack
.
push
(
currentFrame
);
// If the first child of node has an opening, skip over it
if
(
node
.
children
[
0
].
isWrapped
()
)
{
left
++
;
}
continue
;
}
else
{
// All of node is covered
retval
.
push
(
{
node
:
node
,
// No 'range' because the entire node is covered
index
:
currentFrame
.
index
,
nodeRange
:
new
ve
.
Range
(
left
,
right
),
nodeOuterRange
:
new
ve
.
Range
(
left
-
isWrapped
,
right
+
isWrapped
),
parentOuterRange
:
new
ve
.
Range
(
parentRange
.
start
-
currentFrame
.
node
.
isWrapped
(),
parentRange
.
end
+
currentFrame
.
node
.
isWrapped
()
)
}
);
return
retval
;
}
}
else
if
(
endInside
)
{
if
(
(
mode
===
'leaves'
||
mode
===
'covered'
||
(
mode
===
'branches'
&&
node
.
canHaveChildrenNotContent
()
)
)
&&
node
.
children
&&
node
.
children
.
length
)
{
// node is a branch node and the end is inside it
// Descend into it
currentFrame
=
{
node
:
node
,
index
:
0
,
startOffset
:
left
};
stack
.
push
(
currentFrame
);
// If the first child of node has an opening, skip over it
if
(
node
.
children
[
0
].
isWrapped
()
)
{
left
++
;
}
continue
;
}
else
{
// node is a leaf node and the end is inside it
// Add to retval and return
retval
.
push
(
{
node
:
node
,
range
:
new
ve
.
Range
(
left
,
end
),
index
:
currentFrame
.
index
,
nodeRange
:
new
ve
.
Range
(
left
,
right
),
nodeOuterRange
:
new
ve
.
Range
(
left
-
isWrapped
,
right
+
isWrapped
),
parentOuterRange
:
new
ve
.
Range
(
parentRange
.
start
-
currentFrame
.
node
.
isWrapped
(),
parentRange
.
end
+
currentFrame
.
node
.
isWrapped
()
)
}
);
return
retval
;
}
}
else
if
(
startFound
&&
end
>
right
)
{
// Neither the start nor the end is inside node, but we found the start earlier,
// so node must be between the start and the end
// Add the entire node, so no range property
if
(
(
mode
===
'leaves'
||
(
mode
===
'branches'
&&
node
.
canHaveChildrenNotContent
()
)
)
&&
node
.
children
&&
node
.
children
.
length
)
{
// Descend into node
currentFrame
=
{
node
:
node
,
index
:
0
,
startOffset
:
left
};
stack
.
push
(
currentFrame
);
// If the first child of node has an opening, skip over it
if
(
node
.
children
[
0
].
isWrapped
()
)
{
left
++
;
}
continue
;
}
else
{
// All of node is covered
retval
.
push
(
{
node
:
node
,
// No 'range' because the entire node is covered
index
:
currentFrame
.
index
,
nodeRange
:
new
ve
.
Range
(
left
,
right
),
nodeOuterRange
:
new
ve
.
Range
(
left
-
isWrapped
,
right
+
isWrapped
),
parentOuterRange
:
new
ve
.
Range
(
parentRange
.
start
-
currentFrame
.
node
.
isWrapped
(),
parentRange
.
end
+
currentFrame
.
node
.
isWrapped
()
)
}
);
}
}
// Move to the next node
if
(
nextNode
)
{
// The next node exists
// Advance the index; the start of the next iteration will essentially
// do node = nextNode;
currentFrame
.
index
++
;
// Advance to the first offset inside nextNode
left
=
right
+
// Skip over node's closing, if present
(
node
.
isWrapped
()
?
1
:
0
)
+
// Skip over nextNode's opening, if present
(
nextNode
.
isWrapped
()
?
1
:
0
);
}
else
{
// There is no next node, move up the stack until there is one
left
=
right
+
// Skip over node's closing, if present
(
node
.
isWrapped
()
?
1
:
0
);
while
(
!
nextNode
)
{
// Check if the start is right past the end of this node, at the end of
// the parent
if
(
node
.
isWrapped
()
&&
start
===
left
)
{
// TODO duplicated code
parentRange
=
new
ve
.
Range
(
currentFrame
.
startOffset
,
currentFrame
.
startOffset
+
currentFrame
.
node
.
getLength
()
);
isWrapped
=
currentFrame
.
node
.
isWrapped
();
retval
=
[
{
node
:
currentFrame
.
node
,
indexInNode
:
currentFrame
.
index
+
1
,
range
:
new
ve
.
Range
(
left
,
left
),
nodeRange
:
parentRange
,
nodeOuterRange
:
new
ve
.
Range
(
parentRange
.
start
-
isWrapped
,
parentRange
.
end
+
isWrapped
)
}
];
parentFrame
=
stack
[
stack
.
length
-
2
];
if
(
parentFrame
)
{
retval
[
0
].
index
=
parentFrame
.
index
;
}
}
// Move up the stack
stack
.
pop
();
if
(
stack
.
length
===
0
)
{
// This shouldn't be possible
return
retval
;
}
currentFrame
=
stack
[
stack
.
length
-
1
];
currentFrame
.
index
++
;
nextNode
=
currentFrame
.
node
.
children
[
currentFrame
.
index
];
// Skip over the parent node's closing
// (this is present for sure, because the parent has children)
left
++
;
}
// Skip over nextNode's opening if present
if
(
nextNode
.
isWrapped
()
)
{
left
++
;
}
}
}
while
(
end
>=
left
-
1
);
if
(
retval
.
length
===
0
)
{
throw
new
Error
(
'Failed to select any nodes'
);
}
return
retval
;
};
/**
* Get groups of sibling nodes covered by the given range.
*
* @param {ve.Range} range
* @return {Array} Array of objects. Each object has the following keys:
*
* - nodes: Array of sibling nodes covered by a part of range
* - parent: Parent of all of these nodes
* - grandparent: parent's parent
*/
ve
.
Document
.
prototype
.
getCoveredSiblingGroups
=
function
(
range
)
{
const
leaves
=
this
.
selectNodes
(
range
,
'leaves'
),
groups
=
[];
let
lastEndOffset
=
0
;
for
(
let
i
=
0
;
i
<
leaves
.
length
;
i
++
)
{
if
(
leaves
[
i
].
nodeOuterRange
.
end
<=
lastEndOffset
)
{
// This range is contained within a range we've already processed
continue
;
}
let
node
=
leaves
[
i
].
node
;
// Traverse up to a content branch from content elements
if
(
node
.
isContent
()
)
{
node
=
node
.
getParent
();
}
const
parentNode
=
node
.
getParent
();
if
(
!
parentNode
)
{
break
;
}
// Group this with its covered siblings
groups
.
push
(
{
parent
:
parentNode
,
grandparent
:
parentNode
.
getParent
(),
nodes
:
[]
}
);
// Seek forward to the last covered sibling
let
siblingNode
=
node
;
do
{
// Add this to its sibling's group
groups
[
groups
.
length
-
1
].
nodes
.
push
(
siblingNode
);
i
++
;
if
(
leaves
[
i
]
===
undefined
)
{
break
;
}
// Traverse up to a content branch from content elements
siblingNode
=
leaves
[
i
].
node
;
if
(
siblingNode
.
isContent
()
)
{
siblingNode
=
siblingNode
.
getParent
();
}
}
while
(
siblingNode
.
getParent
()
===
parentNode
);
i
--
;
lastEndOffset
=
parentNode
.
getOuterRange
().
end
;
}
return
groups
;
};
/**
* Test whether a range lies within a single leaf node.
*
* @param {ve.Range} range The range to test
* @return {boolean} Whether the range lies within a single node
*/
ve
.
Document
.
prototype
.
rangeInsideOneLeafNode
=
function
(
range
)
{
const
selected
=
this
.
selectNodes
(
range
,
'leaves'
);
return
selected
.
length
===
1
&&
selected
[
0
].
nodeRange
.
containsRange
(
range
)
&&
selected
[
0
].
indexInNode
===
undefined
;
};
/**
* Callback when a node is attached with ve.Node#setDocument
*
* The node and all its children are guaranteed to be attached
*
* @param {ve.Node} node The node attached
* @fires ve.Document#nodeAttached
*/
ve
.
Document
.
prototype
.
nodeAttached
=
function
(
node
)
{
this
.
emit
(
'nodeAttached'
,
node
);
};
/**
* Callback when a node is attached with ve.Node#setDocument
*
* The node and all its children are guaranteed to be attached
*
* @param {ve.Node} node The node detached
* @fires ve.Document#nodeDetached
*/
ve
.
Document
.
prototype
.
nodeDetached
=
function
(
node
)
{
this
.
emit
(
'nodeDetached'
,
node
);
};
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Aug 19, 01:56 (2 w, 3 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
27/b4/83ce062be972c3fd178a4b8c5cbb
Default Alt Text
ve.Document.js (18 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment