Merge pull request #333 from squidfunk/feature/clipboard-js-integration

Clipboard.js integration
This commit is contained in:
Martin Donath 2017-06-01 09:11:28 +02:00 committed by GitHub
commit d40f6ce69f
20 changed files with 337 additions and 107 deletions

View File

@ -360,8 +360,10 @@ extra:
A new entry at the bottom of the table of contents is generated that is linking
to the comments section. The necessary JavaScript is automatically included.
!!! warning
`site_url` value must be set in `mkdocs.yml` for the Discus integration to load properly.
!!! warning "Requirements"
`site_url` value must be set in `mkdocs.yml` for the Discus integration to
load properly.
[17]: https://disqus.com

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/* ----------------------------------------------------------------------------
* Declarations
* ------------------------------------------------------------------------- */
declare module "clipboard" {
/* Class: Clipboard action */
declare class ClipboardAction {
trigger: HTMLElement,
clearSelection(): void
}
/* Class: Clipboard */
declare class Clipboard {
static isSupported(): boolean,
on(name: string, cb: (action: ClipboardAction) => void): void
}
/* Exports */
declare export default typeof Clipboard
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -38,9 +38,9 @@
<script src="{{ base_url }}/assets/javascripts/modernizr-1df76c4e58.js"></script>
{% endblock %}
{% block styles %}
<link rel="stylesheet" href="{{ base_url }}/assets/stylesheets/application-e2807e330f.css">
<link rel="stylesheet" href="{{ base_url }}/assets/stylesheets/application-c147e0d28f.css">
{% if config.extra.palette %}
<link rel="stylesheet" href="{{ base_url }}/assets/stylesheets/application-f78e5cb881.palette.css">
<link rel="stylesheet" href="{{ base_url }}/assets/stylesheets/application-8817cfa535.palette.css">
{% endif %}
{% endblock %}
{% block fonts %}
@ -149,7 +149,7 @@
{% endblock %}
</div>
{% block scripts %}
<script src="{{ base_url }}/assets/javascripts/application-06a3e72efd.js"></script>
<script src="{{ base_url }}/assets/javascripts/application-6a09300d7e.js"></script>
<script>app.initialize({url:{base:"{{ base_url }}"}})</script>
{% for path in extra_javascript %}
<script src="{{ path }}"></script>

View File

@ -34,9 +34,7 @@
"test:visual:session": "scripts/test/visual/session",
"travis": "scripts/travis"
},
"dependencies": {
"escape-string-regexp": "^1.0.5"
},
"dependencies": {},
"devDependencies": {
"autoprefixer": "^7.0.1",
"babel-core": "^6.23.0",
@ -49,10 +47,12 @@
"babel-register": "^6.23.0",
"babel-root-import": "^4.1.5",
"chalk": "^1.1.3",
"clipboard": "^1.7.1",
"core-js": "^2.4.1",
"css-mqpacker": "^6.0.0",
"custom-event-polyfill": "^0.3.0",
"del": "^2.2.2",
"escape-string-regexp": "^1.0.5",
"eslint": "^3.16.0",
"fastclick": "^1.0.6",
"flow-bin": "^0.46.0",

View File

@ -20,7 +20,9 @@
* IN THE SOFTWARE.
*/
import Clipboard from "clipboard"
import FastClick from "fastclick"
import Material from "./components/Material"
/* ----------------------------------------------------------------------------
@ -48,7 +50,7 @@ function initialize(config) { // eslint-disable-line func-style
})
/* Wrap all data tables for better overflow scrolling */
const tables = document.querySelectorAll("table:not([class])")
const tables = document.querySelectorAll("table:not([class])") // TODO: this is JSX, we should rename the file
Array.prototype.forEach.call(tables, table => {
const wrap = (
<div class="md-typeset__scrollwrap">
@ -63,6 +65,52 @@ function initialize(config) { // eslint-disable-line func-style
wrap.children[0].appendChild(table)
})
/* Clipboard integration */
if (Clipboard.isSupported()) {
const blocks = document.querySelectorAll("div > pre, pre > code")
Array.prototype.forEach.call(blocks, (block, index) => {
const id = `__code_${index}`
/* Create button with message container */
const button = (
<button class="md-clipboard" title="Copy to clipboard"
data-clipboard-target={`#${id} pre, #${id} code`}>
<span class="md-clipboard__message"></span>
</button>
)
/* Link to block and insert button */
const parent = block.parentNode
parent.id = id
parent.insertBefore(button, block)
})
/* Initialize Clipboard listener */
const copy = new Clipboard(".md-clipboard")
/* Success handler */
let timer = null
copy.on("success", action => {
const message = action.trigger.querySelector(".md-clipboard__message")
if (!(message instanceof HTMLElement))
throw new ReferenceError
/* Clear selection and reset debounce logic */
action.clearSelection()
if (timer)
clearTimeout(timer)
/* Set message indicating success and show it */
message.classList.add("md-clipboard__message--active")
message.innerHTML = "Copied to clipboard"
/* Hide message after two seconds */
timer = setTimeout(() => {
message.classList.remove("md-clipboard__message--active")
}, 2000)
})
}
/* Force 1px scroll offset to trigger overflow scrolling */
if (Modernizr.ios) {
const scrollable = document.querySelectorAll("[data-md-scrollfix]")

View File

@ -195,11 +195,17 @@ button[data-md-color-accent] {
}
// Hovered scrollbar thumb
pre::-webkit-scrollbar-thumb:hover,
.codehilite::-webkit-scrollbar-thumb:hover {
pre code::-webkit-scrollbar-thumb:hover,
.codehilite pre::-webkit-scrollbar-thumb:hover {
background-color: $color;
}
// Copy to clipboard active icon
.md-clipboard:hover::before,
.md-clipboard:active::before {
color: $color;
}
// Active or targeted back reference
.footnote li:hover .footnote-backref:hover,
.footnote li:target .footnote-backref {

View File

@ -42,6 +42,7 @@
@import "base/typeset";
@import "layout/base";
@import "layout/clipboard";
@import "layout/content";
@import "layout/header";
@import "layout/footer";

View File

@ -214,43 +214,49 @@ kbd {
// Unformatted code blocks
pre {
position: relative;
margin: 1em 0;
padding: 1rem 1.2rem;
border-radius: 0.2rem;
line-height: 1.4;
overflow: auto;
-webkit-overflow-scrolling: touch;
// [mobile -]: Stretch to whole width
@include break-to-device(mobile) {
margin: 1em -1.6rem;
padding: 1rem 1.6rem;
border-radius: 0;
}
// Override native scrollbar styles
&::-webkit-scrollbar {
width: 0.4rem;
height: 0.4rem;
}
// Style scrollbar thumb
&::-webkit-scrollbar-thumb {
background-color: $md-color-black--lighter;
// Hovered scrollbar thumb
&:hover {
background-color: $md-color-accent;
}
}
// Reset, if code is wrapped inside pre tag
// Actual container with code, overflowing
> code {
display: block;
margin: 0;
padding: 1.05rem 1.2rem;
background-color: transparent;
font-size: inherit;
box-shadow: none;
box-decoration-break: none;
overflow: auto;
// [mobile -]: Increase padding to match text
@include break-to-device(mobile) {
padding: 1.05rem 1.6rem;
}
// Override native scrollbar styles
&::-webkit-scrollbar {
width: 0.4rem;
height: 0.4rem;
}
// Style scrollbar thumb
&::-webkit-scrollbar-thumb {
background-color: $md-color-black--lighter;
// Hovered scrollbar thumb
&:hover {
background-color: $md-color-accent;
}
}
}
}

View File

@ -216,41 +216,52 @@ $codehilite-whitespace: transparent;
// If code blocks are wrapped with codehilite, the styles must be adjusted
// so the marker stretches to the whole width and the padding is respected
.codehilite {
position: relative;
margin: 1em 0;
padding: 1rem 1.2rem 0.8rem;
padding: 0;
border-radius: 0.2rem;
background-color: $md-code-background;
color: $md-code-color;
line-height: 1.4;
overflow: auto;
-webkit-overflow-scrolling: touch;
// Override native scrollbar styles
&::-webkit-scrollbar {
width: 0.4rem;
height: 0.4rem;
}
// Actual container with code, overflowing
pre,
code {
display: block;
margin: 0;
padding: 1.05rem 1.2rem;
background-color: transparent;
overflow: auto;
vertical-align: top;
// Style scrollbar thumb
&::-webkit-scrollbar-thumb {
background-color: $md-color-black--lighter;
// Override native scrollbar styles
&::-webkit-scrollbar {
width: 0.4rem;
height: 0.4rem;
}
// Hovered scrollbar thumb
&:hover {
background-color: $md-color-accent;
// Style scrollbar thumb
&::-webkit-scrollbar-thumb {
background-color: $md-color-black--lighter;
// Hovered scrollbar thumb
&:hover {
background-color: $md-color-accent;
}
}
}
}
// Hack: set pre-tag to inline-block, in order to stetch the content on
// overflow correctly to the whole width
pre {
display: inline-block;
min-width: 100%;
margin: 0;
padding: 0;
background-color: transparent;
overflow: visible;
vertical-align: top;
// If not using Pygments, code will be under pre>code
pre.codehilite {
overflow: visible;
// Actual container with code, overflowing
code {
display: block;
padding: 1.05rem 1.2rem;
overflow: auto;
}
}
@ -286,7 +297,7 @@ $codehilite-whitespace: transparent;
// Add spacing to line number container
.linenodiv {
padding: 1rem 1.2rem 0.8rem;
padding: 1.05rem 1.2rem;
// Stretch the line number container vertically, so it always aligns with
// the code container, even when there's a scrollbar.
@ -327,8 +338,13 @@ $codehilite-whitespace: transparent;
// [mobile -]: Stretch to whole width
@include break-to-device(mobile) {
margin: 1em -1.6rem;
padding: 1rem 1.6rem 0.8rem;
border-radius: 0;
// Actual container with code, overflowing
pre,
code {
padding: 1.05rem 1.6rem;
}
}
}
@ -342,10 +358,17 @@ $codehilite-whitespace: transparent;
border-radius: 0;
// Increase spacing
.codehilite,
.codehilite > pre,
.codehilite > code,
.linenodiv {
padding: 1rem 1.6rem;
}
}
}
// When pymdownx.superfences is enabled but codehilite is disabled, the
// outer container gets this class name by default.
.highlight {
@extend .codehilite;
}
}

View File

@ -49,39 +49,8 @@
}
}
// All headers with permalinks have ids
[id] {
// Add spacing to anchor for offset
&::before {
display: inline-block;
content: "";
}
// Targeted anchor
&:target::before {
margin-top: -(5.6rem + 2.4rem + 1.8rem);
padding-top: (5.6rem + 2.4rem + 1.8rem);
}
// Make permalink visible on hover
&:hover .headerlink,
&:target .headerlink,
& .headerlink:focus {
transform: translate(0, 0);
opacity: 1;
}
// Active or targeted permalink
&:hover .headerlink:hover,
&:target .headerlink,
& .headerlink:focus {
color: $md-color-accent;
}
}
// Hide anchor for top-level heading, as it makes no sense
h1 .headerlink {
h1[id] .headerlink {
display: none;
}
@ -93,18 +62,36 @@
h5: 1.1rem,
h6: 1.1rem
) {
#{$level}[id] {
// Un-targeted anchor
#{$level}[id]::before {
display: block;
margin-top: -$delta;
padding-top: $delta;
}
// Un-targeted anchor
&::before {
display: block;
margin-top: -$delta;
padding-top: $delta;
content: "";
}
// Targeted anchor (56px from header, 24px from sidebar offset)
#{$level}[id]:target::before {
margin-top: -(5.6rem + 2.4rem + $delta);
padding-top: (5.6rem + 2.4rem + $delta);
// Targeted anchor (56px from header, 24px from sidebar offset)
&:target::before {
margin-top: -(5.6rem + 2.4rem + $delta);
padding-top: (5.6rem + 2.4rem + $delta);
}
// Make permalink visible on hover
&:hover .headerlink,
&:target .headerlink,
& .headerlink:focus {
transform: translate(0, 0);
opacity: 1;
}
// Active or targeted permalink
&:hover .headerlink:hover,
&:target .headerlink,
& .headerlink:focus {
color: $md-color-accent;
}
}
}
}

View File

@ -0,0 +1,113 @@
////
/// Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com>
///
/// Permission is hereby granted, free of charge, to any person obtaining a
/// copy of this software and associated documentation files (the "Software"),
/// to deal in the Software without restriction, including without limitation
/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
/// and/or sell copies of the Software, and to permit persons to whom the
/// Software is furnished to do so, subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in
/// all copies or substantial portions of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
/// DEALINGS
////
// ----------------------------------------------------------------------------
// Rules
// ----------------------------------------------------------------------------
// Copy to clipboard
.md-clipboard {
position: absolute;
top: 0.6rem;
right: 0.6rem;
width: 2.8rem;
height: 2.8rem;
border-radius: 0.2rem;
font-size: 1.6rem;
cursor: pointer;
z-index: 1;
// Hack: put everything on the GPU to omit flickering
backface-visibility: hidden;
// Icon
&::before {
@extend %md-icon;
transition:
color 0.25s,
opacity 0.25s;
color: $md-color-black--light;
content: "content_copy";
opacity: 0.25;
// Show on container hover
pre:hover &,
.codehilite:hover & {
opacity: 1;
}
}
// Hovered and active icon
&:hover::before,
&:active::before {
color: $md-color-accent;
}
// Message
&__message {
display: block;
position: absolute;
top: 0;
right: 3.4rem;
padding: 0.6rem 1rem;
transform: translateX(0.8rem);
transition:
transform 0.25s cubic-bezier(0.9, 0.1, 0.9, 0),
opacity 0.175s;
border-radius: 0.2rem;
background: $md-color-black--light;
color: $md-color-white;
font-size: ms(-1);
white-space: nowrap;
opacity: 0;
pointer-events: none;
// Active message
&--active {
transform: translateX(0);
transition:
transform 0.25s cubic-bezier(0.4, 0, 0.2, 1),
opacity 0.175s 0.075s;
opacity: 1;
pointer-events: initial;
}
// Inject content from ARIA label
&::before {
content: attr(aria-label);
}
// Paint a nice speech bubble
&::after {
display: block;
position: absolute;
top: 50%;
right: -0.4rem;
width: 0;
margin-top: -0.4rem;
border-width: 0.4rem 0 0.4rem 0.4rem;
border-style: solid;
border-color: transparent $md-color-black--light;
content: "";
}
}
}

View File

@ -36,7 +36,7 @@
transition: background-color 0.25s;
background-color: $md-color-primary;
color: $md-color-white;
z-index: 1;
z-index: 2;
// Hack: putting the header on the GPU avoids unnecessary repaints
backface-visibility: hidden;

View File

@ -21,7 +21,7 @@
////
// ----------------------------------------------------------------------------
// Rules
// Keyframes
// ----------------------------------------------------------------------------
// Show source facts

View File

@ -277,7 +277,7 @@
{% block scripts %}
<script src="{{ base_url }}/assets/javascripts/application.js"></script>
<script>
app.initialize({ url: { base: "{{ base_url }}", } });
app.initialize({ url: { base: "{{ base_url }}" } });
</script>
{% for path in extra_javascript %}
<script src="{{ path }}"></script>