2222 * @source : https://github.com/kogmbh/WebODF/
2323 */
2424
25- /*global core, ops, gui, odf, runtime*/
25+ /*global core, ops, gui, odf, NodeFilter, runtime*/
2626
2727/**
2828 * @implements {core.Destroyable}
@@ -36,14 +36,17 @@ gui.ListController = function ListController(session, sessionConstraints, sessio
3636 "use strict" ;
3737 var odtDocument = session . getOdtDocument ( ) ,
3838 odfUtils = odf . OdfUtils ,
39+ domUtils = core . DomUtils ,
3940 eventNotifier = new core . EventNotifier ( [
4041 gui . ListController . listStylingChanged ,
4142 gui . ListController . enabledChanged
4243 ] ) ,
4344 /**@type {!gui.ListController.SelectionInfo }*/
4445 lastSignalledSelectionInfo ,
4546 /**@type {!core.LazyProperty.<!gui.ListController.SelectionInfo> }*/
46- cachedSelectionInfo ;
47+ cachedSelectionInfo ,
48+ /**@const */
49+ NEXT = core . StepDirection . NEXT ;
4750
4851 /**
4952 * @param {!ops.OdtCursor|!string } cursorOrId
@@ -136,6 +139,161 @@ gui.ListController = function ListController(session, sessionConstraints, sessio
136139 emitSelectionChanges ( ) ;
137140 }
138141
142+ /**
143+ * Find all top level text:list elements in the given range.
144+ * This includes any elements that contain the start or end containers of the range.
145+ * @param {!Range } range
146+ * @return {!Array.<!Element> }
147+ */
148+ function getTopLevelListElementsInRange ( range ) {
149+ var elements ,
150+ topLevelList ,
151+ rootNode = odtDocument . getRootNode ( ) ;
152+
153+ /**
154+ * @param {!Node } node
155+ * @return {!number }
156+ */
157+ function isListOrListItem ( node ) {
158+ var result = NodeFilter . FILTER_REJECT ;
159+ if ( odfUtils . isListElement ( node ) && ! odfUtils . isListItemElement ( node . parentNode ) ) {
160+ result = NodeFilter . FILTER_ACCEPT ;
161+ } else if ( odfUtils . isTextContentContainingNode ( node ) || odfUtils . isGroupingElement ( node ) ) {
162+ result = NodeFilter . FILTER_SKIP ;
163+ }
164+ return result ;
165+ }
166+
167+ // ignore the list element if it is nested within another list
168+ elements = domUtils . getNodesInRange ( range , isListOrListItem , NodeFilter . SHOW_ELEMENT ) ;
169+
170+ // add any top level lists that contain the start or end containers of the range
171+ // check in the elements collection for duplicates in case these top level lists intersected the specified range
172+ topLevelList = odfUtils . getTopLevelListElement ( /**@type {!Node }*/ ( range . startContainer ) , rootNode ) ;
173+ if ( topLevelList && topLevelList !== elements [ 0 ] ) {
174+ elements . unshift ( topLevelList ) ;
175+ }
176+
177+ topLevelList = odfUtils . getTopLevelListElement ( /**@type {!Node }*/ ( range . endContainer ) , rootNode ) ;
178+ if ( topLevelList && topLevelList !== elements [ elements . length - 1 ] ) {
179+ elements . push ( topLevelList ) ;
180+ }
181+
182+ return elements ;
183+ }
184+
185+ /**
186+ * @param {!Element } initialParagraph
187+ * @return {!{startParagraph: !Element, endParagraph: !Element} }
188+ */
189+ function createParagraphGroup ( initialParagraph ) {
190+ return {
191+ startParagraph : initialParagraph ,
192+ endParagraph : initialParagraph
193+ } ;
194+ }
195+
196+ /**
197+ * Takes all the paragraph elements in the current selection and breaks
198+ * them into add list operations based on their common ancestors. Paragraph elements
199+ * with the same common ancestor will be grouped into the same operation
200+ * @return {!Array.<!ops.Operation> }
201+ */
202+ function determineOpsForAddingLists ( ) {
203+ var paragraphElements ,
204+ /**@type {!Array.<!{startParagraph: !Element, endParagraph: !Element}> }*/
205+ paragraphGroups = [ ] ,
206+ paragraphParent ,
207+ commonAncestor ,
208+ i ;
209+
210+ paragraphElements = odfUtils . getParagraphElements ( odtDocument . getCursor ( inputMemberId ) . getSelectedRange ( ) ) ;
211+
212+ for ( i = 0 ; i < paragraphElements . length ; i += 1 ) {
213+ paragraphParent = paragraphElements [ i ] . parentNode ;
214+
215+ //TODO: handle selections that intersect with existing lists
216+ if ( odfUtils . isListItemElement ( paragraphParent ) ) {
217+ runtime . log ( "DEBUG: Current selection intersects with an existing list which is not supported at this time" ) ;
218+ paragraphGroups . length = 0 ;
219+ break ;
220+ }
221+
222+ if ( paragraphParent === commonAncestor ) {
223+ // if the current paragraph has the same common ancestor as the current group of paragraphs
224+ // then the paragraph group gets extended to include the current paragraph
225+ paragraphGroups [ paragraphGroups . length - 1 ] . endParagraph = paragraphElements [ i ] ;
226+ } else {
227+ // if the ancestor of this paragraph does not match then begin a new group of paragraphs
228+ commonAncestor = paragraphParent ;
229+ paragraphGroups . push ( createParagraphGroup ( paragraphElements [ i ] ) ) ;
230+ }
231+ }
232+
233+ // each paragraph group becomes one add list operation
234+ return paragraphGroups . map ( function ( group ) {
235+ // take the first step of the start and end paragraph of each group and
236+ // pass them in as the coordinates for the add list operation
237+ var newOp = new ops . OpAddList ( ) ;
238+ newOp . init ( {
239+ memberid : inputMemberId ,
240+ startParagraphPosition : odtDocument . convertDomPointToCursorStep ( group . startParagraph , 0 , NEXT ) ,
241+ endParagraphPosition : odtDocument . convertDomPointToCursorStep ( group . endParagraph , 0 , NEXT )
242+ } ) ;
243+ return newOp ;
244+ } ) ;
245+ }
246+
247+ /**
248+ * Finds all the lists to be removed in the current selection and creates an operation for each
249+ * top level list element found
250+ * @return {!Array.<!ops.Operation> }
251+ */
252+ function determineOpsForRemovingLists ( ) {
253+ var topLevelListElements ,
254+ stepIterator = odtDocument . createStepIterator (
255+ odtDocument . getRootNode ( ) ,
256+ 0 ,
257+ [ odtDocument . getPositionFilter ( ) ] ,
258+ odtDocument . getRootNode ( ) ) ;
259+
260+ topLevelListElements = getTopLevelListElementsInRange ( odtDocument . getCursor ( inputMemberId ) . getSelectedRange ( ) ) ;
261+
262+ return topLevelListElements . map ( function ( listElement ) {
263+ var newOp = new ops . OpRemoveList ( ) ;
264+
265+ stepIterator . setPosition ( listElement , 0 ) ;
266+ runtime . assert ( stepIterator . roundToNextStep ( ) , "Top level list element contains no steps" ) ;
267+
268+ newOp . init ( {
269+ memberid : inputMemberId ,
270+ firstParagraphPosition : odtDocument . convertDomPointToCursorStep ( stepIterator . container ( ) , stepIterator . offset ( ) )
271+ } ) ;
272+ return newOp ;
273+ } ) ;
274+ }
275+
276+ /**
277+ * @param {function():!Array.<!ops.Operation> } executeFunc
278+ * @return {!boolean }
279+ */
280+ function executeListOperations ( executeFunc ) {
281+ var newOps ;
282+
283+ if ( ! cachedSelectionInfo . value ( ) . isEnabled ) {
284+ return false ;
285+ }
286+
287+ newOps = executeFunc ( ) ;
288+
289+ if ( newOps . length > 0 ) {
290+ session . enqueue ( newOps ) ;
291+ return true ;
292+ }
293+
294+ return false ;
295+ }
296+
139297 /**
140298 * @return {!boolean }
141299 */
@@ -161,6 +319,47 @@ gui.ListController = function ListController(session, sessionConstraints, sessio
161319 eventNotifier . unsubscribe ( eventid , cb ) ;
162320 } ;
163321
322+ /**
323+ * @return {!boolean }
324+ */
325+ function makeList ( ) {
326+ return executeListOperations ( determineOpsForAddingLists ) ;
327+ }
328+
329+ this . makeList = makeList ;
330+
331+ /**
332+ * @return {!boolean }
333+ */
334+ function removeList ( ) {
335+ return executeListOperations ( determineOpsForRemovingLists ) ;
336+ }
337+
338+ this . removeList = removeList ;
339+
340+ /**
341+ * @param {!boolean } checked
342+ * @return {!boolean }
343+ */
344+ this . setNumberedList = function ( checked ) {
345+ if ( checked ) {
346+ return makeList ( ) ;
347+ }
348+ return removeList ( ) ;
349+
350+ } ;
351+
352+ /**
353+ * @param {!boolean } checked
354+ * @return {!boolean }
355+ */
356+ this . setBulletedList = function ( checked ) {
357+ if ( checked ) {
358+ return makeList ( ) ;
359+ }
360+ return removeList ( ) ;
361+ } ;
362+
164363 /**
165364 * @param {!function(!Error=) } callback
166365 * @return {undefined }
0 commit comments