Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
R
rendezvous
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
VeNtura
rendezvous
Commits
280d2049
Commit
280d2049
authored
Mar 20, 2014
by
tady
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
text complete機能
parent
e409130f
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
117 additions
and
0 deletions
+117
-0
app/assets/javascripts/lib/jquery.textcomplete.min.js
+5
-0
app/assets/javascripts/lib/mod-md-editor.js.coffee
+6
-0
app/assets/stylesheets/lib/jquery.textcomplete.css
+33
-0
app/controllers/apis_controller.rb
+6
-0
app/models/user.rb
+8
-0
app/views/posts/_form.html.slim
+44
-0
config/routes.rb
+1
-0
lib/tasks/migrations.rake
+14
-0
No files found.
app/assets/javascripts/lib/jquery.textcomplete.min.js
0 → 100755
View file @
280d2049
/*! jquery-textcomplete - v0.1.3 - 2014-03-07 */
!
function
(
a
){
"use strict"
;
var
b
=
function
(
a
){
var
b
,
d
;
return
b
=
function
(){
d
=!
1
},
function
(){
var
e
;
d
||
(
d
=!
0
,
e
=
c
(
arguments
),
e
.
unshift
(
b
),
a
.
apply
(
this
,
e
))}},
c
=
function
(
a
){
var
b
;
return
b
=
Array
.
prototype
.
slice
.
call
(
a
)},
d
=
function
(){
var
b
;
return
b
=
a
(
"<div></div>"
).
css
([
"color"
]).
color
,
"undefined"
!=
typeof
b
?
function
(
a
,
b
){
return
a
.
css
(
b
)}:
function
(
b
,
c
){
var
d
;
return
d
=
{},
a
.
each
(
c
,
function
(
a
,
c
){
d
[
c
]
=
b
.
css
(
c
)}),
d
}}(),
e
=
function
(
a
){
return
a
},
f
=
function
(
a
){
var
b
=
{};
return
function
(
c
,
d
){
b
[
c
]?
d
(
b
[
c
]):
a
.
call
(
this
,
c
,
function
(
a
){
b
[
c
]
=
(
b
[
c
]
||
[]).
concat
(
a
),
d
.
apply
(
null
,
arguments
)})}},
g
=
function
(
a
,
b
){
var
c
,
d
;
if
(
a
.
indexOf
)
return
-
1
!=
a
.
indexOf
(
b
);
for
(
c
=
0
,
d
=
a
.
length
;
d
>
c
;
c
++
)
if
(
a
[
c
]
===
b
)
return
!
0
;
return
!
1
},
h
=
function
(){
function
c
(
b
){
var
c
;
this
.
el
=
b
.
get
(
0
),
c
=
this
.
el
===
document
.
activeElement
,
this
.
$el
=
k
(
b
),
this
.
id
=
"textComplete"
+
j
++
,
this
.
strategies
=
[],
c
?(
this
.
initialize
(),
this
.
$el
.
focus
()):
this
.
$el
.
one
(
"focus.textComplete"
,
a
.
proxy
(
this
.
initialize
,
this
))}
var
e
,
f
,
g
,
h
,
j
;
e
=
{
wrapper
:
'<div class="textcomplete-wrapper"></div>'
,
list
:
'<ul class="dropdown-menu"></ul>'
},
f
=
{
wrapper
:{
position
:
"relative"
},
list
:{
position
:
"absolute"
,
top
:
0
,
left
:
0
,
zIndex
:
"100"
,
display
:
"none"
}},
g
=
a
(
e
.
wrapper
).
css
(
f
.
wrapper
),
h
=
a
(
e
.
list
).
css
(
f
.
list
),
j
=
0
,
a
.
extend
(
c
.
prototype
,{
initialize
:
function
(){
var
b
,
c
;
b
=
h
.
clone
(),
this
.
listView
=
new
i
(
b
,
this
),
this
.
$el
.
before
(
b
).
on
({
"keyup.textComplete"
:
a
.
proxy
(
this
.
onKeyup
,
this
),
"keydown.textComplete"
:
a
.
proxy
(
this
.
listView
.
onKeydown
,
this
.
listView
)}),
c
=
{},
c
[
"click."
+
this
.
id
]
=
a
.
proxy
(
this
.
onClickDocument
,
this
),
c
[
"keyup."
+
this
.
id
]
=
a
.
proxy
(
this
.
onKeyupDocument
,
this
),
a
(
document
).
on
(
c
)},
register
:
function
(
a
){
this
.
strategies
=
this
.
strategies
.
concat
(
a
)},
renderList
:
function
(
a
){
this
.
clearAtNext
&&
(
this
.
listView
.
clear
(),
this
.
clearAtNext
=!
1
),
a
.
length
&&
(
this
.
listView
.
shown
||
(
this
.
listView
.
setPosition
(
this
.
getCaretPosition
()).
clear
().
activate
(),
this
.
listView
.
strategy
=
this
.
strategy
),
a
=
a
.
slice
(
0
,
this
.
strategy
.
maxCount
),
this
.
listView
.
render
(
a
)),
!
this
.
listView
.
data
.
length
&&
this
.
listView
.
shown
&&
this
.
listView
.
deactivate
()},
searchCallbackFactory
:
function
(
a
){
var
b
=
this
;
return
function
(
c
,
d
){
b
.
renderList
(
c
),
d
||
(
a
(),
b
.
clearAtNext
=!
0
)}},
onKeyup
:
function
(
a
){
var
b
,
c
;
if
(
!
this
.
skipSearch
(
a
))
if
(
b
=
this
.
extractSearchQuery
(
this
.
getTextFromHeadToCaret
()),
b
.
length
){
if
(
c
=
b
[
1
],
this
.
term
===
c
)
return
;
this
.
term
=
c
,
this
.
search
(
b
)}
else
this
.
term
=
null
,
this
.
listView
.
deactivate
()},
skipSearch
:
function
(
a
){
if
(
this
.
skipNextKeyup
)
return
this
.
skipNextKeyup
=!
1
,
!
0
;
switch
(
a
.
keyCode
){
case
40
:
case
38
:
return
!
0
}},
onSelect
:
function
(
b
){
var
c
,
d
,
e
;
c
=
this
.
getTextFromHeadToCaret
(),
d
=
this
.
el
.
value
.
substring
(
this
.
el
.
selectionEnd
),
e
=
this
.
strategy
.
replace
(
b
),
a
.
isArray
(
e
)
&&
(
d
=
e
[
1
]
+
d
,
e
=
e
[
0
]),
c
=
c
.
replace
(
this
.
strategy
.
match
,
e
),
this
.
$el
.
val
(
c
+
d
).
trigger
(
"change"
).
trigger
(
"textComplete:select"
,
b
),
this
.
el
.
focus
(),
this
.
el
.
selectionStart
=
this
.
el
.
selectionEnd
=
c
.
length
,
this
.
skipNextKeyup
=!
0
},
onClickDocument
:
function
(
a
){
a
.
originalEvent
&&!
a
.
originalEvent
.
keepTextCompleteDropdown
&&
this
.
listView
.
deactivate
()},
onKeyupDocument
:
function
(
a
){
this
.
listView
.
shown
&&
27
===
a
.
keyCode
&&
(
this
.
listView
.
deactivate
(),
this
.
$el
.
focus
())},
destroy
:
function
(){
var
b
;
this
.
$el
.
off
(
".textComplete"
),
a
(
document
).
off
(
"."
+
this
.
id
),
this
.
listView
&&
this
.
listView
.
destroy
(),
b
=
this
.
$el
.
parent
(),
b
.
after
(
this
.
$el
).
remove
(),
this
.
$el
.
data
(
"textComplete"
,
void
0
),
this
.
$el
=
null
},
getCaretPosition
:
function
(){
if
(
0
!==
this
.
el
.
selectionEnd
){
var
b
,
c
,
e
,
f
,
g
,
h
;
return
h
=
this
.
$el
.
attr
(
"dir"
)
||
this
.
$el
.
css
(
"direction"
),
b
=
[
"border-width"
,
"font-family"
,
"font-size"
,
"font-style"
,
"font-variant"
,
"font-weight"
,
"height"
,
"letter-spacing"
,
"word-spacing"
,
"line-height"
,
"text-decoration"
,
"text-align"
,
"width"
,
"padding-top"
,
"padding-right"
,
"padding-bottom"
,
"padding-left"
,
"margin-top"
,
"margin-right"
,
"margin-bottom"
,
"margin-left"
],
c
=
a
.
extend
({
position
:
"absolute"
,
overflow
:
"auto"
,
"white-space"
:
"pre-wrap"
,
top
:
0
,
left
:
-
9999
,
direction
:
h
},
d
(
this
.
$el
,
b
)),
e
=
a
(
"<div></div>"
).
css
(
c
).
text
(
this
.
getTextFromHeadToCaret
()),
f
=
a
(
"<span></span>"
).
text
(
"."
).
appendTo
(
e
),
this
.
$el
.
before
(
e
),
g
=
f
.
position
(),
g
.
top
+=
f
.
height
()
-
this
.
$el
.
scrollTop
(),
"rtl"
===
h
&&
(
g
.
left
-=
this
.
listView
.
$el
.
width
()),
e
.
remove
(),
g
}},
getTextFromHeadToCaret
:
function
(){
var
a
,
b
,
c
;
return
b
=
this
.
el
.
selectionEnd
,
"number"
==
typeof
b
?
a
=
this
.
el
.
value
.
substring
(
0
,
b
):
document
.
selection
&&
(
c
=
this
.
el
.
createTextRange
(),
c
.
moveStart
(
"character"
,
0
),
c
.
moveEnd
(
"textedit"
),
a
=
c
.
text
),
a
},
extractSearchQuery
:
function
(
a
){
var
b
,
c
,
d
,
e
;
for
(
b
=
0
,
c
=
this
.
strategies
.
length
;
c
>
b
;
b
++
)
if
(
d
=
this
.
strategies
[
b
],
e
=
a
.
match
(
d
.
match
))
return
[
d
,
e
[
d
.
index
]];
return
[]},
search
:
b
(
function
(
a
,
b
){
var
c
;
this
.
strategy
=
b
[
0
],
c
=
b
[
1
],
this
.
strategy
.
search
(
c
,
this
.
searchCallbackFactory
(
a
))})});
var
k
=
function
(
a
){
return
a
.
wrap
(
g
.
clone
().
css
(
"display"
,
a
.
css
(
"display"
)))};
return
c
}(),
i
=
function
(){
function
b
(
b
,
c
){
this
.
data
=
[],
this
.
$el
=
b
,
this
.
index
=
0
,
this
.
completer
=
c
,
this
.
$el
.
on
(
"click.textComplete"
,
"li.textcomplete-item"
,
a
.
proxy
(
this
.
onClick
,
this
))}
return
a
.
extend
(
b
.
prototype
,{
shown
:
!
1
,
render
:
function
(
a
){
var
b
,
c
,
d
,
e
,
f
;
for
(
b
=
""
,
c
=
0
,
d
=
a
.
length
;
d
>
c
&&
(
f
=
a
[
c
],
g
(
this
.
data
,
f
)
||
(
e
=
this
.
data
.
length
,
this
.
data
.
push
(
f
),
b
+=
'<li class="textcomplete-item" data-index="'
+
e
+
'"><a>'
,
b
+=
this
.
strategy
.
template
(
f
),
b
+=
"</a></li>"
,
this
.
data
.
length
!==
this
.
strategy
.
maxCount
));
c
++
);
this
.
$el
.
append
(
b
),
this
.
data
.
length
?
this
.
activateIndexedItem
():
this
.
deactivate
()},
clear
:
function
(){
return
this
.
data
=
[],
this
.
$el
.
html
(
""
),
this
.
index
=
0
,
this
},
activateIndexedItem
:
function
(){
this
.
$el
.
find
(
".active"
).
removeClass
(
"active"
),
this
.
getActiveItem
().
addClass
(
"active"
)},
getActiveItem
:
function
(){
return
a
(
this
.
$el
.
children
().
get
(
this
.
index
))},
activate
:
function
(){
return
this
.
shown
||
(
this
.
$el
.
show
(),
this
.
completer
.
$el
.
trigger
(
"textComplete:show"
),
this
.
shown
=!
0
),
this
},
deactivate
:
function
(){
return
this
.
shown
&&
(
this
.
$el
.
hide
(),
this
.
completer
.
$el
.
trigger
(
"textComplete:hide"
),
this
.
shown
=!
1
,
this
.
data
=
[],
this
.
index
=
null
),
this
},
setPosition
:
function
(
a
){
return
this
.
$el
.
css
(
a
),
this
},
select
:
function
(
a
){
var
b
=
this
;
this
.
completer
.
onSelect
(
this
.
data
[
a
]),
setTimeout
(
function
(){
b
.
deactivate
()},
0
)},
onKeydown
:
function
(
a
){
this
.
shown
&&
(
38
===
a
.
keyCode
?(
a
.
preventDefault
(),
0
===
this
.
index
?
this
.
index
=
this
.
data
.
length
-
1
:
this
.
index
-=
1
,
this
.
activateIndexedItem
()):
40
===
a
.
keyCode
?(
a
.
preventDefault
(),
this
.
index
===
this
.
data
.
length
-
1
?
this
.
index
=
0
:
this
.
index
+=
1
,
this
.
activateIndexedItem
()):(
13
===
a
.
keyCode
||
9
===
a
.
keyCode
)
&&
(
a
.
preventDefault
(),
this
.
select
(
parseInt
(
this
.
getActiveItem
().
data
(
"index"
),
10
))))},
onClick
:
function
(
b
){
var
c
=
a
(
b
.
target
);
b
.
originalEvent
.
keepTextCompleteDropdown
=!
0
,
c
.
hasClass
(
"textcomplete-item"
)
||
(
c
=
c
.
parents
(
"li.textcomplete-item"
)),
this
.
select
(
parseInt
(
c
.
data
(
"index"
),
10
))},
destroy
:
function
(){
this
.
deactivate
(),
this
.
$el
.
off
(
"click.textComplete"
).
remove
(),
this
.
$el
=
null
}}),
b
}();
a
.
fn
.
textcomplete
=
function
(
b
){
var
c
,
d
,
g
,
i
;
if
(
i
=
"textComplete"
,
"destroy"
===
b
)
return
this
.
each
(
function
(){
var
b
=
a
(
this
).
data
(
i
);
b
&&
b
.
destroy
()});
for
(
c
=
0
,
d
=
b
.
length
;
d
>
c
;
c
++
)
g
=
b
[
c
],
g
.
template
||
(
g
.
template
=
e
),
null
==
g
.
index
&&
(
g
.
index
=
2
),
g
.
cache
&&
(
g
.
search
=
f
(
g
.
search
)),
g
.
maxCount
||
(
g
.
maxCount
=
10
);
return
this
.
each
(
function
(){
var
c
,
d
;
c
=
a
(
this
),
d
=
c
.
data
(
i
),
d
||
(
d
=
new
h
(
c
),
c
.
data
(
i
,
d
)),
d
.
register
(
b
)})}}(
window
.
jQuery
||
window
.
Zepto
);
/*
//@ sourceMappingURL=jquery.textcomplete.min.map
*/
\ No newline at end of file
app/assets/javascripts/lib/mod-md-editor.js.coffee
View file @
280d2049
...
...
@@ -21,6 +21,9 @@ $.fn.extend
keyCode
=
e
.
keyCode
||
e
.
which
console
.
log
(
keyCode
)
console
.
log
(
$this
.
data
(
'autocompleting'
))
# tab key
if
keyCode
is
9
e
.
preventDefault
()
...
...
@@ -38,6 +41,9 @@ $.fn.extend
# enter key
else
if
keyCode
is
13
return
true
if
$this
.
data
(
'autocompleting'
)
val
=
$this
.
val
()
start
=
$this
.
get
(
0
).
selectionStart
bl
=
val
.
lastIndexOf
(
"
\n
"
,
start
-
1
)
...
...
app/assets/stylesheets/lib/jquery.textcomplete.css
0 → 100755
View file @
280d2049
/* Sample */
.dropdown-menu
{
border
:
1px
solid
#ddd
;
background-color
:
white
;
}
.dropdown-menu
li
{
border-top
:
1px
solid
#ddd
;
padding
:
2px
5px
;
}
.dropdown-menu
li
:first-child
{
border-top
:
none
;
}
.dropdown-menu
li
:hover
,
.dropdown-menu
.active
{
background-color
:
rgb
(
110
,
183
,
219
);
}
/* SHOULD not modify */
.dropdown-menu
{
list-style
:
none
;
padding
:
0
;
margin
:
0
;
}
.dropdown-menu
a
:hover
{
cursor
:
pointer
;
}
app/controllers/apis_controller.rb
View file @
280d2049
...
...
@@ -32,4 +32,10 @@ class ApisController < ApplicationController
render
json:
{
status:
'OK'
,
files:
s3_files
}
end
def
user_mention
name_list
=
User
.
search
(
params
[
:q
]).
map
{
|
_user
|
"
#{
_user
.
nickname
}
[
#{
_user
.
name
}
]"
}
render
json:
name_list
end
end
app/models/user.rb
View file @
280d2049
...
...
@@ -20,6 +20,11 @@ class User < ActiveRecord::Base
User
.
joins
(
:posts
).
group
(
'id'
).
order
(
'posts.updated_at desc'
)
}
scope
:search
,
(
lambda
do
|
_query
|
where
(
'name LIKE ? OR nickname LIKE ?'
,
"%
#{
_query
}
%"
,
"%
#{
_query
}
%"
)
end
)
######################################################################
# validations
######################################################################
...
...
@@ -52,6 +57,7 @@ class User < ActiveRecord::Base
user
end
# check if google oauth token is expired
def
google_oauth_token_expired?
google_token_expires_at
<
Time
.
now
...
...
@@ -77,4 +83,6 @@ class User < ActiveRecord::Base
)
end
end
app/views/posts/_form.html.slim
View file @
280d2049
...
...
@@ -55,3 +55,47 @@ input#fileupload data-url="/apis/file_receiver" multiple="" name="files[]" style
var
val
=
$
(
'.mod-mdEditor-body'
).
val
();
console
.
log
(
val
);
});
// mention補完
// TODO: mod-md-editorに入れる?
$
(
'textarea'
).
textcomplete
([
{
// html
mentions
:
[
'yuku_t'
],
match
:
/
\B
@
([^\B]
*
)
$/
,
search
:
function
(
term
,
callback
)
{
$
.
getJSON
(
'/apis/user_mention'
,
{
q
:
term
})
.
done
(
function
(
resp
)
{
callback
(
resp
);
})
.
fail
(
function
()
{
callback
([]);
});
},
index
:
1
,
replace
:
function
(
mention
)
{
return
'@'
+
mention
+
' '
;
}
}
]).
on
({
'textComplete:select'
:
function
(
e
,
value
)
{
console
.
log
(
'textComplete:select '
+
value
);
},
'textComplete:show'
:
function
(
e
)
{
console
.
log
(
'textComplete:show'
);
$
(
this
).
data
(
'autocompleting'
,
true
);
},
'textComplete:hide'
:
function
(
e
)
{
console
.
log
(
'textComplete:hide'
);
$
(
this
).
data
(
'autocompleting'
,
false
);
}
});
// .overlay([
// {
// match: /\B@\w+/g,
// css: {
// 'background-color': '#d8dfea'
// }
// }
// ]);
css:
.textcomplete-wrapper
{
width
:
100%
;
}
config/routes.rb
View file @
280d2049
...
...
@@ -2,6 +2,7 @@ Rendezvous::Application.routes.draw do
post
'apis/markdown_preview'
post
'apis/file_receiver'
get
'apis/user_mention'
get
'tags/:name/events'
=>
'tags#events'
,
as:
'event_tag'
root
'welcome#top'
,
as:
'root'
...
...
lib/tasks/migrations.rake
0 → 100644
View file @
280d2049
namespace
:migrations
do
desc
'001 nicknameを自動付与'
task
task_001_user_nickname: :environment
do
User
.
all
.
each
do
|
_user
|
next
if
_user
.
nickname
.
present?
new_nickname
=
((
"a"
..
"z"
).
to_a
+
(
"A"
..
"Z"
).
to_a
+
(
0
..
9
).
to_a
).
shuffle
[
0
..
4
].
join
_user
.
update_attributes!
(
nickname:
new_nickname
)
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment