Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
coopwire-hypothesis
Project
Project
Details
Activity
Releases
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
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
孙灵跃 Leon Sun
coopwire-hypothesis
Commits
1a3d7e0d
Commit
1a3d7e0d
authored
Dec 17, 2015
by
Robert Knight
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2805 from hypothesis/TPUsXCk4-add-media-embeds-feature
Add media embeds feature
parents
22aa37eb
e5beb610
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
324 additions
and
6 deletions
+324
-6
markdown.coffee
h/static/scripts/directive/markdown.coffee
+14
-3
media-embedder.js
h/static/scripts/media-embedder.js
+155
-0
media-embedder-test.js
h/static/scripts/test/media-embedder-test.js
+146
-0
annotations.scss
h/static/styles/annotations.scss
+5
-0
annotation.html
h/templates/client/annotation.html
+3
-2
markdown.html
h/templates/client/markdown.html
+1
-1
No files found.
h/static/scripts/directive/markdown.coffee
View file @
1a3d7e0d
mediaEmbedder
=
require
(
'../media-embedder'
)
loadMathJax
=
->
if
!
MathJax
?
$
.
ajax
{
...
...
@@ -297,7 +299,16 @@ module.exports = ['$filter', '$sanitize', '$sce', '$timeout', ($filter, $sanitiz
startMath
=
index
+
2
$sanitize
convert
renderInlineMath
textToCheck
.
substring
(
endMath
,
index
)
return
parts
.
join
(
''
)
htmlString
=
parts
.
join
(
''
)
# Transform the HTML string into a DOM element.
domElement
=
document
.
createElement
(
'div'
)
domElement
.
innerHTML
=
htmlString
if
scope
.
embedsEnabled
mediaEmbedder
.
replaceLinksWithEmbeds
(
domElement
)
return
domElement
.
innerHTML
renderInlineMath
=
(
textToCheck
)
->
re
=
/\\?\\\(|\\?\\\)/g
...
...
@@ -333,8 +344,7 @@ module.exports = ['$filter', '$sanitize', '$sce', '$timeout', ($filter, $sanitiz
if
!
scope
.
readOnly
and
!
scope
.
preview
inputEl
.
val
(
ctrl
.
$viewValue
or
''
)
value
=
ctrl
.
$viewValue
or
''
rendered
=
renderMathAndMarkdown
value
scope
.
rendered
=
$sce
.
trustAsHtml
rendered
output
.
innerHTML
=
renderMathAndMarkdown
(
value
)
if
mathJaxFallback
$timeout
(
->
MathJax
?
.
Hub
.
Queue
[
'Typeset'
,
MathJax
.
Hub
,
output
]),
0
,
false
...
...
@@ -356,5 +366,6 @@ module.exports = ['$filter', '$sanitize', '$sce', '$timeout', ($filter, $sanitiz
scope
:
readOnly
:
'='
required
:
'@'
embedsEnabled
:
'='
templateUrl
:
'markdown.html'
]
h/static/scripts/media-embedder.js
0 → 100644
View file @
1a3d7e0d
'use strict'
;
/**
* Return an iframe DOM element with the given src URL.
*/
function
iframe
(
src
)
{
var
iframe_
=
document
.
createElement
(
'iframe'
);
iframe_
.
src
=
src
;
iframe_
.
classList
.
add
(
'annotation-media-embed'
);
iframe_
.
setAttribute
(
'frameborder'
,
'0'
);
return
iframe_
;
}
/**
* Return a YouTube embed (<iframe>) DOM element for the given video ID.
*/
function
youTubeEmbed
(
id
)
{
return
iframe
(
'https://www.youtube.com/embed/'
+
id
);
}
function
vimeoEmbed
(
id
)
{
return
iframe
(
'https://player.vimeo.com/video/'
+
id
);
}
/**
* A list of functions that return an "embed" DOM element (e.g. an <iframe>)
* for a given link.
*
* Each function either returns `undefined` if it can't generate an embed for
* the link, or a DOM element if it can.
*
*/
var
embedGenerators
=
[
// Matches URLs like https://www.youtube.com/watch?v=rw6oWkCojpw
function
iframeFromYouTubeWatchURL
(
link
)
{
if
(
link
.
hostname
!==
'www.youtube.com'
)
{
return
;
}
if
(
!
/
\/
watch
\/?
/
.
test
(
link
.
pathname
))
{
return
;
}
var
groups
=
/
[
&
\?]
v=
([^
&#
]
+
)
/
.
exec
(
link
.
search
);
if
(
groups
)
{
return
youTubeEmbed
(
groups
[
1
]);
}
},
// Matches URLs like https://youtu.be/rw6oWkCojpw
function
iframeFromYouTubeShareURL
(
link
)
{
if
(
link
.
hostname
!==
'youtu.be'
)
{
return
;
}
var
groups
=
/^
\/([^\/]
+
)\/?
$/
.
exec
(
link
.
pathname
);
if
(
groups
)
{
return
youTubeEmbed
(
groups
[
1
]);
}
},
// Matches URLs like https://vimeo.com/149000090
function
iFrameFromVimeoLink
(
link
)
{
if
(
link
.
hostname
!==
'vimeo.com'
)
{
return
;
}
var
groups
=
/^
\/([^\/\?
#
]
+
)\/?
$/
.
exec
(
link
.
pathname
);
if
(
groups
)
{
return
vimeoEmbed
(
groups
[
1
]);
}
},
// Matches URLs like https://vimeo.com/channels/staffpicks/148845534
function
iFrameFromVimeoChannelLink
(
link
)
{
if
(
link
.
hostname
!==
'vimeo.com'
)
{
return
;
}
var
groups
=
/^
\/
channels
\/[^\/]
+
\/([^\/
?#
]
+
)\/?
$/
.
exec
(
link
.
pathname
);
if
(
groups
)
{
return
vimeoEmbed
(
groups
[
1
]);
}
},
];
/**
* Return an embed element for the given link if it's an embeddable link.
*
* If the link is a link for a YouTube video or other embeddable media then
* return an embed DOM element (for example an <iframe>) for that media.
*
* Otherwise return undefined.
*
*/
function
embedForLink
(
link
)
{
var
embed
;
var
j
;
for
(
j
=
0
;
j
<
embedGenerators
.
length
;
j
++
)
{
embed
=
embedGenerators
[
j
](
link
);
if
(
embed
)
{
return
embed
;
}
}
}
/** Replace the given link element with an embed.
*
* If the given link element is a link to an embeddable media then it will be
* replaced in the DOM with an embed (e.g. an <iframe>) of the same media.
*
* If it's not an embeddable link the link will be left untouched.
*
*/
function
replaceLinkWithEmbed
(
link
)
{
// If the user gives a custom link text then we don't replace the link with
// an embed.
if
(
link
.
href
!==
link
.
innerText
)
{
return
;
}
var
embed
=
embedForLink
(
link
);
if
(
embed
)
{
link
.
parentElement
.
replaceChild
(
embed
,
link
);
}
}
/**
* Replace all embeddable link elements beneath the given element with embeds.
*
* All links to YouTube videos or other embeddable media will be replaced with
* embeds of the same media.
*
*/
function
replaceLinksWithEmbeds
(
element
)
{
var
links
=
element
.
getElementsByTagName
(
'a'
);
// `links` is a "live list" of the <a> element children of `element`.
// We want to iterate over `links` and replace some of them with embeds,
// but we can't modify `links` while looping over it so we need to copy it to
// a nice, normal array first.
links
=
Array
.
prototype
.
slice
.
call
(
links
,
0
);
var
i
;
for
(
i
=
0
;
i
<
links
.
length
;
i
++
)
{
replaceLinkWithEmbed
(
links
[
i
]);
}
}
module
.
exports
=
{
replaceLinksWithEmbeds
:
replaceLinksWithEmbeds
,
};
h/static/scripts/test/media-embedder-test.js
0 → 100644
View file @
1a3d7e0d
'use strict'
;
var
mediaEmbedder
=
require
(
'../media-embedder.js'
);
describe
(
'media-embedder'
,
function
()
{
function
domElement
(
html
)
{
var
element
=
document
.
createElement
(
'div'
);
element
.
innerHTML
=
html
;
return
element
;
}
it
(
'replaces YouTube watch links with iframes'
,
function
()
{
var
urls
=
[
'https://www.youtube.com/watch?v=QCkm0lL-6lc'
,
'https://www.youtube.com/watch/?v=QCkm0lL-6lc'
,
'https://www.youtube.com/watch?foo=bar&v=QCkm0lL-6lc'
,
'https://www.youtube.com/watch?foo=bar&v=QCkm0lL-6lc&h=j'
,
'https://www.youtube.com/watch?v=QCkm0lL-6lc&foo=bar'
,
];
urls
.
forEach
(
function
(
url
)
{
var
element
=
domElement
(
'<a href="'
+
url
+
'">'
+
url
+
'</a>'
);
mediaEmbedder
.
replaceLinksWithEmbeds
(
element
);
assert
.
equal
(
element
.
childElementCount
,
1
);
assert
.
equal
(
element
.
children
[
0
].
tagName
,
'IFRAME'
,
url
);
assert
.
equal
(
element
.
children
[
0
].
src
,
'https://www.youtube.com/embed/QCkm0lL-6lc'
);
});
});
it
(
'replaces YouTube share links with iframes'
,
function
()
{
var
urls
=
[
'https://youtu.be/QCkm0lL-6lc'
,
'https://youtu.be/QCkm0lL-6lc/'
,
]
urls
.
forEach
(
function
(
url
)
{
var
element
=
domElement
(
'<a href="'
+
url
+
'">'
+
url
+
'</a>'
);
mediaEmbedder
.
replaceLinksWithEmbeds
(
element
);
assert
.
equal
(
element
.
childElementCount
,
1
);
assert
.
equal
(
element
.
children
[
0
].
tagName
,
'IFRAME'
);
assert
.
equal
(
element
.
children
[
0
].
src
,
'https://www.youtube.com/embed/QCkm0lL-6lc'
);
});
});
it
(
'replaces Vimeo links with iframes'
,
function
()
{
var
urls
=
[
'https://vimeo.com/149000090'
,
'https://vimeo.com/149000090/'
,
'https://vimeo.com/149000090#fragment'
,
'https://vimeo.com/149000090/#fragment'
,
'https://vimeo.com/149000090?foo=bar&a=b'
,
'https://vimeo.com/149000090/?foo=bar&a=b'
,
]
urls
.
forEach
(
function
(
url
)
{
var
element
=
domElement
(
'<a href="'
+
url
+
'">'
+
url
+
'</a>'
);
mediaEmbedder
.
replaceLinksWithEmbeds
(
element
);
assert
.
equal
(
element
.
childElementCount
,
1
);
assert
.
equal
(
element
.
children
[
0
].
tagName
,
'IFRAME'
);
assert
.
equal
(
element
.
children
[
0
].
src
,
'https://player.vimeo.com/video/149000090'
);
});
});
it
(
'replaces Vimeo channel links with iframes'
,
function
()
{
var
urls
=
[
'https://vimeo.com/channels/staffpicks/148845534'
,
'https://vimeo.com/channels/staffpicks/148845534/'
,
'https://vimeo.com/channels/staffpicks/148845534/?q=foo&id=bar'
,
'https://vimeo.com/channels/staffpicks/148845534#fragment'
,
'https://vimeo.com/channels/staffpicks/148845534/#fragment'
,
'https://vimeo.com/channels/staffpicks/148845534?foo=bar&id=1'
,
'https://vimeo.com/channels/otherchannel/148845534'
,
];
urls
.
forEach
(
function
(
url
)
{
var
element
=
domElement
(
'<a href="'
+
url
+
'">'
+
url
+
'</a>'
);
mediaEmbedder
.
replaceLinksWithEmbeds
(
element
);
assert
.
equal
(
element
.
childElementCount
,
1
);
assert
.
equal
(
element
.
children
[
0
].
tagName
,
'IFRAME'
);
assert
.
equal
(
element
.
children
[
0
].
src
,
'https://player.vimeo.com/video/148845534'
);
});
});
it
(
'does not replace links if the link text is different'
,
function
()
{
var
url
=
'https://youtu.be/QCkm0lL-6lc'
;
var
element
=
domElement
(
'<a href="'
+
url
+
'">different label</a>'
);
mediaEmbedder
.
replaceLinksWithEmbeds
(
element
);
assert
.
equal
(
element
.
childElementCount
,
1
);
assert
.
equal
(
element
.
children
[
0
].
tagName
,
'A'
);
});
it
(
'does not replace non-media links'
,
function
()
{
var
url
=
'https://example.com/example.html'
;
var
element
=
domElement
(
'<a href="'
+
url
+
'">'
+
url
+
'</a>'
);
mediaEmbedder
.
replaceLinksWithEmbeds
(
element
);
assert
.
equal
(
element
.
childElementCount
,
1
);
assert
.
equal
(
element
.
children
[
0
].
tagName
,
'A'
);
});
it
(
'does not mess with the rest of the HTML'
,
function
()
{
var
url
=
'https://www.youtube.com/watch?v=QCkm0lL-6lc'
;
var
element
=
domElement
(
'<p>Look at this video:</p>
\
n
\
n'
+
'<a href="'
+
url
+
'">'
+
url
+
'</a>
\
n
\
n'
+
'<p>Isn
\'
t it cool!</p>
\
n
\
n'
);
mediaEmbedder
.
replaceLinksWithEmbeds
(
element
);
assert
.
equal
(
element
.
childElementCount
,
3
);
assert
.
equal
(
element
.
children
[
0
].
outerHTML
,
'<p>Look at this video:</p>'
);
assert
.
equal
(
element
.
children
[
2
].
outerHTML
,
'<p>Isn
\'
t it cool!</p>'
);
});
it
(
'replaces multiple links with multiple embeds'
,
function
()
{
var
url1
=
'https://www.youtube.com/watch?v=QCkm0lL-6lc'
;
var
url2
=
'https://youtu.be/abcdefg'
;
var
element
=
domElement
(
'<a href="'
+
url1
+
'">'
+
url1
+
'</a>
\
n
\
n'
+
'<a href="'
+
url2
+
'">'
+
url2
+
'</a>'
);
mediaEmbedder
.
replaceLinksWithEmbeds
(
element
);
assert
.
equal
(
element
.
childElementCount
,
2
);
assert
.
equal
(
element
.
children
[
0
].
tagName
,
'IFRAME'
);
assert
.
equal
(
element
.
children
[
0
].
src
,
'https://www.youtube.com/embed/QCkm0lL-6lc'
);
assert
.
equal
(
element
.
children
[
1
].
tagName
,
'IFRAME'
);
assert
.
equal
(
element
.
children
[
1
].
src
,
'https://www.youtube.com/embed/abcdefg'
);
});
});
h/static/styles/annotations.scss
View file @
1a3d7e0d
...
...
@@ -99,6 +99,11 @@ $annotation-card-left-padding: 10px;
.excerpt
{
max-height
:
16
.2em
;
}
}
.annotation-media-embed
{
width
:
369px
;
height
:
208px
;
}
.annotation-user
{
color
:
$text-color
;
font-weight
:
bold
;
...
...
h/templates/client/annotation.html
View file @
1a3d7e0d
...
...
@@ -72,8 +72,9 @@
<section
name=
"text"
class=
"annotation-body"
>
<excerpt
enabled=
"vm.feature('truncate_annotations') && !vm.editing()"
>
<markdown
ng-model=
"vm.form.text"
read-only=
"!vm.editing()"
></markdown>
read-only=
"!vm.editing()"
embeds-enabled=
"vm.feature('embed_media')"
>
</markdown>
</excerpt>
</section>
<!-- / Body -->
...
...
h/templates/client/markdown.html
View file @
1a3d7e0d
...
...
@@ -17,4 +17,4 @@
ng-hide=
"readOnly || preview"
ng-click=
"$event.stopPropagation()"
ng-required=
"required"
></textarea>
<div
class=
"styled-text js-markdown-preview"
ng-class=
"preview && 'markdown-preview'"
ng-dblclick=
"togglePreview()"
ng-
bind-html=
"rendered"
ng-
show=
"readOnly || preview"
></div>
<div
class=
"styled-text js-markdown-preview"
ng-class=
"preview && 'markdown-preview'"
ng-dblclick=
"togglePreview()"
ng-show=
"readOnly || preview"
></div>
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