mirror of
https://github.com/aelve/guide.git
synced 2024-12-23 04:42:24 +03:00
Implement merging for traits
This commit is contained in:
parent
57afee3b64
commit
ccb8693a97
@ -34,6 +34,7 @@ executable guide
|
||||
Config
|
||||
Types
|
||||
Utils
|
||||
Merge
|
||||
Cache
|
||||
Markdown
|
||||
JS
|
||||
@ -63,8 +64,8 @@ executable guide
|
||||
, friendly-time == 0.4.*
|
||||
, hashable
|
||||
, http-types
|
||||
, iproute == 1.7.*
|
||||
, ilist
|
||||
, iproute == 1.7.*
|
||||
, lucid >= 2.9.5 && < 3
|
||||
, megaparsec == 4.4.*
|
||||
, microlens-platform >= 0.2.3
|
||||
@ -72,15 +73,18 @@ executable guide
|
||||
, mtl >= 2.1.1
|
||||
, neat-interpolation == 0.3.*
|
||||
, network
|
||||
, patches-vector
|
||||
, path-pieces
|
||||
, random >= 1.1
|
||||
, safecopy
|
||||
, shortcut-links >= 0.4.2
|
||||
, split
|
||||
, stm-containers == 0.2.10.*
|
||||
, template-haskell
|
||||
, text-all == 0.3.*
|
||||
, time >= 1.5
|
||||
, transformers
|
||||
, vector
|
||||
, wai
|
||||
, wai-middleware-metrics
|
||||
, wai-middleware-static
|
||||
|
99
src/JS.hs
99
src/JS.hs
@ -43,6 +43,7 @@ allJSFunctions = JS . T.unlines . map fromJS $ [
|
||||
autosizeTextarea,
|
||||
expandHash,
|
||||
expandItemNotes,
|
||||
showDiffPopup,
|
||||
-- Creating parts of interface
|
||||
makeTraitEditor,
|
||||
makeItemNotesEditor,
|
||||
@ -289,6 +290,73 @@ expandItemNotes =
|
||||
switchSection("#item-notes-"+itemId, "expanded");
|
||||
|]
|
||||
|
||||
showDiffPopup :: JSFunction a => a
|
||||
showDiffPopup =
|
||||
makeJSFunction "showDiffPopup" ["ours", "modified", "merged", "send"]
|
||||
[text|
|
||||
dialog = $("<div>", {
|
||||
"class" : "diff-popup"
|
||||
})[0];
|
||||
choices = $("<div>", {
|
||||
"class" : "diff-choices"
|
||||
})[0];
|
||||
|
||||
// our version
|
||||
choiceOurs = $("<div>", {
|
||||
"class" : "var-a" })[0];
|
||||
textOurs = $("<div>", {
|
||||
"class" : "text",
|
||||
"text" : ours })[0];
|
||||
headerOurs = $("<strong>", {
|
||||
"text" : "Your version" })[0];
|
||||
buttonOurs = $("<button>", {
|
||||
"text" : "Submit this version, disregard changes on the server" })[0];
|
||||
$(buttonOurs).click(function() {
|
||||
send(ours); });
|
||||
$(choiceOurs).append(headerOurs, textOurs, buttonOurs);
|
||||
|
||||
// modified version
|
||||
choiceMod = $("<div>", {
|
||||
"class" : "var-b" })[0];
|
||||
textMod = $("<div>", {
|
||||
"class" : "text",
|
||||
"text" : modified })[0];
|
||||
headerMod = $("<strong>", {
|
||||
"text" : "Version on the server" })[0];
|
||||
buttonMod = $("<button>", {
|
||||
"text" : "Accept this version, disregard my changes" })[0];
|
||||
$(buttonMod).click(function() {
|
||||
send(modified); });
|
||||
$(choiceMod).append(headerMod, textMod, buttonMod);
|
||||
|
||||
// building merged
|
||||
choiceMerged = $("<div>", {
|
||||
"class" : "var-merged" })[0];
|
||||
areaMerged = $("<textarea>", {
|
||||
"autocomplete" : "off",
|
||||
"text" : merged })[0];
|
||||
headerMerged = $("<strong>", {
|
||||
"text" : "Merged version (edit if needed)" })[0];
|
||||
buttonMerged = $("<button>", {
|
||||
"text" : "Submit the merged version" })[0];
|
||||
$(buttonMerged).click(function () {
|
||||
send(areaMerged.value); });
|
||||
$(choiceMerged).append(headerMerged, areaMerged, buttonMerged);
|
||||
|
||||
$(choices).append(choiceOurs, choiceMod, choiceMerged);
|
||||
$(dialog).append(choices);
|
||||
|
||||
$.magnificPopup.open({
|
||||
modal: true,
|
||||
items: {
|
||||
src: dialog,
|
||||
type: 'inline' }
|
||||
});
|
||||
|
||||
autosizeTextarea(areaMerged);
|
||||
|]
|
||||
|
||||
|
||||
{- Note [dynamic interface]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -305,7 +373,8 @@ See <https://github.com/aelve/guide/issues/24>.
|
||||
makeTraitEditor :: JSFunction a => a
|
||||
makeTraitEditor =
|
||||
makeJSFunction "makeTraitEditor"
|
||||
["traitNode", "sectionNode", "textareaUid", "content", "itemId", "traitId"]
|
||||
["traitNode", "sectionNode", "textareaUid",
|
||||
"content", "itemId", "traitId"]
|
||||
[text|
|
||||
$(sectionNode).html("");
|
||||
area = $("<textarea>", {
|
||||
@ -316,7 +385,7 @@ makeTraitEditor =
|
||||
"text" : content })[0];
|
||||
area.onkeydown = function (event) {
|
||||
if (event.keyCode == 13) {
|
||||
submitTrait(traitNode, itemId, traitId, area.value);
|
||||
submitTrait(traitNode, itemId, traitId, content, area.value);
|
||||
return false; } };
|
||||
br = $("<br>")[0];
|
||||
a = $("<a>", {
|
||||
@ -479,16 +548,28 @@ addCon =
|
||||
|
||||
submitTrait :: JSFunction a => a
|
||||
submitTrait =
|
||||
makeJSFunction "submitTrait" ["node", "itemId", "traitId", "s"]
|
||||
makeJSFunction "submitTrait"
|
||||
["node", "itemId", "traitId", "original", "ours"]
|
||||
[text|
|
||||
$.post("/haskell/set/item/"+itemId+"/trait/"+traitId, {content: s})
|
||||
.done(function (data) {
|
||||
$.post({
|
||||
url: "/haskell/set/item/"+itemId+"/trait/"+traitId,
|
||||
data: {
|
||||
original : original,
|
||||
content : ours },
|
||||
success: function (data) {
|
||||
$.magnificPopup.close();
|
||||
$(node).replaceWith(data);
|
||||
switchSection(node, "editable");
|
||||
// Switching has to be done here and not in 'Main.renderTrait'
|
||||
// because $.post is asynchronous and will be done *after*
|
||||
// switchSection has worked.
|
||||
switchSection(node, "editable"); },
|
||||
statusCode: {
|
||||
409: function (xhr, st, err) {
|
||||
modified = xhr.responseJSON["modified"];
|
||||
merged = xhr.responseJSON["merged"];
|
||||
showDiffPopup(ours, modified, merged, function (x) {
|
||||
submitTrait(node, itemId, traitId, modified, x) }); } }
|
||||
});
|
||||
// Switching has to be done here and not in 'Main.renderTrait'
|
||||
// because $.post is asynchronous and will be done *after*
|
||||
// switchSection has worked.
|
||||
|]
|
||||
|
||||
submitItemInfo :: JSFunction a => a
|
||||
|
25
src/Main.hs
25
src/Main.hs
@ -65,6 +65,7 @@ import JS (JS(..), allJSFunctions)
|
||||
import Utils
|
||||
import Markdown
|
||||
import Cache
|
||||
import Merge
|
||||
|
||||
|
||||
{- Note [acid-state]
|
||||
@ -80,11 +81,11 @@ This application doesn't use a database – instead, it uses acid-state. Acid-st
|
||||
|
||||
* The data is kept in-memory, but all changes are logged to the disk (which lets us recover the state in case of a crash by reapplying the changes) and you can't access the state directly. When the application exits, it creates a snapshot of the state (called “checkpoint”) and writes it to the disk. Additionally, a checkpoint is created every hour (grep for “createCheckpoint”).
|
||||
|
||||
* acid-state has a nasty surprise – when the state hasn't changed, 'createCheckpoint' appends it to the previous checkpoint. When state doesn't change for a long time, it means that checkpoints can grow to 100 MB or more. So, we employ a dirty bit and use createCheckpoint' instead of createCheckpoint (which doesn't create the checkpoint if the dirty bit isn't set).
|
||||
* acid-state has a nasty feature – when the state hasn't changed, 'createCheckpoint' appends it to the previous checkpoint. When state doesn't change for a long time, it means that checkpoints can grow to 100 MB or more. So, we employ a dirty bit and use createCheckpoint' instead of createCheckpoint. The former only creates the checkpoint if the dirty bit is set, which is good.
|
||||
|
||||
* When any type is changed, we have to write a migration function that would read the old version of the type and turn it into the new version. It's enough to keep just one old version (and even that isn't needed after the migration happened and a new checkpoint has been created). For examples, look at “instance Migrate” in Types.hs. Also, all types involved in acid-state (whether migrate-able or not) have to have a SafeCopy instance, which is generated by 'deriveSafeCopySimple'.
|
||||
|
||||
* There are actually ways to access the state directly (GetGlobalState and SetGlobalState), but the latter should only be used when doing something one-off (like migrating all IDs to a different ID scheme, or whatever).
|
||||
* There are actually ways to access the state directly (GetGlobalState and SetGlobalState), but the latter should only be used when doing something one-off (e.g. if you need to migrate all IDs to a different ID scheme).
|
||||
|
||||
-}
|
||||
|
||||
@ -92,7 +93,8 @@ This application doesn't use a database – instead, it uses acid-state. Acid-st
|
||||
-- creating checkpoints, etc).
|
||||
type DB = AcidState GlobalState
|
||||
|
||||
-- | Update something in the database.
|
||||
-- | Update something in the database. Don't forget to 'invalidateCache' when
|
||||
-- you update something that is cached.
|
||||
dbUpdate :: (MonadIO m, HasSpock m, SpockState m ~ ServerState,
|
||||
EventState event ~ GlobalState, UpdateEvent event)
|
||||
=> event -> m (EventResult event)
|
||||
@ -481,11 +483,20 @@ setMethods = Spock.subcomponent "set" $ do
|
||||
lucidIO $ renderItemNotes category item
|
||||
-- Trait
|
||||
Spock.post (itemVar <//> traitVar) $ \itemId traitId -> do
|
||||
original <- param' "original"
|
||||
content' <- param' "content"
|
||||
invalidateCache' (CacheItemTraits itemId)
|
||||
(edit, trait) <- dbUpdate (SetTraitContent itemId traitId content')
|
||||
addEdit edit
|
||||
lucidIO $ renderTrait itemId trait
|
||||
modified <- view (content.mdText) <$> dbQuery (GetTrait itemId traitId)
|
||||
if modified == original
|
||||
then do
|
||||
invalidateCache' (CacheItemTraits itemId)
|
||||
(edit, trait) <- dbUpdate (SetTraitContent itemId traitId content')
|
||||
addEdit edit
|
||||
lucidIO $ renderTrait itemId trait
|
||||
else do
|
||||
setStatus HTTP.status409
|
||||
json $ M.fromList [
|
||||
("modified" :: Text, modified),
|
||||
("merged" :: Text, merge original content' modified)]
|
||||
|
||||
addMethods :: SpockM () () ServerState ()
|
||||
addMethods = Spock.subcomponent "add" $ do
|
||||
|
83
src/Merge.hs
Normal file
83
src/Merge.hs
Normal file
@ -0,0 +1,83 @@
|
||||
{-# LANGUAGE
|
||||
OverloadedStrings,
|
||||
NoImplicitPrelude
|
||||
#-}
|
||||
|
||||
|
||||
module Merge
|
||||
(
|
||||
merge,
|
||||
)
|
||||
where
|
||||
|
||||
|
||||
-- TODO: get rid of “-- General” everywhere (and in Emacs snippet too)
|
||||
|
||||
-- General
|
||||
import BasePrelude
|
||||
-- Lenses
|
||||
import Lens.Micro.Platform hiding ((&))
|
||||
-- Text
|
||||
import qualified Data.Text.All as T
|
||||
import Data.Text.All (Text)
|
||||
import Data.List.Split
|
||||
-- Vector
|
||||
import qualified Data.Vector as V
|
||||
-- Diffing
|
||||
import qualified Data.Patch as PV
|
||||
|
||||
|
||||
-- | An implementation of a 3-way diff and merge.
|
||||
merge
|
||||
:: Text -- ^ Original text
|
||||
-> Text -- ^ Variant A (preferred)
|
||||
-> Text -- ^ Variant B
|
||||
-> Text -- ^ Merged text
|
||||
merge orig a b = T.concat . V.toList $ PV.apply (pa <> pb') orig'
|
||||
where
|
||||
(orig', a', b') = (orig, a, b) & each %~
|
||||
V.fromList . consolidate . map T.toStrict . break' . T.toString
|
||||
pa = PV.diff orig' a'
|
||||
pb = PV.diff orig' b'
|
||||
(_, pb') = PV.transformWith PV.ours pa pb
|
||||
|
||||
-- | Break a string into words, spaces, and special characters.
|
||||
break' :: String -> [String]
|
||||
break' = split . dropInitBlank . dropFinalBlank . dropInnerBlanks . whenElt $
|
||||
\c -> not (isAlphaNum c) && c /= '\''
|
||||
|
||||
-- | Consolidate some of the things into tokens (like links, consecutive
|
||||
-- spaces, and Markdown elements).
|
||||
consolidate :: [Text] -> [Text]
|
||||
-- spaces
|
||||
consolidate s@(" ":_) =
|
||||
let (l, r) = span (== " ") s
|
||||
in T.concat l : consolidate r
|
||||
-- breaks between paragraphs
|
||||
consolidate s@("\n":_) =
|
||||
let (l, r) = span (== "\n") s
|
||||
in T.concat l : consolidate r
|
||||
-- code block markers
|
||||
consolidate s@("~":_) =
|
||||
let (l, r) = span (== "~") s
|
||||
in if length l >= 3 then T.concat l : consolidate r else l ++ consolidate r
|
||||
consolidate s@("`":_) =
|
||||
let (l, r) = span (== "`") s
|
||||
in if length l >= 3 then T.concat l : consolidate r else l ++ consolidate r
|
||||
-- hrules
|
||||
consolidate s@("-":_) =
|
||||
let (l, r) = span (== "-") s
|
||||
in if length l >= 3 then T.concat l : consolidate r else l ++ consolidate r
|
||||
-- ellipses
|
||||
consolidate (".":".":".":xs) = "..." : consolidate xs
|
||||
-- links
|
||||
consolidate s@("http":":":"/":"/":_) =
|
||||
let (l, r) = span (\x -> x /= ")" && not (isSpace (T.head x))) s
|
||||
in T.concat l : consolidate r
|
||||
consolidate s@("https":":":"/":"/":_) =
|
||||
let (l, r) = span (\x -> x /= ")" && not (isSpace (T.head x))) s
|
||||
in T.concat l : consolidate r
|
||||
consolidate ("(":"@":"hk":")":xs) = "(" : "@hk" : ")" : consolidate xs
|
||||
-- the rest
|
||||
consolidate (x:xs) = x : consolidate xs
|
||||
consolidate [] = []
|
@ -558,6 +558,9 @@ wrapPage pageTitle page = doctypehtml_ $ do
|
||||
return false; };
|
||||
|]
|
||||
includeJS "/jquery.js"
|
||||
-- for modal dialogs
|
||||
includeJS "/magnific-popup.js"
|
||||
includeCSS "/magnific-popup.css"
|
||||
-- See Note [autosize]
|
||||
includeJS "/autosize.js"
|
||||
onPageLoad (JS "autosize($('textarea'));")
|
||||
|
@ -22,6 +22,8 @@ h1 {
|
||||
|
||||
/* Other CSS */
|
||||
|
||||
/* TODO: move }s to new lines */
|
||||
|
||||
#footer {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
@ -189,3 +191,46 @@ textarea.fullwidth {
|
||||
.notes-toc {
|
||||
background-color: rgba(10, 10, 10, 0.1);
|
||||
padding: 1px 0; }
|
||||
|
||||
.diff-popup {
|
||||
background: white;
|
||||
padding: 20px 30px;
|
||||
text-align: left;
|
||||
margin: 40px auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.diff-choices {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
margin: 0px -10px;
|
||||
}
|
||||
|
||||
.diff-choices > * {
|
||||
flex: 1;
|
||||
margin-top: 10px;
|
||||
margin: 7px 5px;
|
||||
min-width: 400px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.diff-choices > * > .text {
|
||||
border: 1px solid gray;
|
||||
padding: 5px 10px;
|
||||
margin: 5px 0px;
|
||||
font-size: 80%;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.diff-choices > .var-a > .text {
|
||||
background-color: #fdd;
|
||||
}
|
||||
.diff-choices > .var-b > .text {
|
||||
background-color: #cfc;
|
||||
}
|
||||
|
||||
.diff-choices > .var-merged > textarea {
|
||||
margin: 5px 0px;
|
||||
width: 100%;
|
||||
}
|
||||
|
351
static/magnific-popup.css
Normal file
351
static/magnific-popup.css
Normal file
@ -0,0 +1,351 @@
|
||||
/* Magnific Popup CSS */
|
||||
.mfp-bg {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1042;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
background: #0b0b0b;
|
||||
opacity: 0.8; }
|
||||
|
||||
.mfp-wrap {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1043;
|
||||
position: fixed;
|
||||
outline: none !important;
|
||||
-webkit-backface-visibility: hidden; }
|
||||
|
||||
.mfp-container {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
padding: 0 8px;
|
||||
box-sizing: border-box; }
|
||||
|
||||
.mfp-container:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
vertical-align: middle; }
|
||||
|
||||
.mfp-align-top .mfp-container:before {
|
||||
display: none; }
|
||||
|
||||
.mfp-content {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
z-index: 1045; }
|
||||
|
||||
.mfp-inline-holder .mfp-content,
|
||||
.mfp-ajax-holder .mfp-content {
|
||||
width: 100%;
|
||||
cursor: auto; }
|
||||
|
||||
.mfp-ajax-cur {
|
||||
cursor: progress; }
|
||||
|
||||
.mfp-zoom-out-cur, .mfp-zoom-out-cur .mfp-image-holder .mfp-close {
|
||||
cursor: -moz-zoom-out;
|
||||
cursor: -webkit-zoom-out;
|
||||
cursor: zoom-out; }
|
||||
|
||||
.mfp-zoom {
|
||||
cursor: pointer;
|
||||
cursor: -webkit-zoom-in;
|
||||
cursor: -moz-zoom-in;
|
||||
cursor: zoom-in; }
|
||||
|
||||
.mfp-auto-cursor .mfp-content {
|
||||
cursor: auto; }
|
||||
|
||||
.mfp-close,
|
||||
.mfp-arrow,
|
||||
.mfp-preloader,
|
||||
.mfp-counter {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none; }
|
||||
|
||||
.mfp-loading.mfp-figure {
|
||||
display: none; }
|
||||
|
||||
.mfp-hide {
|
||||
display: none !important; }
|
||||
|
||||
.mfp-preloader {
|
||||
color: #CCC;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: auto;
|
||||
text-align: center;
|
||||
margin-top: -0.8em;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
z-index: 1044; }
|
||||
.mfp-preloader a {
|
||||
color: #CCC; }
|
||||
.mfp-preloader a:hover {
|
||||
color: #FFF; }
|
||||
|
||||
.mfp-s-ready .mfp-preloader {
|
||||
display: none; }
|
||||
|
||||
.mfp-s-error .mfp-content {
|
||||
display: none; }
|
||||
|
||||
button.mfp-close,
|
||||
button.mfp-arrow {
|
||||
overflow: visible;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
-webkit-appearance: none;
|
||||
display: block;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
z-index: 1046;
|
||||
box-shadow: none;
|
||||
touch-action: manipulation; }
|
||||
|
||||
button::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border: 0; }
|
||||
|
||||
.mfp-close {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
opacity: 0.65;
|
||||
padding: 0 0 18px 10px;
|
||||
color: #FFF;
|
||||
font-style: normal;
|
||||
font-size: 28px;
|
||||
font-family: Arial, Baskerville, monospace; }
|
||||
.mfp-close:hover,
|
||||
.mfp-close:focus {
|
||||
opacity: 1; }
|
||||
.mfp-close:active {
|
||||
top: 1px; }
|
||||
|
||||
.mfp-close-btn-in .mfp-close {
|
||||
color: #333; }
|
||||
|
||||
.mfp-image-holder .mfp-close,
|
||||
.mfp-iframe-holder .mfp-close {
|
||||
color: #FFF;
|
||||
right: -6px;
|
||||
text-align: right;
|
||||
padding-right: 6px;
|
||||
width: 100%; }
|
||||
|
||||
.mfp-counter {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
color: #CCC;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap; }
|
||||
|
||||
.mfp-arrow {
|
||||
position: absolute;
|
||||
opacity: 0.65;
|
||||
margin: 0;
|
||||
top: 50%;
|
||||
margin-top: -55px;
|
||||
padding: 0;
|
||||
width: 90px;
|
||||
height: 110px;
|
||||
-webkit-tap-highlight-color: transparent; }
|
||||
.mfp-arrow:active {
|
||||
margin-top: -54px; }
|
||||
.mfp-arrow:hover,
|
||||
.mfp-arrow:focus {
|
||||
opacity: 1; }
|
||||
.mfp-arrow:before,
|
||||
.mfp-arrow:after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
margin-top: 35px;
|
||||
margin-left: 35px;
|
||||
border: medium inset transparent; }
|
||||
.mfp-arrow:after {
|
||||
border-top-width: 13px;
|
||||
border-bottom-width: 13px;
|
||||
top: 8px; }
|
||||
.mfp-arrow:before {
|
||||
border-top-width: 21px;
|
||||
border-bottom-width: 21px;
|
||||
opacity: 0.7; }
|
||||
|
||||
.mfp-arrow-left {
|
||||
left: 0; }
|
||||
.mfp-arrow-left:after {
|
||||
border-right: 17px solid #FFF;
|
||||
margin-left: 31px; }
|
||||
.mfp-arrow-left:before {
|
||||
margin-left: 25px;
|
||||
border-right: 27px solid #3F3F3F; }
|
||||
|
||||
.mfp-arrow-right {
|
||||
right: 0; }
|
||||
.mfp-arrow-right:after {
|
||||
border-left: 17px solid #FFF;
|
||||
margin-left: 39px; }
|
||||
.mfp-arrow-right:before {
|
||||
border-left: 27px solid #3F3F3F; }
|
||||
|
||||
.mfp-iframe-holder {
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px; }
|
||||
.mfp-iframe-holder .mfp-content {
|
||||
line-height: 0;
|
||||
width: 100%;
|
||||
max-width: 900px; }
|
||||
.mfp-iframe-holder .mfp-close {
|
||||
top: -40px; }
|
||||
|
||||
.mfp-iframe-scaler {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
padding-top: 56.25%; }
|
||||
.mfp-iframe-scaler iframe {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
|
||||
background: #000; }
|
||||
|
||||
/* Main image in popup */
|
||||
img.mfp-img {
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
line-height: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 40px 0 40px;
|
||||
margin: 0 auto; }
|
||||
|
||||
/* The shadow behind the image */
|
||||
.mfp-figure {
|
||||
line-height: 0; }
|
||||
.mfp-figure:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 40px;
|
||||
bottom: 40px;
|
||||
display: block;
|
||||
right: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
z-index: -1;
|
||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
|
||||
background: #444; }
|
||||
.mfp-figure small {
|
||||
color: #BDBDBD;
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
line-height: 14px; }
|
||||
.mfp-figure figure {
|
||||
margin: 0; }
|
||||
|
||||
.mfp-bottom-bar {
|
||||
margin-top: -36px;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
cursor: auto; }
|
||||
|
||||
.mfp-title {
|
||||
text-align: left;
|
||||
line-height: 18px;
|
||||
color: #F3F3F3;
|
||||
word-wrap: break-word;
|
||||
padding-right: 36px; }
|
||||
|
||||
.mfp-image-holder .mfp-content {
|
||||
max-width: 100%; }
|
||||
|
||||
.mfp-gallery .mfp-image-holder .mfp-figure {
|
||||
cursor: pointer; }
|
||||
|
||||
@media screen and (max-width: 800px) and (orientation: landscape), screen and (max-height: 300px) {
|
||||
/**
|
||||
* Remove all paddings around the image on small screen
|
||||
*/
|
||||
.mfp-img-mobile .mfp-image-holder {
|
||||
padding-left: 0;
|
||||
padding-right: 0; }
|
||||
.mfp-img-mobile img.mfp-img {
|
||||
padding: 0; }
|
||||
.mfp-img-mobile .mfp-figure:after {
|
||||
top: 0;
|
||||
bottom: 0; }
|
||||
.mfp-img-mobile .mfp-figure small {
|
||||
display: inline;
|
||||
margin-left: 5px; }
|
||||
.mfp-img-mobile .mfp-bottom-bar {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
top: auto;
|
||||
padding: 3px 5px;
|
||||
position: fixed;
|
||||
box-sizing: border-box; }
|
||||
.mfp-img-mobile .mfp-bottom-bar:empty {
|
||||
padding: 0; }
|
||||
.mfp-img-mobile .mfp-counter {
|
||||
right: 5px;
|
||||
top: 3px; }
|
||||
.mfp-img-mobile .mfp-close {
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
position: fixed;
|
||||
text-align: center;
|
||||
padding: 0; } }
|
||||
|
||||
@media all and (max-width: 900px) {
|
||||
.mfp-arrow {
|
||||
-webkit-transform: scale(0.75);
|
||||
transform: scale(0.75); }
|
||||
.mfp-arrow-left {
|
||||
-webkit-transform-origin: 0;
|
||||
transform-origin: 0; }
|
||||
.mfp-arrow-right {
|
||||
-webkit-transform-origin: 100%;
|
||||
transform-origin: 100%; }
|
||||
.mfp-container {
|
||||
padding-left: 6px;
|
||||
padding-right: 6px; } }
|
4
static/magnific-popup.js
Normal file
4
static/magnific-popup.js
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user