| 1382 | |
| 1383 | // Webkit requires textual content within a selection in order to apply styles to it (#3708) |
| 1384 | // webkitFix provides fixApplyStyle and fixRemoveStyle which (mostly) mitigate |
| 1385 | // webkit's difficulties with styling collapsed selections. BugzID: 1904972 |
| 1386 | var webkitFix = (function() { |
| 1387 | |
| 1388 | var chBogus = '\ufeff'; //Zero-width non-breaking space. |
| 1389 | |
| 1390 | function isControlCode(evt) { |
| 1391 | var keyCode = evt.data.getKey(); |
| 1392 | |
| 1393 | return ( |
| 1394 | evt.data.$.ctrlKey || |
| 1395 | evt.data.$.altKey || |
| 1396 | $.inArray(keyCode, [ |
| 1397 | 16, // Shift |
| 1398 | 17, // Ctrl |
| 1399 | 18, // Alt |
| 1400 | 20 // Caps Lock |
| 1401 | ]) !== -1 |
| 1402 | ); |
| 1403 | } |
| 1404 | |
| 1405 | //injectBogusNode fixes adding and removing styles from collapsed selections by injecting a bogus character into |
| 1406 | //the current selection so that it is no longer collapsed. A callback to createFxnCleanup is provided to allow |
| 1407 | //the bogus-character cleanup code to be customized based on whether the style is being applied or removed. |
| 1408 | //createInjectBogusNode is used to instantiate versions of the injectBogusNode function that are tailored to |
| 1409 | //specific style add or remove tasks. |
| 1410 | function createInjectBogusNode(fxnCleanUp) { |
| 1411 | return function(el, range) { |
| 1412 | function onInputRemoveBogus(evt) { |
| 1413 | // Skip control keys |
| 1414 | if (isControlCode(evt)) { |
| 1415 | return; |
| 1416 | } |
| 1417 | |
| 1418 | //Valid input- kill the handlers. |
| 1419 | doc.removeListener('keydown', onInputRemoveBogus); |
| 1420 | doc.removeListener('mouseup', onInputRemoveBogus); |
| 1421 | |
| 1422 | //The rest of this work must be done inside a timeout, |
| 1423 | //since its state should reflect whatever happens after this event finishes. |
| 1424 | setTimeout(function() { fxnCleanUp(el, range, elBogus) }, 1); |
| 1425 | } |
| 1426 | |
| 1427 | var doc = range.document; |
| 1428 | var elBogus = doc.createText(chBogus); |
| 1429 | |
| 1430 | el.append(elBogus); |
| 1431 | doc.on('keydown', onInputRemoveBogus); |
| 1432 | doc.on('mouseup', onInputRemoveBogus); |
| 1433 | }; |
| 1434 | } |
| 1435 | |
| 1436 | function safeMoveRange(range, elBogus) { |
| 1437 | try { |
| 1438 | range.moveToPosition(elBogus, CKEDITOR.POSITION_AFTER_END); |
| 1439 | } catch (e) { } |
| 1440 | } |
| 1441 | |
| 1442 | //fixRemove operates on a text range and an editing context. If the bogusnode trick is necessary, |
| 1443 | //we'll invoke injectBogusNode, otherwise we'll just let the editor proceed. |
| 1444 | var fixRemove = (function() { |
| 1445 | var injectBogusNode = createInjectBogusNode(function(el, range, elBogus) { |
| 1446 | if (elBogus.getText() === chBogus) { |
| 1447 | safeMoveRange(range, elBogus); |
| 1448 | elBogus.remove(); |
| 1449 | if (!editor.ck.getSelection().getRanges().length) { |
| 1450 | range.select(); |
| 1451 | } |
| 1452 | } |
| 1453 | }); |
| 1454 | |
| 1455 | return function(range, context) { |
| 1456 | if (range.collapsed) { |
| 1457 | //Webkit can't handle style changes to collapsed ranges. |
| 1458 | //Let's insert a dummy node into the range and instead apply the style to that. |
| 1459 | var el = getElement(context, range.document); |
| 1460 | injectBogusNode(el, range); |
| 1461 | range.insertNode(el); |
| 1462 | } |
| 1463 | } |
| 1464 | } ()); |
| 1465 | |
| 1466 | //fixApply operates on an element and a selection range, and is assuming that |
| 1467 | //the range is already collapsed. It can call directly to injectBogusNode. |
| 1468 | var fixApply = createInjectBogusNode(function(el, range, elBogus) { |
| 1469 | var elBogus = el.getFirst(); |
| 1470 | if (!elBogus) { |
| 1471 | return; |
| 1472 | } |
| 1473 | |
| 1474 | // If there're characters inside the text node aside from bogus, |
| 1475 | // we saved the day! Bogus is no longer needed and can be removed. |
| 1476 | if (elBogus.getText().length !== 1) { |
| 1477 | // The bogus text has been merged with newly inputted char, |
| 1478 | // split it out, move selection after it, then remove it. |
| 1479 | elBogus = elBogus.split(1).getPrevious(); |
| 1480 | safeMoveRange(range, elBogus); |
| 1481 | elBogus.remove(); |
| 1482 | range.select(); |
| 1483 | } |
| 1484 | // We've felt a keypress, but the bogus element hasn't expanded. |
| 1485 | // This means that the selection has moved or we aborted the style. Kill bogus. |
| 1486 | else { |
| 1487 | elBogus.remove(); |
| 1488 | if (!editor.ck.getSelection().getRanges().length) { |
| 1489 | //If we just removed from within our range, we've lost focus. |
| 1490 | //Reselect our range. |
| 1491 | range.select(); |
| 1492 | } |
| 1493 | } |
| 1494 | }); |
| 1495 | |
| 1496 | return { |
| 1497 | fixApplyStyle: fixApply, |
| 1498 | fixRemoveStyle: fixRemove |
| 1499 | }; |
| 1500 | } ()); |