Skip to content
Snippets Groups Projects
Commit d40f6ce6 authored by Martin Donath's avatar Martin Donath Committed by GitHub
Browse files

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

Clipboard.js integration
parents 9bb8ecc6 745058c7
Branches
Tags
No related merge requests found
Showing
with 337 additions and 107 deletions
......@@ -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
......
/*
* 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
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -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>
......
......@@ -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",
......
......@@ -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]")
......
......@@ -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 {
......
......@@ -42,6 +42,7 @@
@import "base/typeset";
@import "layout/base";
@import "layout/clipboard";
@import "layout/content";
@import "layout/header";
@import "layout/footer";
......
......@@ -214,20 +214,34 @@ 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;
}
// 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;
......@@ -243,14 +257,6 @@ kbd {
background-color: $md-color-accent;
}
}
// Reset, if code is wrapped inside pre tag
> code {
margin: 0;
background-color: transparent;
font-size: inherit;
box-shadow: none;
box-decoration-break: none;
}
}
......
......@@ -216,15 +216,25 @@ $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;
// Actual container with code, overflowing
pre,
code {
display: block;
margin: 0;
padding: 1.05rem 1.2rem;
background-color: transparent;
overflow: auto;
vertical-align: top;
// Override native scrollbar styles
&::-webkit-scrollbar {
width: 0.4rem;
......@@ -240,17 +250,18 @@ $codehilite-whitespace: transparent;
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;
// If not using Pygments, code will be under pre>code
pre.codehilite {
overflow: visible;
vertical-align: top;
// 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;
}
}
......@@ -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 {
&::before {
display: block;
margin-top: -$delta;
padding-top: $delta;
content: "";
}
// Targeted anchor (56px from header, 24px from sidebar offset)
#{$level}[id]:target::before {
&: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;
}
}
}
}
////
/// 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: "";
}
}
}
......@@ -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;
......
......@@ -21,7 +21,7 @@
////
// ----------------------------------------------------------------------------
// Rules
// Keyframes
// ----------------------------------------------------------------------------
// Show source facts
......
......@@ -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>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment