2013-05-22 22:13:12 +02:00
/ * *
2013-05-29 21:25:12 +02:00
* EGroupware - Home - Javascript UI
2013-05-22 22:13:12 +02:00
*
* @ link http : //www.egroupware.org
2013-05-29 21:25:12 +02:00
* @ package home
* @ author Nathan Gray
* @ copyright ( c ) 2013 Nathan Gray
2013-05-22 22:13:12 +02:00
* @ license http : //opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @ version $Id$
* /
"use strict" ;
/ * e g w : u s e s
jquery . jquery ;
jquery . jquery - ui ;
/ p h p g w a p i / j s / j q u e r y / g r i d s t e r / j q u e r y . g r i d s t e r . j s ;
* /
/ * *
* JS for home application
2013-11-04 21:54:23 +01:00
*
2013-05-22 22:13:12 +02:00
* Home is a collection of little bits of content ( portlets ) from the other applications .
*
2013-11-04 21:54:23 +01:00
*
2013-05-22 22:13:12 +02:00
* Uses Gridster for the grid layout
2013-05-29 21:25:12 +02:00
* @ see http : //gridster.net
2013-05-22 22:13:12 +02:00
* @ augments AppJS
* /
2013-11-04 21:54:23 +01:00
app . classes . home = AppJS . extend (
2013-05-22 22:13:12 +02:00
{
/ * *
* AppJS requires overwriting this with the actual application name
* /
appname : "home" ,
2013-05-29 21:25:12 +02:00
/ * *
* Grid resolution . Must match et2 _portlet GRID
* /
2014-11-25 01:45:14 +01:00
GRID : 50 ,
2013-05-29 21:25:12 +02:00
/ * *
* Default size for new portlets
* /
DEFAULT : {
2014-11-25 01:45:14 +01:00
WIDTH : 4 ,
2013-05-29 21:25:12 +02:00
HEIGHT : 1
} ,
2013-05-22 22:13:12 +02:00
/ * *
* Constructor
2013-11-04 21:54:23 +01:00
*
2013-05-23 00:44:27 +02:00
* @ memberOf app . home
* /
init : function ( )
{
// call parent
this . _super . apply ( this , arguments ) ;
} ,
/ * *
* Destructor
* @ memberOf app . home
* /
destroy : function ( )
{
delete this . et2 ;
2013-05-22 22:13:12 +02:00
delete this . portlet _container ;
// call parent
2013-05-23 00:44:27 +02:00
this . _super . apply ( this , arguments ) ;
2014-11-06 22:40:03 +01:00
// Make sure all other sub-etemplates in portlets are done
var others = etemplate2 . getByApplication ( this . appname ) ;
for ( var i = 0 ; i < others . length ; i ++ )
{
others [ i ] . clear ( ) ;
}
2013-05-23 00:44:27 +02:00
} ,
2013-05-22 22:13:12 +02:00
2013-05-23 00:44:27 +02:00
/ * *
* This function is called when the etemplate2 object is loaded
* and ready . If you must store a reference to the et2 object ,
* make sure to clean it up in destroy ( ) .
*
2014-11-06 22:40:03 +01:00
* @ param { etemplate2 } et2 Newly ready object
* @ param { string } Template name
2013-05-23 00:44:27 +02:00
* /
2014-11-06 22:40:03 +01:00
et2 _ready : function ( et2 , name )
2013-05-23 00:44:27 +02:00
{
2014-11-06 22:40:03 +01:00
// Top level
if ( name == 'home.index' )
{
// call parent
this . _super . apply ( this , arguments ) ;
2013-05-23 00:44:27 +02:00
2014-11-06 22:40:03 +01:00
this . et2 . set _id ( 'home.index' ) ;
this . et2 . set _actions ( this . et2 . getArrayMgr ( 'modifications' ) . getEntry ( 'home.index' ) [ 'actions' ] ) ;
2013-11-04 21:54:23 +01:00
2014-11-06 22:40:03 +01:00
this . portlet _container = this . et2 . getWidgetById ( "portlets" ) ;
2013-05-22 22:13:12 +02:00
2014-11-06 22:40:03 +01:00
// Set up sorting of portlets
this . _do _ordering ( ) ;
2014-11-12 00:07:35 +01:00
// Accept drops of favorites, which aren't part of action system
2014-11-17 23:18:24 +01:00
$j ( this . et2 . getDOMNode ( ) . parentNode ) . droppable ( {
2014-11-12 00:07:35 +01:00
hoverClass : 'drop-hover' ,
accept : function ( draggable ) {
// Check for direct support for that application
if ( draggable [ 0 ] . dataset && draggable [ 0 ] . dataset . appname )
{
return egw _getActionManager ( 'home' , false , 1 ) . getActionById ( 'drop_' + draggable [ 0 ] . dataset . appname + '_favorite_portlet' ) != null ;
}
return false ;
} ,
drop : function ( event , ui ) {
// Favorite dropped on home - fake an action and divert to normal handler
var action = {
data : {
class : 'add_home_favorite_portlet'
}
}
// Check for direct support for that application
if ( ui . helper . context . dataset && ui . helper . context . dataset . appname )
{
var action = egw _getActionManager ( 'home' , false , 1 ) . getActionById ( 'drop_' + ui . helper . context . dataset . appname + '_favorite_portlet' ) || { }
}
action . ui = ui ;
app . home . add _from _drop ( action , [ { data : ui . helper . context . dataset } ] )
}
} )
2014-11-06 22:40:03 +01:00
}
else if ( et2 . uniqueId )
2013-05-22 22:13:12 +02:00
{
2014-11-06 22:40:03 +01:00
// Handle bad timing - a sub-template was finished first
if ( ! this . portlet _container )
{
window . setTimeout ( jQuery . proxy ( this , function ( ) { this . et2 _ready ( et2 , name ) ; } ) , 200 ) ;
return ;
}
var portlet = this . portlet _container . getWidgetById ( et2 . uniqueId ) ;
// Check for existing etemplate, this one loaded over it
// NOTE: Moving them around like this can cause problems with event handlers
var existing = etemplate2 . getById ( et2 . uniqueId ) ;
if ( portlet && existing && existing . etemplate _exec _id != et2 . etemplate _exec _id )
{
for ( var i = 0 ; i < portlet . _children . length ; i ++ )
{
portlet . _children [ i ] . _inst . clear ( ) ;
}
portlet . _children = [ ] ;
}
// It's in the right place for original load, but move it into portlet
var misplaced = $j ( etemplate2 . getById ( 'home-index' ) . DOMContainer ) . siblings ( '#' + et2 . DOMContainer . id ) ;
if ( portlet )
{
2014-11-12 00:07:35 +01:00
portlet . content = $j ( et2 . DOMContainer ) . appendTo ( portlet . content ) ;
2014-11-06 22:40:03 +01:00
portlet . addChild ( et2 . widgetContainer ) ;
2014-11-12 00:07:35 +01:00
et2 . resize ( ) ;
2014-11-06 22:40:03 +01:00
}
if ( portlet && misplaced . length )
{
et2 . DOMContainer . id = et2 . uniqueId ;
}
2013-05-22 22:13:12 +02:00
}
2014-12-15 17:39:52 +01:00
// Special handling to deal with legacy (non-et2) calendar links
if ( name == 'home.legacy' )
{
2014-12-15 17:48:32 +01:00
$j ( '.calendar_calDayColHeader a, .calendar_plannerDayScale a, .calendar_plannerWeekScale a, .calendar_plannerMonthScale a, .calendar_calGridHeader a' , et2 . DOMContainer )
2014-12-15 17:39:52 +01:00
. on ( 'click' , function ( e ) {
egw . link _handler ( this . href , 'calendar' ) ;
return false ;
} ) ;
}
2014-11-06 22:40:03 +01:00
} ,
2013-05-22 22:13:12 +02:00
/ * *
2013-05-23 00:44:27 +02:00
* Add a new portlet from the context menu
2013-05-22 22:13:12 +02:00
* /
2013-06-05 00:34:21 +02:00
add : function ( action , source ) {
2014-11-19 00:46:58 +01:00
// Basic portlet attributes
var attrs = {
id : this . _create _id ( ) ,
class : action . data . class ,
width : this . DEFAULT . WIDTH ,
height : this . DEFAULT . HEIGHT
} ;
2014-11-12 00:07:35 +01:00
// Try to put it about where the menu was opened
2014-11-10 19:44:11 +01:00
if ( action . menu _context )
{
var $portlet _container = $j ( this . portlet _container . getDOMNode ( ) ) ;
2014-11-17 23:18:24 +01:00
attrs . row = Math . max ( 1 , Math . round ( ( action . menu _context . posy - $portlet _container . offset ( ) . top ) / this . GRID ) + 1 ) ;
2014-11-10 19:44:11 +01:00
attrs . col = Math . max ( 1 , Math . round ( ( action . menu _context . posx - $portlet _container . offset ( ) . left ) / this . GRID ) + 1 ) ;
}
2013-06-05 00:34:21 +02:00
2013-05-22 22:13:12 +02:00
var portlet = et2 _createWidget ( 'portlet' , attrs , this . portlet _container ) ;
portlet . loadingFinished ( ) ;
2014-11-25 22:50:42 +01:00
// Immediately add content ID so etemplate loads into the right place
portlet . content . append ( '<div id="' + attrs . id + '" class="et2_container"/>' ) ;
2013-05-22 22:13:12 +02:00
// Get actual attributes & settings, since they're not available client side yet
2014-11-19 00:46:58 +01:00
portlet . _process _edit ( et2 _dialog . OK _BUTTON , attrs ) ;
2013-05-22 22:13:12 +02:00
// Set up sorting/grid of new portlet
var $portlet _container = $j ( this . portlet _container . getDOMNode ( ) ) ;
$portlet _container . data ( "gridster" ) . add _widget (
2013-06-05 00:34:21 +02:00
portlet . getDOMNode ( ) ,
this . DEFAULT . WIDTH , this . DEFAULT . HEIGHT ,
attrs . col , attrs . row
2013-05-22 22:13:12 +02:00
) ;
} ,
2013-05-23 00:44:27 +02:00
/ * *
* User dropped something on home . Add a new portlet
* /
2014-11-12 00:07:35 +01:00
add _from _drop : function ( action , source ) {
2013-05-29 21:25:12 +02:00
// Actions got confused drop vs popup
if ( source [ 0 ] . id == 'portlets' )
{
return this . add ( action ) ;
}
var $portlet _container = $j ( this . portlet _container . getDOMNode ( ) ) ;
// Basic portlet attributes
2014-11-19 00:46:58 +01:00
var attrs = {
id : this . _create _id ( ) ,
class : action . data . class || action . id . substr ( 5 ) ,
width : this . DEFAULT . WIDTH ,
height : this . DEFAULT . HEIGHT
} ;
2013-05-29 21:25:12 +02:00
// Try to find where the drop was
if ( action != null && action . ui && action . ui . position )
{
2014-11-17 23:18:24 +01:00
attrs . row = Math . max ( 1 , Math . round ( ( action . ui . position . top - $portlet _container . offset ( ) . top ) / this . GRID ) ) ;
attrs . col = Math . max ( 1 , Math . round ( ( action . ui . position . left - $portlet _container . offset ( ) . left ) / this . GRID ) ) ;
2013-05-29 21:25:12 +02:00
}
2013-05-23 00:44:27 +02:00
var portlet = et2 _createWidget ( 'portlet' , attrs , this . portlet _container ) ;
2014-11-25 23:42:44 +01:00
portlet . loadingFinished ( ) ;
2014-11-25 22:50:42 +01:00
// Immediately add content ID so etemplate loads into the right place
portlet . content . append ( '<div id="' + attrs . id + '" class="et2_container"/>' ) ;
2013-05-23 00:44:27 +02:00
// Get actual attributes & settings, since they're not available client side yet
var drop _data = [ ] ;
for ( var i = 0 ; i < source . length ; i ++ )
{
2014-11-12 00:07:35 +01:00
if ( source [ i ] . id )
{
drop _data . push ( source [ i ] . id ) ;
}
else
{
drop _data . push ( source [ i ] . data ) ;
}
2013-05-23 00:44:27 +02:00
}
2014-11-19 00:46:58 +01:00
portlet . _process _edit ( et2 _dialog . OK _BUTTON , jQuery . extend ( { dropped _data : drop _data } , attrs ) ) ;
2013-05-23 00:44:27 +02:00
// Set up sorting/grid of new portlet
$portlet _container . data ( "gridster" ) . add _widget (
2013-05-29 21:25:12 +02:00
portlet . getDOMNode ( ) ,
this . DEFAULT . WIDTH , this . DEFAULT . HEIGHT ,
attrs . col , attrs . row
2013-05-23 00:44:27 +02:00
) ;
} ,
2014-11-12 22:33:42 +01:00
/ * *
* Allow a refresh from anywhere by triggering an update with no changes
*
* @ param { string } id
* /
refresh : function ( $id ) {
var p = this . portlet _container . getWidgetById ( $id ) ;
if ( p )
{
p . _process _edit ( et2 _dialog . OK _BUTTON , '~reload~' ) ;
}
} ,
2013-05-22 22:13:12 +02:00
/ * *
* For link _portlet - opens the configured record when the user
* double - clicks or chooses view from the context menu
* /
open _link : function ( action ) {
// Get widget
var widget = null ;
while ( action . parent != null )
{
if ( action . data && action . data . widget )
{
widget = action . data . widget ;
break ;
}
action = action . parent ;
}
if ( widget == null )
{
egw ( ) . log ( "warning" , "Could not find widget" ) ;
return ;
}
2014-11-25 01:45:14 +01:00
egw ( ) . open ( widget . options . settings . entry , "" , 'view' , null , widget . options . settings . entry . app ) ;
2013-05-22 22:13:12 +02:00
} ,
/ * *
* Set up the drag / drop / re - order of portlets
* /
_do _ordering : function ( ) {
var $portlet _container = $j ( this . portlet _container . getDOMNode ( ) ) ;
$portlet _container
. addClass ( "home ui-helper-clearfix" )
. disableSelection ( )
/* Gridster */
. gridster ( {
widget _selector : 'div.et2_portlet' ,
2013-05-29 21:25:12 +02:00
// Dimensions + margins = grid spacing
widget _base _dimensions : [ this . GRID - 5 , this . GRID - 5 ] ,
2013-05-22 22:13:12 +02:00
widget _margins : [ 5 , 5 ] ,
extra _rows : 1 ,
extra _cols : 1 ,
min _cols : 3 ,
min _rows : 3 ,
/ * *
* Set which parameters we want when calling serialize ( ) .
* @ param $w jQuery jQuery - wrapped element
* @ param grid Object Grid settings
* @ return Object - will be returned by gridster . serialize ( )
* /
serialize _params : function ( $w , grid ) {
2013-11-04 21:54:23 +01:00
return {
2014-11-12 22:33:42 +01:00
id : $w . attr ( 'id' ) . replace ( app . home . portlet _container . getInstanceManager ( ) . uniqueId + '_' , '' ) ,
2013-11-04 21:54:23 +01:00
row : grid . row ,
col : grid . col ,
2014-11-06 22:40:03 +01:00
width : grid . size _x ,
height : grid . size _y
2013-05-22 22:13:12 +02:00
} ;
} ,
/ * *
* Gridster ' s internal drag settings
* /
draggable : {
handle : '.ui-widget-header' ,
stop : function ( event , ui ) {
// Update widget(s)
var changed = this . serialize _changed ( ) ;
2014-11-06 22:40:03 +01:00
// Reset changed, or they keep accumulating
this . $changed = $j ( [ ] ) ;
2013-05-22 22:13:12 +02:00
for ( var key in changed )
{
if ( ! changed [ key ] . id ) continue ;
2014-11-06 22:40:03 +01:00
// Changed ID is the ID
var widget = window . app . home . portlet _container . getWidgetById ( changed [ key ] . id ) ;
2013-05-22 22:13:12 +02:00
if ( ! widget || widget == window . app . home . portlet _container ) continue ;
2014-11-06 22:40:03 +01:00
egw ( ) . jsonq ( "home.home_ui.ajax_set_properties" , [ changed [ key ] . id , widget . options . settings , {
2013-05-22 22:13:12 +02:00
row : changed [ key ] . row ,
col : changed [ key ] . col
} ] ,
null ,
widget , true , widget
2013-05-29 21:25:12 +02:00
) ;
2013-05-22 22:13:12 +02:00
}
}
}
} ) ;
2014-11-06 22:40:03 +01:00
// Bind window resize to re-layout gridster
$j ( window ) . one ( "resize." + this . et2 . _inst . uniqueId , function ( ) {
// Note this doesn't change the positions, just makes them invalid
$portlet _container . data ( 'gridster' ) . recalculate _faux _grid ( ) ;
} ) ;
2013-05-22 22:13:12 +02:00
// Bind resize to update gridster - this may happen _before_ the widget gets a
// chance to update itself, so we can't use the widget
$portlet _container
. on ( "resizestop" , function ( event , ui ) {
$portlet _container . data ( "gridster" ) . resize _widget (
ui . element ,
2013-06-10 17:05:21 +02:00
Math . round ( ui . size . width / app . home . GRID ) ,
Math . round ( ui . size . height / app . home . GRID )
2013-05-22 22:13:12 +02:00
) ;
} ) ;
} ,
/ * *
* Create an ID that should be unique , at least amoung a single user ' s portlets
* /
_create _id : function ( ) {
var id = '' ;
do
{
id = Math . floor ( ( 1 + Math . random ( ) ) * 0x10000 )
. toString ( 16 )
. substring ( 1 ) ;
}
while ( this . portlet _container . getWidgetById ( id ) ) ;
return id ;
2013-05-29 21:25:12 +02:00
} ,
/ * *
* Functions for the list portlet
* /
2014-11-19 00:46:58 +01:00
/ * *
* For list _portlet - opens a dialog to add a new entry to the list
*
* @ param { egwAction } action Drop or add action
* @ param { egwActionObject [ ] } Selected entries
* @ param { egwActionObject } target _action Drop target
* /
add _link : function ( action , source , target _action ) {
// Actions got confused drop vs popup
if ( source [ 0 ] . id == 'portlets' )
{
return this . add _link ( action ) ;
}
2013-05-29 21:25:12 +02:00
2014-11-19 00:46:58 +01:00
// Get widget
var widget = null ;
while ( action . parent != null )
{
if ( action . data && action . data . widget )
2013-05-29 21:25:12 +02:00
{
2014-11-19 00:46:58 +01:00
widget = action . data . widget ;
break ;
2013-05-29 21:25:12 +02:00
}
2014-11-19 00:46:58 +01:00
action = action . parent ;
}
if ( target _action == null )
{
// use template base url from initial template, to continue using webdav, if that was loaded via webdav
var splitted = 'home.edit' . split ( '.' ) ;
var path = app . home . portlet _container . getRoot ( ) . _inst . template _base _url + splitted . shift ( ) + "/templates/default/" +
splitted . join ( '.' ) + ".xet" ;
var dialog = et2 _createWidget ( "dialog" , {
callback : function ( button _id , value ) {
if ( button _id == et2 _dialog . CANCEL _BUTTON ) return ;
var new _list = widget . options . settings . list || [ ] ;
for ( var i = 0 ; i < new _list . length ; i ++ )
2014-11-06 22:40:03 +01:00
{
2014-11-19 00:46:58 +01:00
if ( new _list [ i ] . app == value . add . app && new _list [ i ] . id == value . add . id )
2014-11-06 22:40:03 +01:00
{
// Duplicate - skip it
2014-11-19 00:46:58 +01:00
return ;
2014-11-06 22:40:03 +01:00
}
}
2014-11-19 00:46:58 +01:00
value . add . link _id = value . add . app + ':' + value . add . id ;
// Update server side
new _list . push ( value . add ) ;
widget . _process _edit ( button _id , { list : new _list } ) ;
// Update client side
widget . getWidgetById ( 'list' ) . set _value ( new _list ) ;
} ,
buttons : et2 _dialog . BUTTONS _OK _CANCEL ,
title : app . home . egw . lang ( 'add' ) ,
template : path ,
value : { content : [ { label : app . home . egw . lang ( 'add' ) , type : 'link-entry' , name : 'add' , size : '' } ] }
} ) ;
}
else
{
// Drag'n'dropped something on the list - just send action IDs
var new _list = widget . options . settings . list || [ ] ;
var changed = false ;
for ( var i = 0 ; i < new _list . length ; i ++ )
{
// Avoid duplicates
for ( var j = 0 ; j < source . length ; j ++ )
2014-11-17 23:18:24 +01:00
{
2014-11-19 00:46:58 +01:00
if ( ! source [ j ] . id || new _list [ i ] . app + "::" + new _list [ i ] . id == source [ j ] . id )
2014-11-17 23:18:24 +01:00
{
2014-11-19 00:46:58 +01:00
// Duplicate - skip it
source . splice ( j , 1 ) ;
2014-11-17 23:18:24 +01:00
}
}
2013-05-29 21:25:12 +02:00
}
2014-11-19 00:46:58 +01:00
for ( var i = 0 ; i < source . length ; i ++ )
{
var explode = source [ i ] . id . split ( '::' ) ;
new _list . push ( { app : explode [ 0 ] , id : explode [ 1 ] , link _id : explode . join ( ':' ) } ) ;
changed = true ;
}
if ( changed )
{
widget . _process _edit ( et2 _dialog . OK _BUTTON , {
list : new _list || { }
} ) ;
}
// Filemanager support - links need app = 'file' and type set
for ( var i = 0 ; i < new _list . length ; i ++ )
{
if ( new _list [ i ] [ 'app' ] == 'filemanager' )
{
new _list [ i ] [ 'app' ] = 'file' ;
new _list [ i ] [ 'path' ] = new _list [ i ] [ 'title' ] = new _list [ i ] [ 'icon' ] = new _list [ i ] [ 'id' ] ;
}
}
widget . getWidgetById ( 'list' ) . set _value ( new _list ) ;
2013-05-29 21:25:12 +02:00
}
2014-11-12 22:33:42 +01:00
} ,
2014-11-19 00:46:58 +01:00
/ * *
* Remove a link from the list
* /
link _change : function ( list , link _id , row ) {
// Quick response client side
row . slideUp ( row . remove ) ;
// Actual removal
var portlet = list . _parent . _parent ;
portlet . options . settings . list . splice ( row . index ( ) , 1 ) ;
portlet . _process _edit ( et2 _dialog . OK _BUTTON , { list : portlet . options . settings . list || { } } ) ;
} ,
2014-11-12 22:33:42 +01:00
/ * *
* Functions for the note portlet
* /
/ * *
* Set up for editing a note
* CKEditor has CSP issues , so we need a popup
*
* @ param { egwAction } action
* @ param { egwActionObject [ ] } Selected
* /
note _edit : function ( action , selected ) {
2014-11-12 22:54:58 +01:00
if ( ! selected && typeof action == 'string' )
{
var id = action ;
}
else
{
var id = selected [ 0 ] . id ;
}
2014-11-12 22:33:42 +01:00
// Aim to match the size
var portlet _dom = $j ( '[id$=' + id + '][data-sizex]' , this . portlet _container . getDOMNode ) ;
var width = portlet _dom . attr ( 'data-sizex' ) * this . GRID ;
var height = portlet _dom . attr ( 'data-sizey' ) * this . GRID ;
// CKEditor is impossible to use below a certain size
// Add 35px for the toolbar, 35px for the buttons
var window _width = Math . max ( 580 , width + 20 ) ;
var window _height = Math . max ( 350 , height + 70 ) ;
2014-11-17 19:47:47 +01:00
// Open popup, but add 70 to the height for the toolbar
2014-11-12 22:33:42 +01:00
egw . open _link ( egw . link ( '/index.php' , {
menuaction : 'home.home_note_portlet.edit' ,
2014-11-12 22:54:58 +01:00
id : id ,
2014-11-12 22:33:42 +01:00
height : window _height - 70
2014-11-12 22:54:58 +01:00
} ) , 'home_' + id , window _width + 'x' + window _height , 'home' ) ;
2014-11-19 00:46:58 +01:00
} ,
/ * *
* Favorites / nextmatch
* /
/ * *
* Toggle the nextmatch header shown / hidden
*
* @ param { Event } event
* @ param { et2 _button } widget
* /
nextmatch _toggle _header : function ( event , widget ) {
2014-11-25 01:45:14 +01:00
widget . set _class ( widget . class == 'opened' ? 'closed' : 'opened' ) ;
2014-11-19 00:46:58 +01:00
// We operate on the DOM here, nm should be unaware of our fiddling
var nm = widget . getParent ( ) . getWidgetById ( 'nm' ) ;
if ( ! nm ) return ;
// Hide header
nm . div . toggleClass ( 'header_hidden' ) ;
2014-11-25 01:45:14 +01:00
nm . set _hide _header ( nm . div . hasClass ( 'header_hidden' ) ) ;
2014-11-25 22:50:42 +01:00
nm . resize ( ) ;
2013-05-22 22:13:12 +02:00
}
} ) ;