-
Notifications
You must be signed in to change notification settings - Fork 3
/
MIGRATING.txt
620 lines (526 loc) · 24.7 KB
/
MIGRATING.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
Migrating from 1.2 to 1.3
=========================
1.3 is a bug fixing release with new functions and breaking changes.
Most important new features are the new batch() system and hook priorities^.
Bug fixes:
* once() now passes on() the given cx (previously it passed none, i.e. this)
* Fixed autoOff()'s processing of cx:
* events' functions are called in caller-provided cx
* cx given to on() is always this
* previously, cx of both was sqim if autoOff() was given 2 arguments,
or this if more (was ignoring caller-provided cx)
* Fixed _unregHandler() not restoring hooks of an unhooked =wrapped hook that
in turn was also =wrapped (but still hooked)
* ...which also led to them being unregistrable from _eventsByID/_eventsByCx
* Fixed unregistering all hooks when doing off('event') in presence of
=wrapped hooks with still-registered hooks in supList
* Improved Core::fire():
* doing nothing if funcs is !isArray
* correctly calling supList of an unhooked =wrapped handler
* not calling eobj.post if func wasn't called (unhooked or mismatching ev__)
* Fixed bugs in Ordered::indexFor()
* Fixed cloning of properties declared in a subclass if an instance of its
superclass was instantiated before subclass declaration: Superclass.extend()
* Added cloning of instance properties of mix-in applied to instance, not class
* expandFunc() now treats '.' and '-' as part of method name (not mask) if
they are followed by [^\d.-]
* Fixed unlist() removing wrong child by casting null/undefined key to string
* ...also happened if it was given an object that wasn't part of own children
* Added internal _nested/_unnested methods for Ordered to ensure consistent
state of _children and _ordered for user hooks on nestEx and unnested
Improvements:
* parseEvent() no longer restricts event name to [\w\d.:+\-=]
* Improved performance of object instantiation-time cloning of class
properties by 2-3X (new Core::deepCloner() function)
* init() now resolves relative _childClass once per class instantiation,
writing resolved class to own prototype
* Default subclass name (when no name was given to extend()) is the
superclass' name, not the string "Sqimitive"
* _event key is now deleted if no active handlers remain for that event
* _owning findKey() no longer iterates over own children when given an object
* Ordered no longer calls _repos if child's index in _ordered didn't change
* Ordered no longer calls _sorter to compare the object with itself
New features:
* Reworked former Base's deferring of change/change_OPT events (and removed
_firingSet and _fireSet()) into a Core batch system with new fields:
batch(), ::batchGuard(), _batchOptions(),
_batches, _batch, _batchID, _batched - internal properties
* Added hook priorities: '123^<event>' to maintain hook's position relative to
others with different priorities and '-prefixes' added later
* unnested() now receives former child's key as second argument
* assignChildren()'s keyFunc now receives response object as second argument
* Added new assignChildren() options: newFunc (ex. classFUnc), posFunc, unFunc
* picker() now accepts dot-path, list of arguments for functions, etc.
* Added getSet() cx default of this (previously called func in globalThis)
* ...and made it return pre-toSet values for toReturn keys with leading '-'
* Added default cx of this to all #util-s
* once() now allows retaining the hook if it returns a special value
* autoOff() now allows iterating over tracked objects, unlisting an object
without unhooking and hooking with an array in addition to object
* nest() now accepts a plain object as sqim to construct sqim from _childClass
* Replaced calls to _.isEqual() with new Base.isEqual() using === by default
* Added Ordered nestEx options.repos to re-evaluate child position
* Added _initToOpt to control assignment of opt given to new/constructor/init()
* Moved _forward() from Base to Core and added an optional prepend parameter
* assignResp() options.schema may now be a string (name of property on this)
* assignResp() sets this.prop from leading '.' in schema's key
* Added #util forEach() (in addition to non-standard but common each())
* Added Base.sort() method
* Added Ordered methods: shift(), pop(), reverse()
* Ordered._indexFor() and ::indexFor() now have new fifth oldIndex parameter
* Added Core._mustClone() method to determine properties to clone
* sup.name is now set to the name of event the =handler was added for
* Added Core._wrapUndeclared to not create firers in missing properties
* Added Core::trace flag and many debug info fields (Core::lastFired, etc.)
Breaking changes:
* Renamed sqimitive.js to main.js, sqimitive-async/jquery.js to async/jquery.js
=> if using npm, the require('sqimitive') call will continue to function
=> if using npm or Require.js, instead of:
define(['sqimitive/sqimitive'], function (Sqimitive) { ... })
var jQuery = require('sqimitive/sqimitive-jquery')
do:
define(['sqimitive/main'], function (Sqimitive) { ... })
var jQuery = require('sqimitive/jquery')
=> if using <script>, instead of:
<script src="sqimitive/sqimitive-async.js"></script>
do:
<script src="sqimitive/async.js"></script>
* Calling autoOff() with one argument now unlists and unhooks that sqimitive
rather than listing it without adding hooks
=> instead of:
this.autoOff(sqim).once(...)
do:
this.autoOff(sqim, {}).once(...)
* assignResp() now returns list of changed properties rather than this
=> instead of:
col.nest(sqim.assignResp(...))
do:
sqim.assignResp(...)
col.nest(sqim)
* Removed assignChildren() options.classFunc, replaced with new newFunc
=> instead of:
col.assignChildren(..., {classFunc: resp => SomeClass})
do:
col.assignChildren(..., {newFunc: resp => new SomeClass})
* Added _childEvents to _mergeProps
=> given this:
var Super = Sqimitive.Core.extend({
_childEvents: ['sup'],
})
var Sub = Super.extend({
_childEvents: ['sub'],
})
;(new Sub)._childEvents //=> ['sub']
update _mergeProps immediately after calling extend() on the superclass:
Sub._mergeProps.splice(Sub._mergeProps.indexOf('_childEvents'), 1)
if not done:
;(new Sub)._childEvents //=> ['sup', 'sub']
* Removed non-standard #util alias contains(), replaced with new includes()
=> instead of:
col.contains(sqim)
do:
col.includes(sqim)
* Made Core.firer() static
=> instead of:
this.firer('event', ...)
do one of this:
Sqimitive.Core.firer('event', ...)
this.constructor.firer('event', ...)
* picker()'s string is now a dot-path (prop.sub.it) or a pre-split array
=> instead of:
picker('x.y')({'x.y': ...})
do:
picker(['x.y'])({'x.y': ...})
* picker()'s args is now a list of arguments, not arguments for (one) call
=> instead of:
picker('method', [1, 2])({method: (a, b) { ... }})
do:
picker('method', [[1, 2]])({method: (a, b) { ... }})
=> instead of:
picker('method', [1])({method: (a) { ... }})
do one of this:
picker('method', 1)({method: (a) { ... }})
picker('method', [[1]])({method: (a) { ... }})
* if picker()'s returned function is given a function, that function is called
and descended into rather than undefined returned
=> before:
picker('length')(new Function) //=> undefined
now:
picker('length')(new Function) //=> 0
* picker()'s returned function doesn't call functions with names starting with
a capital letter (considering them constructors/class names)
=> before:
picker('x')({x: Date}) //=> 'Sun Dec 11 ...'
now:
picker('x')({x: Date}) //=> Date
* Replaced calls to _.isEqual() with new Base.isEqual() using === by default;
this change affects ifSet() (set()) and Ordered's test of pos in nestEx
=> before:
sqim.on('change_opt', () => console.log('changed'))
sqim.set('opt', 1) //=> logged
sqim.set('opt', '1')
now:
sqim.set('opt', 1) //=> logged
sqim.set('opt', '1') //=> logged
=> restore old behaviour by overriding isEqual:
events: {'=isEqual': (res, a, b) => _.isEqual(a, b)}
sqim.on('=isEqual', (res, a, b) => _.isEqual(a, b))
* _wrapHandler() no longer creates firer() if event's name has a \W
=> if using such events as methods:
var Class = Sqimitive.Core.extend({
events: {'foo:bar': function ...}
})
var sqim = new Class
sqim['foo:bar']()
sqim.sink('foo:bar')
add an explicit method:
var Class = new Sqimitive.Core({
events: /* as before */,
'foo:bar': function () { this.fire('foo:bar', arguments) }
// or:
'foo:bar': Sqimitive.Core.firer('foo:bar')
})
* normalize_OPT's undefined result doesn't change ifSet()'s value but keeps it
=> ifSet() has to be overridden to allow normalize_OPT set value to undefined
* Giving extend() second argument (staticProps) now replaces value of first
argument's (protoProps) staticProps key; previously, staticProps argument
was applied before applying MixIns which led to counterintuitive results
=> instead of:
var MyClass = Sqimitive.Core.extend({staticProps: {...}}, {...})
do one of this:
var MyClass = Sqimitive.Core.extend({staticProps: {...}})
var MyClass = Sqimitive.Core.extend({}, {...})
Minor breaking changes:
* Changed format of _autoOff from array to Set (can be null)
=> instead of:
var a = this._autoOff.map(...)
do:
var a = []
;(this._autoOff || []).forEach(... a.push(...))
* Core::fire() and Core.fire() now accept an internal third argument
=> don't accidentally provide it, e.g. if using fire() as a forEach callback
* _respToOpt and schema object of assignResp() now recognize leading '.'
in key's string value and in key's Function value's returned array[0]:
leading '.' sets respValue to this[substr(1)], sole '.' - to this[respKey];
previously such values meant the same as others, i.e. set() of _opt['...']
=> instead of (same with '.opt' replaced with '.'):
_respToOpt: {'.opt': true}
_respToOpt: {respKey: '.opt'}
_respToOpt: {respKey: respValue => {return ['.opt', respValue]}}
do:
_respToOpt: {'.opt': respValue => {this.set('.opt', respValue)}}
_respToOpt: {respKey: respValue => {this.set('.opt', respValue)}}
// same for return ['.opt', ...]
* getSet() now ignores leading dash '-' in toGet/toSet/toReturn members;
'-' (-opt) in toReturn causes opt's value be get() before processing toSet
=> instead of:
var res = getSet('-x', '-y', '-z')
do:
set('-y', get('-x'))
var res = get(-z')
* unique() is used to generate these new internal prefixes in addition to 'p':
'e', 'o', 'b', 'bg'
* These functions now defer events of all processed _opt'ions (run in a batch):
init(), getSet(), assignResp() (+ assignChildren())
=> before:
sqim.on('change_x', () => console.log(sqim.get('y')))
sqim.assignResp({x: 1, y: 2}) //=> undefined
now:
sqim.assignResp({x: 1, y: 2}) //=> 2
* Non-_owning Ordered.unlist() when given an object now removes only one
(arbitrary) child entry, not all of them, in line with Base.unlist()
=> instead of:
var Class = Sqimitive.Base.extend({
mixIns: [Sqimitive.Ordered],
_owning: false
})
var col = new Class
var sqim = new Class
col.nest(sqim)
col.nest(sqim)
col.unlist(sqim)
col.length //=> 0
do:
col.nest(sqim)
col.nest(sqim)
col.unlist(sqim)
col.length //=> 1
while (col.unlist(key)) ;
* Changed format of eobj.id and of on()'s result from string to null
=> before:
on('foo', function ...) //=> 'foo/123'
now:
on('foo', function ...) //=> 4567
* eobj can now have null func (unhooked) and has new keys:
batch, priority, prefix, trace
* parseEvent() returns new keys (batch, priority, trace); name became event
=> instead of:
parseEvent('foo').name
do:
parseEvent('foo').event
* parseEvent() returns args as a number or null rather than string of '_'s
=> before:
parseEvent('ev').args //=> ''
parseEvent('ev__').args //=> '__'
now:
parseEvent('ev').args //=> null
parseEvent('ev__').args //=> 2
* Removed _cxHandlers() and changed format of _eventsByCx from array to Map
=> instead of:
_cxHandlers(sqim, ...)
do:
_eventsByCx.get(sqim)
* Leading '^' and '^number^'/'number^' now have special meaning in event names
(parseEvent(), on(), etc.): '^.ev', '^-1^+ev', '+10-ev', .etc.
* _wrapHandler() now treats a defined property with undefined value as stub
(previously it would skip creating firer() as for any non-Function)
=> before:
var Class = Sqimitive.Core.extend({
undef: undefined,
})
var sqim = new Class
// or:
var sqim = new Sqimitive.Core
sqim.undef = undefined
// then:
sqim.on('undef', ...)
sqim.undef //=> undefined
sqim.fire('undef', [...])
now:
// ...
// then:
sqim.on('undef', ...)
sqim.undef //=> Function
sqim.undef(...) // same as fire()
sqim.fire('undef', [...])
* _wrapHandler() now inserts defined property's Function value after existing
handlers (with 0 priority), not -before
=> this should be impossible to meet on normal usage because _wrapHandler()
is executed when adding a first handler but before it's actually added
* _unregHandler() takes new parameters (options, eobj)
=> instead of:
_unregHandler(eobj)
do:
_unregHandler({}, eobj)
* _unregHandler() may no longer be called with null or unregistered eobj
=> instead of:
_unregHandler( this._eventsByID['non-existing key'] )
do:
var eobj = this._eventsByID['non-existing key']
eobj && _unregHandler({}, eobj)
=> instead of:
_unregHandler(eobj)
// later, again:
_unregHandler(eobj)
do:
eobj.func && _unregHandler({}, eobj)
// later, again:
eobj.func && _unregHandler({}, eobj)
* ifSet() (set()) now writes value to _opt only if events were fired;
previously it wrote the value, then checked for forceFire and !isEqual
=> before:
sqim.ifSet('opt', 1)
_.isEqual(1, '1') //=> true
sqim.ifSet('opt', '1') //=> false
sqim._opt.opt //=> '1'
now:
sqim.isEqual = _.isEqual
sqim.ifSet('opt', 1)
sqim.ifSet('opt', '1') //=> false
sqim._opt.opt //=> 1
* Removed key parameter of Ordered._indexOf()
* Changed format of _copyProps from array to Function
Migrating from 1.0 to 1.2
=========================
(1.1 is a transitive version and should not be used.)
1.2 is a mature release with multiple breaking changes. demo/to-do.* were
updated to reflect the most important one (the introduction of Ordered).
Improvements and bug fixes:
* Reworked the innards of instantiation mechanism (extend())
* Improved performance of instantiation (constructor, deepClone()) and events
* No longer requiring Underscore.js - can be replaced with NoDash or LoDash
* Properly isolated from window, supporting AMD and CommonJS thanks to
Universal Module Header (umh.js)
* Enabled strict mode for all scripts, avoided arguments.callee and such
* Fixed hooking events (fuse()) with names of standard properties: 'toString'
* Fixed improper length calculation if not _owning and nesting over an
existing key (replacing)
* Fixed cloning of _mergeProps and _shareProps by extend()
* Fixed sqim._() returning _.chain(), not just _()
New features:
* Added mix-ins (Sqimitive.Core's static and instance mixIn() methods)
* Added nestEx() method (nest() calls it) with a more convenient semantics for
hooking (single argument format and detailed return value)
* Allowed _childClass to be an indirect reference (array or string)
* Improved assignChildren():
* added options.childClass instead of always using _childClass
* now returning array of nested and removed sqimitives
* removed handling of resp.data:
https://flask.palletsprojects.com/en/1.1.x/security/#json-security
* Improved assignResp():
* now setting options.assignResp
* added options.schema instead of always using _respToOpt
* added special _respToOpt[''] key
* _respToOpt function values may now return non-array
* Added Base.getSet(), findKey() methods
* Event handlers can now specify post-action callback (eobj.post property)
* Added Base.chunk() and Ordered.findIndex()/findLastIndex() helper methods
* Added Sqimitive._, Sqimitive.jQuery.$ properties, Sqimitive.Base._() method
* Added ability to give names to constructors (extend()) to aid in debugging
* Added Sqimitive.Async class as an alternative to "promises"
Breaking changes:
* Removed Sqimitive.Sqimitive (used to be an alias of Sqimitive.Base)
=> instead of:
var MyClass = Sqimitive.Sqimitive.extend(...)
do:
var MyClass = Sqimitive.Base.extend(...)
* Extracted DOM-related behaviour from Sqimitive.Base to Sqimitive.jQuery
=> instead of:
var MyClass = Sqimitive.Sqimitive.extend(...)
do:
var MyClass = Sqimitive.jQuery.extend(...)
* If listening to nest() - now listen to nestEx() which is called by the former
=> instead of:
events: {'+nest': function (newChild) { ... }}
do:
events: {'+nestEx': function (res) { var newChild = res.child; ... }}
* Moved away from "optimistically ordered" Sqimitive to new Ordered mix-in:
=> if your sqimitive's children must have stable order, do:
var MyOrdered = Sqimitive.Base.extend({
mixIns: [Sqimitive.Ordered],
...
})
or (outside of class declaration at any time):
MyOrdered.mixIn(Sqimitive.Ordered)
or (for a particular object instance only):
myObj.mixIn(Sqimitive.Ordered)
* Sqimitive.Base.slice() used to treat length parameter as end index; this
was fixed to conform to standard Array's slice()
=> instead of (sqim having 3 children):
sqim.slice(2, 1) that was returning [child3]
do:
sqim.slice(2, 3)
* at() moved from Base to Ordered and return value changed
=> don't rely on children positions in sqimitives without Ordered mix-in
in Ordered, instead of:
osqim.at(2) that was returning child3
do:
osqim.at(2).child
* Helper methods moved from Base to Ordered:
first(), initial(), last(), rest(), indexOf(), lastIndexOf()
* reduceRight() moved from Base to Ordered and made operating on array
=> instead of:
osqim.reduceRight(function (memo, child, key, children) { ... })
do: ^^^ ^^^^^^^^
osqim.reduceRight(function (memo, child, index, array) {
var key = this.at(index).key
var children = this.nested()
...
}, 0, osqim)
* Helper methods now operate on array (Base.slice()), not object (_children):
each(), map(), reduce(), find(), filter(), reject(), every(), some(), max(),
min(), sortBy(), groupBy(), indexBy(), countBy()
=> see above for reduceRight() (omit the first argument, memo):
osqim.each(function (child, index, array) {
var key = this.at(index).key
...
}, osqim)
* Removed Underscore-specific helper methods:
* where()
=> instead of:
sqim.where({length: 1, _some: 2})
that was returning array of children objects
do:
sqim.filter(function (child) {
return child.length == 1 && child._some == 2
})
* findWhere()
=> instead of:
sqim.findWhere({length: 1})
that was returning any one matched children
do:
sqim.filter(function (child) { return child.length == 1 })
* pairs()
=> instead of:
sqim.pairs()
that was returning [ ['k1', child1], ['k2', child2], ... ]
do:
sqim.map(function (child) {
return [this.findKey(child), child]
}, sqim)
* chain()
=> instead of:
sqim.chain().filter(...).invoke(...).value()
do:
sqim._().filter(...).invoke(...).value()
or (if your library doesn't support _() like NoDash):
_.invoke(sqim.filter(...), ...)
or even:
_.invoke(_.filter(sqim.toArray(), ...), ...)
* sortedIndex()
=> use the Ordered mix-in to transparently maintain proper order
* Returning info array in assignChildren(), not this
=> instead of:
sqim.assignChildren(...).somethingMore...
do:
sqim.assignChildren(...); sqim.somethingMore...
* Not handling object parameter with data specially
=> instead of:
sqim.assignChildren({data: [child, child, ...]})
do:
sqim.assignChildren([child, child, ...])
* attach() is using _parent's $(), not repeating look-up if given parent
parameter matched no node, not passing '.ns' part to jQuery.on() (elEvents)
=> these can almost be qualified as bugs; Sqimitive.jQuery must always use
'.sqim-'_cid as a namespace - if you need your own '.ns', do it yourself:
events: {
attach: function () { this.el.on('click.my', ...) },
change_something: function () { this.el.off('.my') },
}
* Not treating 'body' path specially in $()
=> instead of:
_opt: { attachPath: 'body' }
new Sqimitive.Sqimitive({attachPath: 'body'})
sqim.attach('body')
sqim.$('body')
use global $() or document.body:
_opt: { attachPath: $('body') } or _opt: { attachPath: document.body }
new Sqimitive.jQuery({attachPath: $('body') or document.body})
sqim.attach($('body')) or sqim.attach(document.body)
$('body') or $(document.body)
note: document.body is null if executing within <head> before onload:
<head> <script>alert(document.body)</script> </head>
this doesn't affect constructor (opt.el) since it's using global $():
new Sqimitive.jQuery({el: 'body'}) is fine, same as:
new Sqimitive.jQuery({el: $('body')})
* Using declaration-time value of el.class if el.className is falsy
=> if you had:
el: {class: 'one', className: ''}
then el's class would be always '' disregarding the class key
now el's class is 'one'
* Added _respToOpt to default Sqimitive.Base._mergeProps
=> if you had 2 classes:
var C1 = Sqimitive.Sqimitive.extend({ _respToOpt: {foo: true} })
var C2 = C1.extend({ _respToOpt: {bar: false} })
then C2._respToOpt was {bar: false} but is now {foo: true, bar: false}
either remove _respToOpt from your class' _mergeProps:
var C2 = ...
C2._mergeProps = _.without(C2._mergeProps, '_respToOpt')
or change _respToOpt in constructor (less optimal in performance):
var C2 = C1.extend({
init: function () { delete this._respToOpt.foo },
})
* _mergeProps and _shareProps were not previously cloned
=> this is a bug but may impact existing classes (unlikely):
var Sub = Sqimitive.Base.extend()
Sqimitive.Base._mergeProps.length //=> 3
Sub._mergeProps.push('new')
Sqimitive.Base._mergeProps.length //=> 4
* Minimal Underscore.js version increased from 1.4.4 to 1.8.0 or 1.9.0 (if
using chunk() helper)
* window.Sqimitive is replaced entirely, no longer merged if predefined before
script inclusion
=> no workaround - extending Sqimitive is not recommended
---
Squizzle ♥ it
https://squizzle.me