Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2895784
behavior-phabricator-remarkup-assist.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
11 KB
Referenced Files
None
Subscribers
None
behavior-phabricator-remarkup-assist.js
View Options
/**
* @provides javelin-behavior-phabricator-remarkup-assist
* @requires javelin-behavior
* javelin-stratcom
* javelin-dom
* phabricator-phtize
* phabricator-textareautils
* phabricator-remarkup-metadata
* javelin-workflow
* javelin-vector
* phuix-autocomplete
* javelin-mask
*/
JX
.
behavior
(
'phabricator-remarkup-assist'
,
function
(
config
)
{
var
pht
=
JX
.
phtize
(
config
.
pht
);
var
root
=
JX
.
$
(
config
.
rootID
);
var
area
=
JX
.
DOM
.
find
(
root
,
'textarea'
);
var
edit_mode
=
'normal'
;
var
edit_root
=
null
;
var
preview
=
null
;
var
pinned
=
false
;
// When we pin the comment area to the bottom of the window, we need to put
// an extra spacer element at the bottom of the document so that it is
// possible to scroll down far enough to see content at the end. Otherwise,
// the last part of the document will be hidden behind the comment area when
// the document is fully scrolled.
var
pinned_spacer
=
JX
.
$N
(
'div'
,
{
className
:
'remarkup-assist-pinned-spacer'
});
function
set_edit_mode
(
root
,
mode
)
{
if
(
mode
==
edit_mode
)
{
return
;
}
// First, disable any active mode.
if
(
edit_root
)
{
if
(
edit_mode
==
'fullscreen'
)
{
JX
.
DOM
.
alterClass
(
edit_root
,
'remarkup-control-fullscreen-mode'
,
false
);
JX
.
DOM
.
alterClass
(
document
.
body
,
'remarkup-fullscreen-mode'
,
false
);
JX
.
Mask
.
hide
(
'jx-light-mask'
);
}
area
.
style
.
height
=
''
;
// If we're in preview mode, kick the preview back down to default
// size.
if
(
preview
)
{
JX
.
DOM
.
show
(
area
);
resize_preview
();
JX
.
DOM
.
hide
(
area
);
}
}
edit_root
=
root
;
edit_mode
=
mode
;
// Now, apply the new mode.
if
(
mode
==
'fullscreen'
)
{
JX
.
DOM
.
alterClass
(
edit_root
,
'remarkup-control-fullscreen-mode'
,
true
);
JX
.
DOM
.
alterClass
(
document
.
body
,
'remarkup-fullscreen-mode'
,
true
);
JX
.
Mask
.
show
(
'jx-light-mask'
);
// If we're in preview mode, expand the preview to full-size.
if
(
preview
)
{
JX
.
DOM
.
show
(
area
);
}
resizearea
();
if
(
preview
)
{
resize_preview
();
JX
.
DOM
.
hide
(
area
);
}
}
JX
.
DOM
.
focus
(
area
);
}
function
set_pinned_mode
(
root
,
mode
)
{
if
(
mode
===
pinned
)
{
return
;
}
pinned
=
mode
;
var
container
=
get_pinned_container
(
root
);
JX
.
DOM
.
alterClass
(
container
,
'remarkup-assist-pinned'
,
pinned
);
if
(
pinned
)
{
JX
.
DOM
.
appendContent
(
document
.
body
,
pinned_spacer
);
}
else
{
JX
.
DOM
.
remove
(
pinned_spacer
);
}
resizearea
();
JX
.
DOM
.
focus
(
area
);
}
function
get_pinned_container
(
root
)
{
return
JX
.
DOM
.
findAbove
(
root
,
'div'
,
'phui-comment-form'
);
}
function
resizearea
()
{
// If we're in the pinned comment mode, resize the pinned spacer to be the
// same size as the pinned form. This allows users to scroll to the bottom
// of the document by creating extra footer space to scroll through.
if
(
pinned
)
{
var
container
=
get_pinned_container
(
root
);
var
d
=
JX
.
Vector
.
getDim
(
container
);
d
.
x
=
null
;
d
.
setDim
(
pinned_spacer
);
}
if
(
!
edit_root
)
{
return
;
}
if
(
edit_mode
!=
'fullscreen'
)
{
return
;
}
// In Firefox, a textarea with position "absolute" or "fixed", anchored
// "top" and "bottom", and height "auto" renders as two lines high. Force
// it to the correct height with Javascript.
var
v
=
JX
.
Vector
.
getViewport
();
v
.
x
=
null
;
v
.
y
-=
26
;
v
.
setDim
(
area
);
}
JX
.
Stratcom
.
listen
(
'resize'
,
null
,
resizearea
);
JX
.
Stratcom
.
listen
(
'keydown'
,
null
,
function
(
e
)
{
if
(
e
.
getSpecialKey
()
!=
'esc'
)
{
return
;
}
if
(
edit_mode
!=
'fullscreen'
)
{
return
;
}
e
.
kill
();
set_edit_mode
(
edit_root
,
'normal'
);
set_pinned_mode
(
root
,
false
);
});
function
update
(
area
,
l
,
m
,
r
)
{
// Replace the selection with the entire assisted text.
JX
.
TextAreaUtils
.
setSelectionText
(
area
,
l
+
m
+
r
,
true
);
// Now, select just the middle part. For instance, if the user clicked
// "B" to create bold text, we insert '**bold**' but just select the word
// "bold" so if they type stuff they'll be editing the bold text.
var
range
=
JX
.
TextAreaUtils
.
getSelectionRange
(
area
);
JX
.
TextAreaUtils
.
setSelectionRange
(
area
,
range
.
start
+
l
.
length
,
range
.
start
+
l
.
length
+
m
.
length
);
}
function
prepend_char_to_lines
(
ch
,
sel
,
def
)
{
if
(
sel
)
{
sel
=
sel
.
split
(
'\n'
);
}
else
{
sel
=
[
def
];
}
if
(
ch
===
'>'
)
{
for
(
var
i
=
0
;
i
<
sel
.
length
;
i
++
)
{
if
(
sel
[
i
][
0
]
===
'>'
)
{
ch
=
'>'
;
}
else
{
ch
=
'> '
;
}
sel
[
i
]
=
ch
+
sel
[
i
];
}
return
sel
.
join
(
'\n'
);
}
return
sel
.
join
(
'\n'
+
ch
);
}
function
assist
(
area
,
action
,
root
,
button
)
{
// If the user has some text selected, we'll try to use that (for example,
// if they have a word selected and want to bold it). Otherwise we'll insert
// generic text.
var
sel
=
JX
.
TextAreaUtils
.
getSelectionText
(
area
);
var
r
=
JX
.
TextAreaUtils
.
getSelectionRange
(
area
);
var
ch
;
switch
(
action
)
{
case
'fa-bold'
:
update
(
area
,
'**'
,
sel
||
pht
(
'bold text'
),
'**'
);
break
;
case
'fa-italic'
:
update
(
area
,
'//'
,
sel
||
pht
(
'italic text'
),
'//'
);
break
;
case
'fa-link'
:
var
name
=
pht
(
'name'
);
if
(
/^https?:/i
.
test
(
sel
))
{
update
(
area
,
'[[ '
+
sel
+
' | '
,
name
,
' ]]'
);
}
else
{
update
(
area
,
'[[ '
,
pht
(
'URL'
),
' | '
+
(
sel
||
name
)
+
' ]]'
);
}
break
;
case
'fa-text-width'
:
update
(
area
,
'`'
,
sel
||
pht
(
'monospaced text'
),
'`'
);
break
;
case
'fa-list-ul'
:
case
'fa-list-ol'
:
ch
=
(
action
==
'fa-list-ol'
)
?
' # '
:
' - '
;
sel
=
prepend_char_to_lines
(
ch
,
sel
,
pht
(
'List Item'
));
update
(
area
,
((
r
.
start
===
0
)
?
''
:
'\n\n'
)
+
ch
,
sel
,
'\n\n'
);
break
;
case
'fa-code'
:
sel
=
sel
||
'foreach ($list as $item) {\n work_miracles($item);\n}'
;
var
code_prefix
=
(
r
.
start
===
0
)
?
''
:
'\n'
;
update
(
area
,
code_prefix
+
'```\n'
,
sel
,
'\n```'
);
break
;
case
'fa-quote-right'
:
ch
=
'>'
;
sel
=
prepend_char_to_lines
(
ch
,
sel
,
pht
(
'Quoted Text'
));
update
(
area
,
((
r
.
start
===
0
)
?
''
:
'\n\n'
),
sel
,
'\n\n'
);
break
;
case
'fa-table'
:
var
table_prefix
=
(
r
.
start
===
0
?
''
:
'\n\n'
);
update
(
area
,
table_prefix
+
'| '
,
sel
||
pht
(
'data'
),
' |'
);
break
;
case
'fa-meh-o'
:
new
JX
.
Workflow
(
'/macro/meme/create/'
)
.
setHandler
(
function
(
response
)
{
update
(
area
,
''
,
sel
,
(
r
.
start
===
0
?
''
:
'\n\n'
)
+
response
.
text
+
'\n\n'
);
})
.
start
();
break
;
case
'fa-cloud-upload'
:
new
JX
.
Workflow
(
'/file/uploaddialog/'
)
.
setHandler
(
function
(
response
)
{
var
files
=
response
.
files
;
for
(
var
ii
=
0
;
ii
<
files
.
length
;
ii
++
)
{
var
file
=
files
[
ii
];
var
upload
=
new
JX
.
PhabricatorFileUpload
()
.
setID
(
file
.
id
)
.
setPHID
(
file
.
phid
)
.
setURI
(
file
.
uri
);
JX
.
TextAreaUtils
.
insertFileReference
(
area
,
upload
);
var
metadata
=
new
JX
.
RemarkupMetadata
(
config
.
remarkupMetadataValue
,
config
.
remarkupMetadataID
);
var
phids
=
metadata
.
getMetadata
(
'attachedFilePHIDs'
,
[]);
phids
.
push
(
file
.
phid
);
metadata
.
setMetadata
(
'attachedFilePHIDs'
,
phids
);
}
})
.
start
();
break
;
case
'fa-arrows-alt'
:
set_pinned_mode
(
root
,
false
);
if
(
edit_mode
==
'fullscreen'
)
{
set_edit_mode
(
root
,
'normal'
);
}
else
{
set_edit_mode
(
root
,
'fullscreen'
);
}
break
;
case
'fa-eye'
:
if
(
!
preview
)
{
preview
=
JX
.
$N
(
'div'
,
{
className
:
'remarkup-inline-preview'
},
null
);
area
.
parentNode
.
insertBefore
(
preview
,
area
);
JX
.
DOM
.
alterClass
(
button
,
'preview-active'
,
true
);
JX
.
DOM
.
alterClass
(
root
,
'remarkup-preview-active'
,
true
);
resize_preview
();
JX
.
DOM
.
hide
(
area
);
update_preview
();
}
else
{
JX
.
DOM
.
show
(
area
);
resize_preview
(
true
);
JX
.
DOM
.
remove
(
preview
);
preview
=
null
;
JX
.
DOM
.
alterClass
(
button
,
'preview-active'
,
false
);
JX
.
DOM
.
alterClass
(
root
,
'remarkup-preview-active'
,
false
);
}
break
;
case
'fa-thumb-tack'
:
// If we're pinning, kick us out of fullscreen mode first.
set_edit_mode
(
edit_root
,
'normal'
);
// Now pin or unpin the area.
set_pinned_mode
(
root
,
!
pinned
);
break
;
}
}
function
resize_preview
(
restore
)
{
if
(
!
preview
)
{
return
;
}
var
src
;
var
dst
;
if
(
restore
)
{
src
=
preview
;
dst
=
area
;
}
else
{
src
=
area
;
dst
=
preview
;
}
var
d
=
JX
.
Vector
.
getDim
(
src
);
d
.
x
=
null
;
d
.
setDim
(
dst
);
}
function
update_preview
()
{
var
value
=
area
.
value
;
var
data
=
{
text
:
value
};
var
onupdate
=
function
(
r
)
{
if
(
area
.
value
!==
value
)
{
return
;
}
if
(
!
preview
)
{
return
;
}
JX
.
DOM
.
setContent
(
preview
,
JX
.
$H
(
r
.
content
).
getFragment
());
};
new
JX
.
Workflow
(
'/transactions/remarkuppreview/'
,
data
)
.
setHandler
(
onupdate
)
.
start
();
}
JX
.
DOM
.
listen
(
root
,
'click'
,
'remarkup-assist'
,
function
(
e
)
{
var
data
=
e
.
getNodeData
(
'remarkup-assist'
);
if
(
!
data
.
action
)
{
return
;
}
e
.
kill
();
if
(
config
.
disabled
)
{
return
;
}
assist
(
area
,
data
.
action
,
root
,
e
.
getNode
(
'remarkup-assist'
));
});
var
autocomplete
=
new
JX
.
PHUIXAutocomplete
()
.
setArea
(
area
);
for
(
var
k
in
config
.
autocompleteMap
)
{
autocomplete
.
addAutocomplete
(
k
,
config
.
autocompleteMap
[
k
]);
}
autocomplete
.
start
();
if
(
config
.
canPin
)
{
new
JX
.
KeyboardShortcut
(
'z'
,
pht
(
'key-help'
))
.
setGroup
(
'xactions'
)
.
setHandler
(
function
()
{
set_pinned_mode
(
root
,
!
pinned
);
})
.
register
();
}
if
(
config
.
sendOnEnter
)
{
// Send on enter if the shift key is not held.
JX
.
DOM
.
listen
(
area
,
'keydown'
,
null
,
function
(
e
)
{
if
(
e
.
getSpecialKey
()
!=
'return'
)
{
return
;
}
// Let other listeners (particularly the inline autocomplete) have a
// chance to handle this event.
if
(
JX
.
Stratcom
.
pass
())
{
return
;
}
var
raw
=
e
.
getRawEvent
();
if
(
raw
.
shiftKey
)
{
// If the shift key is pressed, let the browser write a newline into
// the textarea.
return
;
}
if
(
edit_mode
==
'fullscreen'
)
{
// Don't send on enter in fullscreen
return
;
}
// From here on, interpret this as a "send" action, not a literal
// newline.
e
.
kill
();
// This allows 'workflow' and similar actions to take effect.
// Such as pontificate in Conpherence
var
form
=
e
.
getNode
(
'tag:form'
);
JX
.
DOM
.
invoke
(
form
,
'didSyntheticSubmit'
);
});
}
});
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Jan 19 2025, 22:02 (6 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1129204
Default Alt Text
behavior-phabricator-remarkup-assist.js (11 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment