diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 06fefd9cbb..c6ae372c04 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -1,455 +1,468 @@ /** * @provides phabricator-remarkup-css */ .phabricator-remarkup { line-height: 1.45em; } .phabricator-remarkup p { margin: 0 0 12px; } .phabricator-remarkup p:last-child { margin-bottom: 0; } .phabricator-remarkup .remarkup-code-block { margin: 12px; white-space: pre; font-family: "Monaco", monospace; font-size: 10px; } .phabricator-remarkup .remarkup-code-header { padding: 6px 8px; background: {$lightyellow}; color: rgba(0,0,0,.75); font-weight: bold; display: inline-block; border-top: 1px solid {$yellow}; border-left: 1px solid {$yellow}; border-right: 1px solid {$yellow}; margin-bottom: -1px; } .phabricator-remarkup .remarkup-code-block pre { background: {$lightyellow}; border: 1px solid {$yellow}; display: block; color: #000000; overflow: auto; padding: 8px; font-family: "Monaco", monospace; } .phabricator-remarkup pre.remarkup-counterexample { border: 1px solid {$red}; background-color: {$lightred}; } .phabricator-remarkup tt.remarkup-monospaced { color: #333333; background: #ebebeb; padding: 0 4px; white-space: pre-wrap; } /* NOTE: You can currently produce this with [[link | `name`]]. Restore the link color. */ .phabricator-remarkup a tt.remarkup-monospaced { color: #18559d; } .phabricator-remarkup ul.remarkup-list { list-style: disc; margin: 12px 0 12px 30px; } .phabricator-remarkup ol.remarkup-list { list-style: decimal; margin: 12px 0 12px 30px; } .phabricator-remarkup .remarkup-list-with-checkmarks { list-style: none; margin-left: 18px; } .phabricator-remarkup .remarkup-list-with-checkmarks input { margin-right: 2px; } .phabricator-remarkup .remarkup-list-with-checkmarks .remarkup-checked-item { color: {$greytext}; } .phabricator-remarkup ul.remarkup-list ol.remarkup-list, .phabricator-remarkup ul.remarkup-list ul.remarkup-list, .phabricator-remarkup ol.remarkup-list ol.remarkup-list, .phabricator-remarkup ol.remarkup-list ul.remarkup-list { margin: 4px 0 4px 24px; } .phabricator-remarkup li.phantom-item, .phabricator-remarkup li.phantom-item { list-style-type: none; } .phabricator-remarkup h1.remarkup-header:first-child, .phabricator-remarkup h2.remarkup-header:first-child, .phabricator-remarkup h3.remarkup-header:first-child, .phabricator-remarkup h4.remarkup-header:first-child, .phabricator-remarkup h5.remarkup-header:first-child, .phabricator-remarkup h6.remarkup-header:first-child { margin-top: 0; } .phabricator-remarkup h1.remarkup-header:last-child, .phabricator-remarkup h2.remarkup-header:last-child, .phabricator-remarkup h3.remarkup-header:last-child, .phabricator-remarkup h4.remarkup-header:last-child, .phabricator-remarkup h5.remarkup-header:last-child, .phabricator-remarkup h6.remarkup-header:last-child { margin-bottom: 0; } .phabricator-remarkup h1.remarkup-header { font-size: 1.625em; line-height: 1.625em; margin: 8px 0; } .phabricator-remarkup h2.remarkup-header { font-size: 1.5em; line-height: 1.5em; margin: 8px 0; } .phabricator-remarkup h3.remarkup-header { font-size: 1.375em; line-height: 1.375em; margin: 8px 0; } .phabricator-remarkup h4.remarkup-header { font-size: 1.25em; line-height: 1.25em; margin: 8px 0; } .phabricator-remarkup h5.remarkup-header { font-size: 1.125em; line-height: 1.125em; margin: 4px 0; } .phabricator-remarkup h6.remarkup-header { font-size: 1em; line-height: 1em; margin: 4px 0; } .phabricator-remarkup blockquote { border-left: 3px solid {$lightbluetext}; color: {$bluetext}; font-style: italic; margin: 4px 0 12px 0; padding: 8px 12px; background-color: #F8F9FC; } .phabricator-remarkup blockquote em { font-style: normal; } .phabricator-remarkup blockquote div.remarkup-reply-head { font-style: normal; padding-bottom: 4px; } .phabricator-remarkup blockquote div.remarkup-reply-head .phui-tag-core { background-color: transparent; border: none; padding: 0; color: {$darkbluetext}; } .phabricator-remarkup img.remarkup-proxy-image { max-width: 640px; max-height: 640px; } .phabricator-remarkup audio { display: block; margin: 16px auto; min-width: 240px; width: 50%; } .phabricator-remarkup-mention-exists { font-weight: bold; background: #e6f3ff; } .phabricator-remarkup-mention-disabled { font-weight: bold; background: #dddddd; } .phui-remarkup-preview .phabricator-remarkup-mention-unknown, .aphront-panel-preview .phabricator-remarkup-mention-unknown { font-weight: bold; background: #ffaaaa; } .phabricator-remarkup .remarkup-note { margin: 16px 0; padding: 12px; border-left: 3px solid {$blue}; background: {$lightblue}; } .phabricator-remarkup .remarkup-warning { margin: 16px 0; padding: 12px; border-left: 3px solid {$yellow}; background: {$lightyellow}; } .phabricator-remarkup .remarkup-important { margin: 16px 0; padding: 12px; border-left: 3px solid {$red}; background: {$lightred}; } .phabricator-remarkup .remarkup-note .remarkup-monospaced, .phabricator-remarkup .remarkup-important .remarkup-monospaced, .phabricator-remarkup .remarkup-warning .remarkup-monospaced { background-color: rgba(150,150,150,.2); } .phabricator-remarkup .remarkup-note-word { font-weight: bold; color: {$darkbluetext}; } .phabricator-remarkup-toc { float: right; border-left: 1px solid {$lightblueborder}; background: #fff; width: 160px; padding-left: 8px; margin: 0 0 4px 8px; } .phabricator-remarkup-toc-header { font-size: 13px; line-height: 12px; color: {$darkbluetext}; font-weight: bold; margin-bottom: 4px; } .phabricator-remarkup-toc ul { padding: 0; margin: 0; list-style: none; overflow: hidden; } .phabricator-remarkup-toc ul ul { margin: 0 0 0 8px; } .phabricator-remarkup-toc ul li { padding: 0; margin: 0; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .phabricator-remarkup-embed-layout-right { text-align: right; } .phabricator-remarkup-embed-layout-center { text-align: center; } .phabricator-remarkup-embed-layout-inline { display: inline; } .phabricator-remarkup-embed-float-right { float: right; margin: .5em 1em 0; } .phabricator-remarkup-embed-layout-link { padding-left: 20px; background: url(/rsrc/image/icon/fatcow/page_white_put.png) 0 0 no-repeat; } .phabricator-remarkup-embed-float-left { float: left; margin: .5em 1em 0; } .phabricator-remarkup-embed-image { display: inline-block; border: 3px solid white; box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.20); } .phabricator-remarkup-embed-image-full { display: inline-block; } .phabricator-remarkup-embed-image-full img { max-width: 100%; } .phabricator-remarkup table.remarkup-table { border-collapse: separate; border-spacing: 1px; background: #d3d3d3; margin: 12px 0; } .phabricator-remarkup table.remarkup-table th { font-weight: bold; padding: 3px 6px; background: #e3e3e3; } .phabricator-remarkup table.remarkup-table td { background: #ffffff; padding: 3px 6px; } .remarkup-assist-textarea { border-left-color: {$blueborder}; border-right-color: {$blueborder}; border-bottom-color: {$blueborder}; border-top-color: {$thinblueborder}; border-radius: 0; + box-shadow: none; + -webkit-box-shadow: none; + + /* Set line height explicitly so the metrics and the real textarea + are forced to the same value. */ + line-height: 1.25em; + /* Prevent Safari and Chrome users from dragging the textarea any wider, because the top bar won't resize along with it. */ resize: vertical; } +var.remarkup-assist-textarea { + /* This is an invisible element used to measure the size of text in the + textarea so we can float typeaheads over the cursor position. */ + display: block; + border-color: orange; + box-sizing: border-box; + padding: 4px 6px; + white-space: pre-wrap; + visibility: hidden; +} + .remarkup-assist-textarea:focus { border: 1px solid rgba(82, 168, 236, 0.8); } .remarkup-assist-bar { height: 26px; border-width: 1px 1px 0; border-style: solid; border-top-color: {$blueborder}; border-left-color: {$blueborder}; border-right-color: {$blueborder}; background: {$lightbluebackground}; overflow: hidden; } .remarkup-assist-button { display: block; padding: 5px; float: left; } .remarkup-assist-button:hover { background-color: rgba(100,100,100,.15); } .remarkup-assist-button:hover .phui-icon-view.phui-font-fa { color: {$darkbluetext}; } .remarkup-assist-button:active { outline: none; } .remarkup-assist-button:focus { outline: none; } .remarkup-assist-separator { display: block; float: left; margin: 7px 4px; height: 14px; width: 0px; border-right: 1px solid #cccccc; } .remarkup-interpreter-error { padding: 8px; border: 1px solid {$red}; background-color: {$lightred}; } .remarkup-cowsay { white-space: pre-wrap; } .remarkup-figlet { white-space: pre-wrap; } .remarkup-assist { width: 14px; height: 14px; overflow: hidden; text-align: center; vertical-align: middle; } .remarkup-assist-right { float: right; } .jx-order-mask { background: #ffffff; opacity: 1.0; } -.remarkup-assist-textarea { - box-shadow: none; - -webkit-box-shadow: none; -} - .remarkup-control-fullscreen-mode { position: fixed; top: -1px; bottom: -1px; left: -1px; right: -1px; } .remarkup-control-fullscreen-mode textarea.remarkup-assist-textarea { position: absolute; top: 27px; left: 0; right: 0; bottom: 0; /* NOTE: This doesn't work in Firefox, there's a JS behavior to correct it. */ height: auto; border-width: 1px 0 0 0; outline: none; } .phabricator-image-macro-hero { margin: auto; max-width: 95%; } diff --git a/webroot/rsrc/js/core/TextAreaUtils.js b/webroot/rsrc/js/core/TextAreaUtils.js index 9beac8b1d0..b96099e0e5 100644 --- a/webroot/rsrc/js/core/TextAreaUtils.js +++ b/webroot/rsrc/js/core/TextAreaUtils.js @@ -1,49 +1,107 @@ /** * @requires javelin-install + * javelin-dom + * javelin-vector * @provides phabricator-textareautils * @javelin */ JX.install('TextAreaUtils', { statics : { getSelectionRange : function(area) { var v = area.value; // NOTE: This works well in Safari, Firefox and Chrome. We'll probably get // less-good behavior on IE. var s = v.length; var e = v.length; if ('selectionStart' in area) { s = area.selectionStart; e = area.selectionEnd; } return {start: s, end: e}; }, getSelectionText : function(area) { var v = area.value; var r = JX.TextAreaUtils.getSelectionRange(area); return v.substring(r.start, r.end); }, setSelectionRange : function(area, start, end) { if ('setSelectionRange' in area) { area.focus(); area.setSelectionRange(start, end); } }, setSelectionText : function(area, text) { var v = area.value; var r = JX.TextAreaUtils.getSelectionRange(area); v = v.substring(0, r.start) + text + v.substring(r.end, v.length); area.value = v; JX.TextAreaUtils.setSelectionRange(area, r.start, r.start + text.length); + }, + + /** + * Get the document pixel positions of the beginning and end of a character + * range in a textarea. + */ + getPixelDimensions: function(area, start, end) { + var v = area.value; + + // We're using zero-width spaces to make sure the spans get some + // height even if there's no text in the metrics tag. + + var head = v.substring(0, start); + var before = JX.$N('span', {}, '\u200b'); + var body = v.substring(start, end); + var after = JX.$N('span', {}, '\u200b'); + + // Create a similar shadow element which we can measure. + var metrics = JX.$N( + 'var', + { + className: area.className, + }, + [head, before, body, after]); + + // If the textarea has a scrollbar, force a scrollbar on the shadow + // element too. + if (area.scrollHeight > area.clientHeight) { + metrics.style.overflowY = 'scroll'; + } + + area.parentNode.appendChild(metrics); + + // Adjust the positions we read out of the document to account for the + // current scroll position of the textarea. + var metrics_pos = JX.Vector.getPos(metrics); + metrics_pos.x += area.scrollLeft; + metrics_pos.y += area.scrollTop; + + var area_pos = JX.Vector.getPos(area); + var before_pos = JX.Vector.getPos(before); + var after_pos = JX.Vector.getPos(after); + + JX.DOM.remove(metrics); + + return { + start: { + x: area_pos.x + (before_pos.x - metrics_pos.x), + y: area_pos.y + (before_pos.y - metrics_pos.y) + }, + end: { + x: area_pos.x + (after_pos.x - metrics_pos.x), + y: area_pos.y + (after_pos.y - metrics_pos.y) + } + }; } + } });