diff --git a/apiMirror.php b/apiMirror.php new file mode 100644 index 0000000..5e0a49e --- /dev/null +++ b/apiMirror.php @@ -0,0 +1,29 @@ + + */ + +// Include functions and settings. + +require_once __DIR__ . "/inc/functions.php"; + +// Check validity of request. + +if (!isset($_GET['args']) || !isset($_GET['area'])) { + echo printErrorPage("File does not exist."); + return; +} + +if (!in_array($_GET['area'], ['events', 'exhibitions'])) { + echo printErrorPage("Using a disallowed value for area."); + return; +} + +// Ensure working environment for frontend. + +ensureEnvironment(); + +echo queryCachePage($settings['mdVersion'] . "?" . urldecode($_GET['args']), $_GET['area'], $settings); + diff --git a/edit/inc/standardHTML.php b/edit/inc/standardHTML.php index 8525876..0292085 100644 --- a/edit/inc/standardHTML.php +++ b/edit/inc/standardHTML.php @@ -36,7 +36,7 @@ function printBackendHead(string $page = "home", string $title = "Home", string $output .= ' - + diff --git a/edit/js/main.js b/edit/js/main.js index 8ee97cf..2b4b85e 100644 --- a/edit/js/main.js +++ b/edit/js/main.js @@ -5,7 +5,7 @@ * * @return {void} */ -function removeElement(elem) { +async function removeElement(elem) { while (elem.firstChild) { elem.removeChild(elem.firstChild); } @@ -29,16 +29,28 @@ document.addEventListener("DOMContentLoaded", function() { let translations = { "en" : { - "uploadFile" : "Upload file", - "submit" : "Submit", - "generate" : "Generate", - "eventCalendar" : "Event calendar", + "uploadFile" : "Upload file", + "submit" : "Submit", + "generate" : "Generate", + "embedCodeGenerator" : "Generator for embed code", + "helpEmbedCode" : "Here you can create embed code.", + "singleObjectTile" : "Single object (tile)", + "singleObjectDetails": "Single object (details)", + "singleCollection" : "Single collection", + "exhibitionCalendar" : "Exhibition calendar", + "eventCalendar" : "Event calendar", }, "de" : { - "uploadFile" : "Eine Datei heraufladen", - "submit" : "Abschicken", - "generate" : "Generieren", - "eventCalendar" : "Veranstaltungen (Kalender)", + "uploadFile" : "Eine Datei heraufladen", + "submit" : "Abschicken", + "generate" : "Generieren", + "embedCodeGenerator" : "Generator für Einbettungen", + "helpEmbedCode" : "Hier können sie den embed-code generieren.", + "singleObjectTile" : "Einzelobjekt (Kachel)", + "singleObjectDetails": "Einzelobjekt (Details)", + "singleCollection" : "Einzelsammlung", + "exhibitionCalendar" : "Ausstellungskalender", + "eventCalendar" : "Veranstaltungen (Kalender)", } }; @@ -159,7 +171,7 @@ document.addEventListener("DOMContentLoaded", function() { } } - (function() { + (async function() { let trigger = document.getElementById("uploadFile"); if (trigger === undefined || trigger === null) return; @@ -206,7 +218,7 @@ document.addEventListener("DOMContentLoaded", function() { overlay.appendChild(uploadForm); document.getElementsByTagName("body")[0].appendChild(overlay); - document.getElementsByTagName("body")[0].addEventListener('keydown', function(e) { + document.getElementsByTagName("body")[0].addEventListener('keydown', async function(e) { if (e.keyCode != 27) return; removeElement(overlay); }); @@ -214,7 +226,7 @@ document.addEventListener("DOMContentLoaded", function() { queryPage( encodeURI('./files.php'), function (request) { - let allFiles = request.response; + let allFiles = JSON.parse(request.response); for (let i = 0, max = allFiles.length; i < max; i++) { console.log(allFiles[i]); @@ -231,14 +243,56 @@ document.addEventListener("DOMContentLoaded", function() { */ (function() { + function generateToolTip(toolTipID, toolTipText, toolTipTitle = "", triggerId = "") { + + let trigger = document.createElement("span"); + + trigger.classList.add("newToolTipTag"); + trigger.classList.add("helpToolTip"); + trigger.setAttribute("data-for", toolTipID); + + let toolTip = document.createElement("div"); + toolTip.id = "tooltip_" + toolTipID; + toolTip.classList.add("newToolTip"); + toolTip.setAttribute("data-title", toolTipTitle); + + let toolTipCont = document.createElement("p"); + toolTipCont.textContent = toolTipText; + toolTip.appendChild(toolTipCont); + + trigger.appendChild(toolTip); + + return trigger; + + } + let generator = document.getElementById("embedGenerator"); if (generator === undefined || generator === null) return; + // Define generator types + let generatorTypes = [ - ["", "", false, "text"], - ["eventCalendar", getTranslation(translations, "eventCalendar"), true, "number"], + ["", "", false], + ["singleObjectTile", getTranslation(translations, "singleObjectTile"), true], + ["singleObjectDetails", getTranslation(translations, "singleObjectDetails"), true], + ["singleCollectionTile", getTranslation(translations, "singleCollection"), true], + ["exhibitionCalendar", getTranslation(translations, "exhibitionCalendar"), true], + ["eventCalendar", getTranslation(translations, "eventCalendar"), true], ]; + // Add help tooltip + + let generatorLabelSpan = document.createElement("span"); + generatorLabelSpan.classList.add("labelLine"); + + let generatorLabel = document.createElement("label"); + generatorLabel.textContent = getTranslation(translations, "embedCodeGenerator"); + + generatorLabelSpan.appendChild(generatorLabel); + generatorLabelSpan.appendChild(generateToolTip("embedGeneratorToolTip", getTranslation(translations, "helpEmbedCode"), getTranslation(translations, "embedCodeGenerator"))); + + generator.appendChild(generatorLabelSpan); + let selectType = document.createElement("select"); for (let i = 0, max = generatorTypes.length; i < max; i++) { @@ -246,7 +300,6 @@ document.addEventListener("DOMContentLoaded", function() { generatorOption.value = generatorTypes[i][0]; generatorOption.textContent = generatorTypes[i][1]; generatorOption.setAttribute("data-useSpecifier", generatorTypes[i][2]); - generatorOption.setAttribute("data-inputType", generatorTypes[i][3]); selectType.appendChild(generatorOption); } @@ -274,7 +327,6 @@ document.addEventListener("DOMContentLoaded", function() { generatorSpecifier.value = ""; if (selectType.options[selectType.selectedIndex].getAttribute("data-useSpecifier") == "true") { generatorSpecifier.classList.remove("invisible"); - generatorSpecifier.type = selectType.options[selectType.selectedIndex].getAttribute("data-inputType"); } else generatorSpecifier.classList.add("invisible"); @@ -283,13 +335,20 @@ document.addEventListener("DOMContentLoaded", function() { }); - buttonGenerate.addEventListener('click', function(e) { + function runGenerator() { if (selectType.value == "") return; generatorFieldCont.textContent = "[" + selectType.value + "]"; if (generatorSpecifier.value != "") { generatorFieldCont.textContent += "{" + generatorSpecifier.value + "}"; } + } + + generatorSpecifier.addEventListener('keydown', function(e) { + if (e.keyCode != 13) return; + e.stopPropagation(); e.preventDefault(); + runGenerator(); }); + buttonGenerate.addEventListener('click', function() { runGenerator(); }); generator.appendChild(generatorField); diff --git a/edit/settings.php b/edit/settings.php index 4d86b6e..9a1abb9 100644 --- a/edit/settings.php +++ b/edit/settings.php @@ -22,7 +22,7 @@ $pages = loadPages(); // Load overview of pages. */ // Check for vars. -loadHttpToGlobals(["task", "startPage", "pageTitle", "logo", "url", "mdVersion", "maxFileSize", "defaultLang"]); +loadHttpToGlobals(["task", "startPage", "pageTitle", "logo", "url", "mdVersion", "mdImgFolder", "cacheRefreshInterval", "maxFileSize", "defaultLang"]); if (isset($task) and $task == "update") { // Adding new users. @@ -32,11 +32,10 @@ if (isset($task) and $task == "update") { // Adding new users. } // Ensure that URLs end with a trailing slash. - if (isset($mdVersion)) { - $mdVersion = rtrim($mdVersion, "/") . "/"; - } + if (isset($mdVersion)) $mdVersion = rtrim($mdVersion, "/") . "/"; + if (isset($mdImgFolder)) $mdImgFolder = rtrim($mdImgFolder, "/") . "/"; - foreach (["startPage", "pageTitle", "logo", "url", "mdVersion", "maxFileSize", "defaultLang"] as $var) { + foreach (["startPage", "pageTitle", "logo", "url", "mdVersion", "mdImgFolder", "cacheRefreshInterval", "maxFileSize", "defaultLang"] as $var) { if (isset($$var)) $settings[$var] = $$var; } @@ -117,6 +116,20 @@ echo ' ' . generateHelpToolTip("helpMDVersion", $translations['mdVersion'], $translations['helpMDVersion']) . ' + + + + + ' . generateHelpToolTip("helpMDimgFolder", $translations['mdImgFolder'], $translations['helpMDimgFolder']) . ' + + + + + + + ' . generateHelpToolTip("helpCacheRefreshInterval", $translations['cacheRefreshInterval'], $translations['helpCacheRefreshInterval']) . ' + + diff --git a/edit/themes/default/default.css b/edit/themes/default/default.css index e330d17..b5cca4b 100644 --- a/edit/themes/default/default.css +++ b/edit/themes/default/default.css @@ -222,22 +222,28 @@ main { padding: .5em 5em 3em 3em; } */ #staticPageOptions > * { display: block; padding: .5em 1rem; } -#staticPageOptions > .labelLine { display: table; width: 100%; } -#staticPageOptions .helpToolTip { width: 2em; text-align: center; } -#staticPageOptions > .labelLine > * { display: table-cell; } -#staticPageOptions label { font-weight: bold; } #staticPageOptions select { display: block; width: 100%; } #staticPageOptions button { display: block; width: 100%; background: #0277BD; color: #FFF; transition: background .4s; } #staticPageOptions button:hover { background: #039BE5; } +#embedGenerator .helpToolTip, +#staticPageOptions .helpToolTip { width: 2em; text-align: center; } + #pageTools > #embedGenerator { display: block; margin-bottom: 1em; padding-bottom: 1em; box-sizing: content-box; border-bottom: 1px solid #D6D6D6; } #embedGenerator > * { display: block; width: 100%; } #embedGenerator .buttonLike { cursor: pointer; } +#embedGenerator > .labelLine { margin-bottom: .5em; } #pageTools > * { padding: 0 1rem; } +#staticPageOptions > .labelLine, +#embedGenerator > .labelLine, +.labelLine { display: table; width: 100%; } +.labelLine > * { display: table-cell; } +.labelLine label { font-weight: bold; } + /************ * Login Page */ diff --git a/edit/translations/en.php b/edit/translations/en.php index 1ee0a8d..33af482 100644 --- a/edit/translations/en.php +++ b/edit/translations/en.php @@ -29,6 +29,8 @@ $translations = [ "helpURL" => "

URL of the page. Filling out this field helps with optimization for search engines.

", "startPage" => "Start page", "helpStartPage" => "

The start page of the public site.

", + "mdImgFolder" => "Image folder (md)", + "helpMDimgFolder" => "

The folder in which image files are stored in the given instance of museum-digital.

", "logo" => "Logo", "helpLogo" => "

The logo of the site. Is mainly used for the little icon you see in the browser, right next to the title of the tab.

", "mdVersion" => "Version of Museum-Digital", @@ -41,6 +43,14 @@ $translations = [ "helpLanguage" => "

The default language of this instance of md:cms.

", "maxFileSize" => "Maximum upload size", "helpMaxFileSize" => "

The maximum file size of file uploads.

", + "cacheRefreshInterval" => "Cache Refresh Interval", + "helpCacheRefreshInterval" => "

This setting determines how often contents fetched from museum-digital should be refreshed (the number equals X seconds). +

+

", "IDatMD" => "ID at museum-digital", "embedFromMD" => "Embed from museum-digital", diff --git a/inc/functions.php b/inc/functions.php index 620061f..f685c8b 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -69,13 +69,15 @@ function ensureEnvironment() { $settings = array_merge( [ - "startPage" => "1", - "pageTitle" => "md:cms", - "logo" => "", - "url" => "", - "mdVersion" => "https://www.museum-digital.de/nat/", - "maxFileSize" => 300000, - "defaultLang" => "en" + "startPage" => "1", + "pageTitle" => "md:cms", + "logo" => "", + "url" => "", + "cacheRefreshInterval" => 0, + "mdVersion" => "https://rlp.museum-digital.de/", + "mdImgFolder" => "https://rlp.museum-digital.de/data/rlp/", + "maxFileSize" => 300000, + "defaultLang" => "en" ], json_decode(file_get_contents(__DIR__ . "/../data/settings.json"), true) ); @@ -109,6 +111,42 @@ function loadPublicPages() { } +/** + * Query or cache pages. + * + * @param string $url URL to query. + * @param string $area The type of the queried page. If caching is enabled, renew cache every X seconds. + * @param array $settings Settings variable. + * + * @return array + */ +function queryCachePage(string $url, string $area = "", array $settings = ['cacheRefreshInterval' => 0]) { + + // Ignore caching if cacheRefreshInterval equals zero. + if ($settings['cacheRefreshInterval'] == 0) { + return file_get_contents($url); + } + + $fileDir = __DIR__ . "/../data/caches/$area"; + ensureDir($fileDir); + + $fileName = md5($url); + $filePath = "$fileDir/$fileName.json"; + + // Load from cache. + if (file_exists($filePath) && time() - filemtime($filePath) < $settings['cacheRefreshInterval']) { + return file_get_contents($filePath); + } + + // Refresh cache. + + $contents = file_get_contents($url); + file_put_contents($filePath, $contents, LOCK_EX); + + return $contents; + +} + /** * Function scanDirConts is a wrapper around scandir(), which removes [".", ".."]. * @@ -320,4 +358,78 @@ function checkPreviewAccess($sessionStarted = false) { } +/** + * Function checking if a string starts with another. + * + * @param string $haystack String to check. + * @param string $needle Potential start of $haystack. + * + * @return boolean + */ +function startswith(string $haystack, string $needle):bool { + if (substr($haystack, 0, strlen($needle)) == $needle) return (true); + else return (false); +} + +/** + * Function checking if a string starts with any input from the input array. + * + * @param string $haystack String to check. + * @param string[] $needles Array containing potential start values of $haystack. + * + * @return boolean + */ +function startswithAny(string $haystack, array $needles):bool { + $output = false; + foreach ($needles as $needle) { + $output = startswith($haystack, $needle); + if ($output == true) return $output; + } + return $output; +} + +/** + * Curling web pages. + * Function to check errors. + * + * @param string $url URL to query. + * @param string $host Authentication data. Optional. + * + * @return string + */ +function runCurl(string $url, string $host = ""):string { + + $curl = curl_init(); + + curl_setopt($curl, CURLOPT_URL, $url); + // curl_setopt($curl, CURLOPT_RESOLVE, ["www.example.com:443:172.16.1.1"]); + curl_setopt($curl, CURLOPT_ENCODING, ''); + if ($host) curl_setopt($ch, CURLOPT_HTTPHEADER, array("Host: $host")); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + $result = curl_exec($curl); + + curl_close($curl); + return $result; + +} + +/** + * This function cuts down a string and adds a period in case it's longer than length to create a snippet. + * + * @param string $string Input text to cut down + * @param int $length Length of the snippet to create + * + * @return string + */ +function createTextSnippet($string, $length) { + + if (strlen($string) > $length) { + $string = substr($string, 0, $length); + $string = substr($string, 0, strrpos($string, ' ')); + $string .= ' ...'; + } + return $string; + +} + ?> diff --git a/inc/mdEmbeds.php b/inc/mdEmbeds.php index 99cc1d0..2695bdd 100644 --- a/inc/mdEmbeds.php +++ b/inc/mdEmbeds.php @@ -11,13 +11,18 @@ * Function for checking a text for the existence of pseudocode * for embedding from museum-digital. * - * @param string $text Input string. + * @param string $text Input string. + * @param array $settings General settings. * * @return string */ -function checkForEmbeds(string $text):string { +function checkForEmbeds(string $text, array $settings):string { $embedOptions = [ + "singleObjectTile", + "singleObjectDetails", + "singleCollectionTile", + "exhibitionCalendar", "eventCalendar" ]; @@ -26,13 +31,32 @@ function checkForEmbeds(string $text):string { if (strpos($text, $option) === false) continue; $position = strpos($text, $option) - 1; - $nextTag = strpos($text, "<", $position); - $nextWhitespace = strpos($text, " ", $position); + $nextTag = $nextWhitespace = strlen($text); + if (strpos($text, "<", $position) !== false) $nextTag = strpos($text, "<", $position); + if (strpos($text, " ", $position) !== false) $nextWhitespace = strpos($text, " ", $position); - $end = min($nextTag, $nextWhitespace, strlen($text)); + $end = min($nextTag, $nextWhitespace); - $pseudocode = substr($text, $position, $position + $end); - echo $pseudocode; + $pseudocode = substr($text, $position, $end - $position); + + $command = substr($pseudocode, 1, strpos($pseudocode, "]") - 1); + $arguments = []; + if (strpos($pseudocode, "{") !== false) $arguments = explode("&", substr($pseudocode, strpos($pseudocode, "{") + 1, -1)); + + switch ($command) { + case "singleObjectTile": + $text = str_replace($pseudocode, embedObject($arguments, $settings), $text); + break; + case "singleObjectDetails": + $text = str_replace($pseudocode, embedObject($arguments, $settings, true), $text); + break; + case "exhibitionCalendar": + $text = str_replace($pseudocode, embedExhibitionCalendar($arguments), $text); + break; + case "eventCalendar": + $text = str_replace($pseudocode, embedEventCalendar($arguments), $text); + break; + } } @@ -40,4 +64,302 @@ function checkForEmbeds(string $text):string { } +/** + * Function drawObjectTile creates a tile with just the most basic information on an object. + * + * @param string[] $contents Input data fetched from the object API at museum-digital. + * @param array $settings Settings variable. + * + * @return string + */ +function drawObjectTile(array $contents, array $settings):string { + + $output = ' +
+ '; + + if (count($contents['object_images']) > 0) { + foreach ($contents['object_images'] as $image) { + if ($image['is_main'] != "j") continue; + $output .= ' + '; + } + } + + $output .= ' +
+ +

' . $contents['object_name'] . '

+ +
+ + ' . $contents['object_name'] . ' +
+ +
+ +
+ '; + + return $output; + +} + +/** + * Function drawObjectDetails creates a tile with just the most basic information on an object. + * + * @param string[] $contents Input data fetched from the object API at museum-digital. + * @param array $settings Settings variable. + * + * @return string + */ +function drawObjectDetails(array $contents, array $settings):string { + + $output = ' +
+ +

' . $contents['object_name'] . '

+ '; + + if (count($contents['object_images']) > 0) { + foreach ($contents['object_images'] as $image) { + if ($image['is_main'] != "j") continue; + $output .= ' + '; + } + } + + $output .= ' +
+ '; + + $output .= ' +

+ ' . $contents['object_description'] . ' +

'; + + $simpleDefinedConts = [ + "object_material_technique", + "object_dimensions", + ]; + + $output .= " +
"; + if (count($contents['object_collection']) > 0) { + $output .= ' +
'; + foreach ($contents['object_collection'] as $collection) { + $output .= ' +
' . $collection['collection_name'] . '
+ '; + } + } + + foreach ($simpleDefinedConts as $value) { + $output .= " +
+
" . $contents[$value] . "
+ "; + } + + if (count($contents['object_tags']) > 0) { + $output .= ' +
'; + foreach ($contents['object_tags'] as $tag) { + $output .= ' +
' . $tag['tag_name'] . '
+ '; + } + } + + if (count($contents['object_relation_places']) > 0) { + $output .= ' +
'; + foreach ($contents['object_relation_places'] as $place) { + $output .= ' +
' . $place['place']['place_name'] . '
+ '; + } + } + + if (count($contents['object_relation_people']) > 0) { + $output .= ' +
'; + foreach ($contents['object_relation_people'] as $people) { + $output .= ' +
' . $people['people']['displayname'] . '
+ '; + } + } + $output .= " +
"; + + if (count($contents['object_events']) > 0) { + $output .= " +
"; + foreach ($contents['object_events'] as $event) { + $output .= ' + +
+
+ + '; + + if (isset($event['people'])) $output .= ' + + + + '; + + if (isset($event['time'])) $output .= ' + + + + '; + + if (isset($event['place'])) $output .= ' + + + + '; + $output .= ' +
+ +
+ ' . $event['time']['time_name'] . ' +
+ +
+
+ '; + } + $output .= ' +
+ '; + } + + $output .= ' +

+ + : ' . $contents['object_last_updated'] . ' + : ' . $contents['licence']['metadata_rights_status'] . ' + +

+
+ '; + + $output .= ' +
+ '; + + return $output; + +} + +/** + * Function for displaying objects. + * + * @param array $arguments Arguments / GET parameters for urls to query. + * @param array $settings Settings variable. + * @param boolean $showDetails Optional. By default, only a tile with most basic information is displayed. + * + * @return string + */ +function embedObject(array $arguments, array $settings, bool $showDetails = false):string { + + $toIgnore = ["t=", "output="]; + $srcArgs = "t=objekt"; + foreach ($arguments as $arg) { + if (startsWithAny($arg, $toIgnore)) continue; + $srcArgs .= "&" . $arg; + } + $srcArgs .= "&output=json"; + + $contents = json_decode(queryCachePage($settings['mdVersion'] . "?$srcArgs", "object", $settings), true); + + if (!$showDetails) return drawObjectTile($contents, $settings); + else return drawObjectDetails($contents, $settings); + +} + +/** + * Function for embedding event calendar. + * + * @param array $arguments Arguments / GET parameters for urls to query. + * + * @return string + */ +function embedExhibitionCalendar(array $arguments):string { + + $toIgnore = ["t=", "calendar=", "output="]; + $srcArgs = "t=exhibitions_overview&calendar=1"; + foreach ($arguments as $arg) { + if (startsWithAny($arg, $toIgnore)) continue; + $srcArgs .= "&" . $arg; + } + $srcArgs .= "&output=json"; + + $srcURL = "apiMirror.php?area=exhibitions&args=" . urlencode($srcArgs); + + if (isset($_GET['y'])) $y = $_GET['y']; + else $y = date("Y"); + if (isset($_GET['m'])) $m = $_GET['m']; + else $m = date("m"); + + $prevMonth = strtotime('-1 month', strtotime("$y-$m-01")); + $nextMonth = strtotime('+1 month', strtotime("$y-$m-01")); + + $output = ' +
+
+ '; + + return $output; +} + +/** + * Function for embedding event calendar. + * + * @param array $arguments Arguments / GET parameters for urls to query. + * + * @return string + */ +function embedEventCalendar(array $arguments):string { + + $toIgnore = ["t=", "calendar=", "output="]; + $srcArgs = "t=events&calendar=1"; + foreach ($arguments as $arg) { + if (startsWithAny($arg, $toIgnore)) continue; + $srcArgs .= "&" . $arg; + } + $srcArgs .= "&output=json"; + + $srcURL = "apiMirror.php?area=events&args=" . urlencode($srcArgs); + + if (isset($_GET['y'])) $y = $_GET['y']; + else $y = date("Y"); + if (isset($_GET['m'])) $m = $_GET['m']; + else $m = date("m"); + + $prevMonth = strtotime('-1 month', strtotime("$y-$m-01")); + $nextMonth = strtotime('+1 month', strtotime("$y-$m-01")); + + $output = ' +
+
+ '; + + return $output; +} + ?> diff --git a/inc/search.php b/inc/search.php new file mode 100644 index 0000000..261ced4 --- /dev/null +++ b/inc/search.php @@ -0,0 +1,52 @@ + + */ + +/** + * Function for searching in all static pages. + * + * @param string $searchTerm Search term. + * + * @return array + */ +function searchInPages(string $searchTerm) { + + $files = scanDirConts(__DIR__ . "/../data/static"); + + $results = []; + foreach ($files as $file) { + + $curResults = []; + $contents = json_decode(file_get_contents(__DIR__ . "/../data/static/$file"), true); + + if (!$contents['public']) continue; // Don't display non-public files. + + $curResults['inTitle'] = substr_count($contents['title'], $searchTerm); + $curResults['inDescription'] = substr_count($contents['content'], $searchTerm); + $curResults['priority'] = $curResults['inTitle'] * 3 + $curResults['inDescription']; + + if ($curResults['priority'] == 0) continue; + + $curResults['title'] = $contents['title']; + // Sanitize content for snippets. + $snippet = preg_replace('/[\[{\(].*[\]}\)]/U' , '', strip_tags($contents['content'])); + $curResults['snippet'] = createTextSnippet($snippet, 180); + $results[$file] = $curResults; + + } + + usort($results, function($a, $b) { + if ($a == $b) return 0; + return ($a > $b) ? -1 : 1; + }); + + return $results; + +} + +?> diff --git a/inc/standardHTML.php b/inc/standardHTML.php index c1b325a..9026bd9 100644 --- a/inc/standardHTML.php +++ b/inc/standardHTML.php @@ -10,25 +10,30 @@ /** * Prints the head element of an HTML page in the public frontend. * - * @param string $page ID of the current page. - * @param string $title Title of the page. - * @param string $icon The icon of the website. + * @param array $settings Settings variable. + * @param string $page ID of the current page. + * @param string $title Title of the page. + * @param string $icon The icon of the website. + * @param string $additional Additional HTML to inject. * * @return string */ -function printPublicHead(string $page = "home", string $title = "Home", string $icon = ""):string { +function printPublicHead(array $settings, string $page = "home", string $title = "Home", string $icon = "", $additional = ""):string { $output = ' - + ' . $title . ' + '; + $output .= $additional; + if ($icon) { $output .= ' @@ -39,6 +44,7 @@ function printPublicHead(string $page = "home", string $title = "Home", string $ + @@ -151,4 +157,37 @@ function printErrorPage(string $content):string { } +/** + * Function for generating the standard navigation of the public parts of the page. + * + * @param array $pages List of all pages. + * + * @return string + */ +function generatePublicNav($pages):string { + $output = ''; + return $output; +} + ?> diff --git a/index.php b/index.php index 5b1026d..3a8d4f1 100644 --- a/index.php +++ b/index.php @@ -5,6 +5,10 @@ * @author Joshua Ramon Enslin */ +// Include functions and settings. + +require_once __DIR__ . "/inc/functions.php"; + // Check validity of request. if (isset($_GET['id']) and !file_exists(__DIR__ . "/data/static/" . $_GET['id'] . ".json")) { @@ -12,10 +16,6 @@ if (isset($_GET['id']) and !file_exists(__DIR__ . "/data/static/" . $_GET['id'] return; } -// Include functions and settings. - -require __DIR__ . "/inc/functions.php"; - // Ensure working environment for frontend. ensureEnvironment(); @@ -25,8 +25,6 @@ $pages = loadPublicPages(); // Load overview of pages. * Load data. */ - - /* * @var array $tPage The variable contains the main data on the displayed page. */ @@ -53,32 +51,11 @@ if (!$tPage['public']) { * Output */ -echo printPublicHead($id, $settings['pageTitle'], $settings['logo']); +echo printPublicHead($settings, $id, $settings['pageTitle'], $settings['logo']); echo printPublicHeader($settings['pageTitle']); echo printStaticPagePart("banner", "header"); // Print aside (if need be) -echo ''; +echo generatePublicNav($pages); echo '
@@ -89,7 +66,7 @@ echo '
'; echo '

' . $tPage['title'] . '

'; -echo checkForEmbeds($tPage['content']); +echo checkForEmbeds($tPage['content'], $settings); echo '
diff --git a/js/main.js b/js/main.js index fbfd403..1f6b5f7 100644 --- a/js/main.js +++ b/js/main.js @@ -14,12 +14,46 @@ document.addEventListener("DOMContentLoaded", function() { let translations = { "en" : { + "More" : "More", + "MoreAtMuseumDigital" : "museum-digital", + "ObjectAtMuseumDigital" : "Object entry at museum-digital", + "Collection" : "Collection", + "object_material_technique" : "Material / Technique", + "object_dimensions" : "Dimensions", + "Metadata" : "Metadata", + "LastUpdated" : "Last Updated", + "Licence" : "Licence", + "Tags" : "Tags", + "People" : "People", + "Places" : "Places", + "Times" : "Times", + "Search" : "Search", + "SearchingFor" : "Searching for", + "... who" : "... who", + "... where" : "... where", + "... when" : "... when", + "eventType1" : "Created", }, "de" : { - - }, - "hu" : { - + "More" : "Mehr", + "MoreAtMuseumDigital" : "museum-digital", + "ObjectAtMuseumDigital" : "Objekt bei museum-digital", + "Collection" : "Sammlung", + "object_material_technique" : "Material / Technik", + "object_dimensions" : "Ausmaße", + "Metadata" : "Metadaten", + "LastUpdated" : "Zuletzt geupdatet", + "Licence" : "Lizenz", + "Tags" : "Schlagworte", + "People" : "Personen", + "Places" : "Orte", + "Times" : "Zeiten", + "Search" : "Suche", + "SearchingFor" : "Suche nach", + "... who" : "... wer", + "... where" : "... wo", + "... when" : "... wann", + "eventType1" : "Hergestellt", } }; @@ -108,6 +142,13 @@ document.addEventListener("DOMContentLoaded", function() { } } + (function() { + let toTranslate = document.getElementsByClassName("toTranslate"); + for (let i = 0, max = toTranslate.length; i < max; i++) { + console.log(toTranslate[i].getAttribute("data-content")); + toTranslate[i].textContent = getTranslation(translations, toTranslate[i].getAttribute("data-content")); + } + })(); }); diff --git a/js/mdCalendar.js b/js/mdCalendar.js new file mode 100644 index 0000000..0a26a7a --- /dev/null +++ b/js/mdCalendar.js @@ -0,0 +1,480 @@ +/** + * mdCalendar.js + */ + +document.addEventListener("DOMContentLoaded", function() { + + /** + * @var {array} translations Array of all translations. + */ + let translations = { + "en" : { + "dow0" : "Sunday", + "dow1" : "Monday", + "dow2" : "Tuesday", + "dow3" : "Wednesday", + "dow4" : "Thursday", + "dow5" : "Friday", + "dow6" : "Saturday", + "mon0" : "January", + "mon1" : "February", + "mon2" : "March", + "mon3" : "April", + "mon4" : "May", + "mon5" : "June", + "mon6" : "July", + "mon7" : "August", + "mon8" : "September", + "mon9" : "October", + "mon10" : "November", + "mon11" : "December", + "Today" : "Today", + "Title" : "Title", + "Start" : "Start", + "End" : "End", + "Location" : "Location" + }, + "de" : { + "dow0" : "Sonntag", + "dow1" : "Montag", + "dow2" : "Dienstag", + "dow3" : "Mittwoch", + "dow4" : "Donnerstag", + "dow5" : "Freitag", + "dow6" : "Samstag", + "mon0" : "Januar", + "mon1" : "Februar", + "mon2" : "März", + "mon3" : "April", + "mon4" : "Mai", + "mon5" : "Juni", + "mon6" : "Juli", + "mon7" : "August", + "mon8" : "September", + "mon9" : "Oktober", + "mon10" : "November", + "mon11" : "Dezember", + "Today" : "Jetzt", + "Title" : "Titel", + "Start" : "Beginn", + "End" : "Ende", + "Location" : "Ort" + }, + "hu" : { + "dow0": "Vas\u00e1rnap", + "dow1": "H\u00e9tf\u0151", + "dow2": "Kedd", + "dow3": "Szerda", + "dow4": "Cs\u00fct\u00f6rt\u00f6k", + "dow5": "P\u00e9ntek", + "dow6": "Szombat", + "mon0": "Janu\u00e1r", + "mon1": "Febru\u00e1r", + "mon2": "M\u00e1rcius", + "mon3": "\u00c1prilis", + "mon4": "M\u00e1jus", + "mon5": "J\u00fanius", + "mon6": "J\u00falius", + "mon7": "Augusztus", + "mon8": "Szeptember", + "mon9": "Okt\u00f3ber", + "mon10": "November", + "mon11": "December", + "Today": "Ma", + "Title": "C\u00edm", + "Start": "Nyit", + "End": "Bez\u00e1r", + "Location": "Helysz\u00edn" + } + } + + /** + * @var {boolean} Toggle debugging. + */ + let debugging = false; + + /** + * Function queryPage queries a web page and runs the specified function over the output. + * + * @param {string} url URL to query. + * @param {function} func Callback function to run on the request after loading. + * @param {boolean} debug Enable / disable debug mode. + * + * @return {boolean} + */ + function queryPage(url, func, debug = false) { + + let request = new XMLHttpRequest(); + request.open('GET', url); + request.setRequestHeader("Cache-Control", "no-cache"); + request.responseType = 'htm'; + request.send(); + request.onload = function() { + + func(request, debug); + }; + + } + + /** + * Removes all children of an element. + * + * @param {string} id ID of the element to tear down. + * + * @return {void} + */ + function emptyElement(element) { + while (element.firstChild) { + emptyElement(element.firstChild); + } + element.parentNode.removeChild(element); + if (debugging === true) { + console.log("Removed element:"); + console.log(element); + } + } + + function tearDownById(id) { + let target = document.getElementById(id); + if (target !== null) emptyElement(target); + } + + /** + * Returns a requested translation from an array in the currently used language. + * + * @param {mixed[]} list Translation variable. + * @param {string} specifier Specifies which translation to get. + * + * @return {string} + */ + function getTranslation(list, specifier) { + + let preferedLang = document.getElementsByTagName("html")[0].getAttribute("lang"); + + if (list[preferedLang] !== undefined && list[preferedLang][specifier] !== null) return list[preferedLang][specifier]; + return list["en"][specifier]; + + } + + /** + * Function to create calendar. + */ + function createCalendarTable(target, events) { + + let startOfWeek = 1; // The week starts on Monday. + let endOfWeek = 0; // The week ends on Sunday. + + let calLocale = document.getElementsByTagName("html")[0].getAttribute("lang"); + if (calLocale === undefined || calLocale === null) calLocale = "en"; + + function removeToolTips() { + let toolTips = document.getElementsByClassName("mdCToolTip"); + for (let i = 0, max = toolTips.length; i < max; i++) { + emptyElement(toolTips[i]); + } + } + + /** + * Gets all days to display per month. + * Adapted version of function described in https://stackoverflow.com/a/13146828. + * + * @param {int} The month number, 0 based + * @param {int} The year, not zero based, required to account for leap years + * + * @return {Date[]} List with date objects for each day of the month + */ + function getDaysInMonth(month, year) { + + var date = new Date(year, month, 1); + var days = []; + + // If the weekday of the first of the month does not equal 1 (Monday), + // get days until the last monday before the month. + if (date.getDay() !== startOfWeek) { + while (date.getDay() !== startOfWeek) { + date.setDate(date.getDate() - 1); + days.unshift(new Date(date)); + } + } + + date = new Date(year, month, 1); + // Get days of the month + while (date.getMonth() === month) { + days.push(new Date(date)); + date.setDate(date.getDate() + 1); + } + + // If the weekday of the first of the month does not equal 1 (Monday), + // get days until the last monday before the month. + while (date.getDay() !== startOfWeek) { + days.push(new Date(date)); + date.setDate(date.getDate() + 1); + } + return days; + } + + events = (function() { + + let colorSchemeLength = 29; + for (var i = 0, max = events.length; i < max; i++) { + let hash = 0; + for (var j = 0, maxj = events[i].name.length; j < maxj; j++) { + hash = events[i].name.charCodeAt(j) + hash; + } + hash = hash % (colorSchemeLength) - 1; + + events[i].color = hash; + } + return events; + + })(); + + let d = new Date(); + + let year; + if (target.getAttribute("data-year") !== null) year = parseInt(target.getAttribute("data-year")); + else year = d.getFullYear(); + + let month; + if (target.getAttribute("data-month") !== null) month = parseInt(target.getAttribute("data-month") - 1); + else month = d.getMonth(); + + // Create outer div + let mdCalDiv = document.createElement("div"); + mdCalDiv.classList.add("mdCalendar"); + + /** + * Creater header line of the calendar. + */ + (function() { + + let mdCalTitleLine = document.createElement("header"); + mdCalTitleLine.classList.add("mdCTitleLine"); + + let mdCalTitle = document.createElement("h3"); + mdCalTitle.textContent = getTranslation(translations, "mon" + month.toString()); + if (year != d.getFullYear()) mdCalTitle.textContent = mdCalTitle.textContent + " (" + year + ")"; + mdCalTitleLine.appendChild(mdCalTitle); + + let mdCalNav = document.createElement("div"); + mdCalNav.classList.add("mdCNav"); + mdCalTitleLine.appendChild(mdCalNav); + + let mdCalNavPrev = document.createElement("a"); + mdCalNavPrev.href = target.getAttribute("data-prev"); + mdCalNavPrev.rel = "prev"; + mdCalNavPrev.classList.add("mdCNavPrev"); + mdCalNav.appendChild(mdCalNavPrev); + + let mdCalNavNext = document.createElement("a"); + mdCalNavNext.href = target.getAttribute("data-next"); + mdCalNavNext.rel = "next"; + mdCalNavNext.classList.add("mdCNavNext"); + mdCalNav.appendChild(mdCalNavNext); + + let mdCalNavToday = document.createElement("a"); + mdCalNavToday.href = target.getAttribute("data-today"); + mdCalNavToday.textContent = getTranslation(translations, "Today"); + mdCalNav.appendChild(mdCalNavToday); + + mdCalTitleLine.appendChild(mdCalNav); + mdCalDiv.appendChild(mdCalTitleLine); + + })(); + + /** + * Create table. + */ + let table = document.createElement("table"); + table.classList.add("mdCTable"); + + let thead = document.createElement("thead"); + let theadTr = document.createElement("tr"); + + for (let i = 1, max = 7; i < max; i++) { + let th = document.createElement("th"); + th.textContent = getTranslation(translations, "dow" + i.toString()); + theadTr.appendChild(th); + } + let th = document.createElement("th"); + th.textContent = getTranslation(translations, "dow0"); + theadTr.appendChild(th); + + thead.appendChild(theadTr); + table.appendChild(thead); + + let tbody = document.createElement("tbody"); + + let days = getDaysInMonth(month, year); + let tr; + + let daysTDs = []; + + function createSingleEventOverlay(e, parentElement, data) { + + let toolTip = document.createElement("table"); + toolTip.classList.add("mdCToolTip"); + + let tableData = []; + tableData.push(["Title", data["name"]]); + if (data["start"] !== undefined) tableData.push(["Start", data["start"]]); + if (data["end"] !== undefined) tableData.push(["End", data["end"]]); + if (data["place"] !== undefined) tableData.push(["Location", data["place"]]); + + for (let i = 0, max= tableData.length; i < max; i++) { + let nameRow = document.createElement("tr"); + let nameTh = document.createElement("th"); + nameTh.textContent = getTranslation(translations, tableData[i][0]); + nameRow.appendChild(nameTh); + let nameTd = document.createElement("td"); + nameTd.textContent = tableData[i][1]; + nameRow.appendChild(nameTd); + toolTip.appendChild(nameRow); + } + + parentElement.appendChild(toolTip); + + } + + for (let i = 0, max= days.length; i < max; i++) { + + // Begin a new table row every Monday. + if (days[i].getDay() === startOfWeek) { + tr = document.createElement("tr"); + tbody.appendChild(tr); + } + + // Create new TD per day and add appropriate classes. + let td = document.createElement("td"); + if (days[i].getMonth() !== month) td.classList.add("mdCOtherMonth"); + if (days[i].getYear() == d.getYear() && days[i].getMonth() == d.getMonth() && days[i].getDate() == d.getDate()) td.classList.add("mdCToday"); + + tdTitle = document.createElement("time"); + tdTitle.textContent = days[i].getDate(); + + td.appendChild(tdTitle); + tr.appendChild(td); + + // Append dates. + let dayTime = days[i].getTime(); + let dayTimeMinusDay = days[i].getTime() + 24 * 3600 * 1000; + let dayTimePlusDay = days[i].getTime() - 0 * 3600 * 1000; + for (let j = 0, maxj = events.length; j < maxj; j++) { + if (Date.parse(events[j].start) > dayTimeMinusDay || Date.parse(events[j].end) < dayTimePlusDay) continue; + + let eventSignifier = document.createElement("a"); + eventSignifier.classList.add("color" + events[j].color.toString()); + if (events[j].link) eventSignifier.href = events[j].link; + + let eventSignifierText = document.createElement("span"); + eventSignifierText.textContent = events[j].name; + eventSignifier.appendChild(eventSignifierText); + + eventSignifier.addEventListener('mouseover', function(e) { + createSingleEventOverlay(e, eventSignifier, events[j]); + }); + eventSignifier.addEventListener('mouseout', function(e) { + removeToolTips(); + }); + + td.appendChild(eventSignifier); + + } + + // Add posibility for overlay. + tdTitle.addEventListener('click', function(e) { + + tearDownById("mdCOverlay"); + + let overlay = document.createElement("div"); + overlay.id = "mdCOverlay"; + + let eventTitleBar = document.createElement("div"); + eventTitleBar.classList.add("mdCOverlayTitleBar"); + overlay.appendChild(eventTitleBar); + + let title = document.createElement("span"); + title.classList.add("mdCOverlayTitle"); + title.textContent = days[i].toLocaleDateString(calLocale); + eventTitleBar.appendChild(title); + + let closer = document.createElement("span"); + closer.classList.add("mdCOverlayClose"); + closer.addEventListener('click', function(e) { + if (debugging === true) console.log("Clicked close button: Tearing down daily agenda."); + tearDownById("mdCOverlay"); + }); + eventTitleBar.appendChild(closer); + + let ul = document.createElement("ul"); + + // Append events. + for (let j = 0, maxj = events.length; j < maxj; j++) { + + let start = new Date(events[j].start); + let end = new Date(events[j].end); + + if (start.getTime() > dayTime || end.getTime() < dayTimePlusDay) continue; + + let eventLi = document.createElement("li"); + + let eventSignifier = document.createElement("a"); + eventSignifier.textContent = events[j].name; + if (events[j].link) eventSignifier.href = events[j].link; + eventLi.appendChild(eventSignifier); + + let eventP = document.createElement("p"); + eventP.textContent = start.toLocaleDateString(calLocale) + " - " + end.toLocaleDateString(calLocale); + if (events[j].place !== undefined) eventP.textContent = eventP.textContent + ", " + events[j].place; + eventLi.appendChild(eventP); + + let eventDesc = document.createElement("p"); + eventDesc.textContent = events[j].description; + eventLi.appendChild(eventDesc); + + ul.appendChild(eventLi); + + } + + overlay.appendChild(ul); + document.getElementsByTagName("body")[0].appendChild(overlay); + + }); + + if (td.childElementCount > 6) td.classList.add("mdCManyElements"); + daysTDs[days[i].getFullYear() + "-" + days[i].getMonth() + "-" + days[i].getDate()] = td; + + } + + table.appendChild(tbody); + mdCalDiv.appendChild(table); + + target.appendChild(mdCalDiv); + + // Enable closing overlay by pressing escape. + document.addEventListener('keydown', function(e) { + if (e.keyCode !== 27) return; + if (debugging === true) console.log("Pressed escape: Tearing down daily agenda."); + tearDownById("mdCOverlay"); + }); + + return daysTDs; + } + + (function() { + + let calendars = document.getElementsByClassName("mdCalendar"); + for (let i = 0, max = calendars.length; i < max; i++) { + + queryPage( + encodeURI(calendars[i].getAttribute("data-src")), + function (request) { + if (debugging === true) console.log("Loaded\n" + request.response); + let elements = JSON.parse(request.response); + let tCalendar = createCalendarTable(calendars[i], elements); + }); + } + } + )(); + +}); diff --git a/object.php b/object.php new file mode 100644 index 0000000..10e2811 --- /dev/null +++ b/object.php @@ -0,0 +1,66 @@ + + */ + +// Include functions and settings. + +require_once __DIR__ . "/inc/functions.php"; + +// Check validity of request. + +if (!isset($_GET['id']) or !is_numeric($_GET['id'])) { + echo printErrorPage("Object does not exist."); + return; +} + +// Ensure working environment for frontend. + +ensureEnvironment(); +$pages = loadPublicPages(); // Load overview of pages. + +$contents = json_decode(queryCachePage($settings['mdVersion'] . "?t=objekt&oges=" . urlencode($_GET['id']) . "&output=json", "object", $settings), true); + +if (!$contents || (isset($contents[0]) and $contents[0] == "There is no object with this ID yet.")) { + echo printErrorPage("Temporarily unavailable."); + return; +} + +/* + * Output + */ +$addToHead = ' + '; + +echo printPublicHead($settings, $_GET['id'], $settings['pageTitle'] . " - " . $contents['object_name'], $settings['logo'], $addToHead); +echo printPublicHeader($settings['pageTitle']); +echo printStaticPagePart("banner", "header"); // Print aside (if need be) + +echo generatePublicNav($pages); + +echo ' +
+'; + +// Print main content +echo ' +
'; + +echo drawObjectDetails($contents, $settings); + +echo ' +
+'; + +echo printStaticPagePart("aside", "aside"); // Print aside (if need be) + +echo ' +
'; + +echo printStaticPagePart("footer", "footer"); // Print footer (if need be) + +echo printPublicEnd(); + +?> diff --git a/search.php b/search.php new file mode 100644 index 0000000..88907d7 --- /dev/null +++ b/search.php @@ -0,0 +1,73 @@ + + */ + +// Include functions and settings. + +require_once __DIR__ . "/inc/functions.php"; +require_once __DIR__ . "/inc/search.php"; + +// Ensure working environment for frontend. + +ensureEnvironment(); +$pages = loadPublicPages(); // Load overview of pages. + +/* + * Load data. + */ + +/* + * Output + */ + +echo printPublicHead($settings, "search", $settings['pageTitle'], $settings['logo']); +echo printPublicHeader($settings['pageTitle']); +echo printStaticPagePart("banner", "header"); // Print aside (if need be) + +echo generatePublicNav($pages); + +echo ' +
+'; + +// Print main content +echo ' +
'; + +if (!isset($_GET['q'])) { + + echo ' +

+
+ +
+ '; + +} +else { + + echo '

"' . $_GET['q'] . '"

'; + $resultsInPages = searchInPages($_GET['q']); + print_r($resultsInPages); + +} + +echo ' +
+'; + +echo printStaticPagePart("aside", "aside"); // Print aside (if need be) + +echo ' +
'; + +echo printStaticPagePart("footer", "footer"); // Print footer (if need be) + +echo printPublicEnd(); + +?> diff --git a/themes/default/default.css b/themes/default/default.css index f7ed95d..1c5ab52 100644 --- a/themes/default/default.css +++ b/themes/default/default.css @@ -2,6 +2,12 @@ * Default theme for the backend of md:cms. */ +/**************************** + * Imports + */ + +@import 'mdEmbeds.css'; + /**************************** * Load fonts */ @@ -82,7 +88,7 @@ aside > * { display: block; padding: 1em; border: 1px solid #D6D6D6; } body > header:nth-child(2) { margin: 0; padding: 0; } body > header:nth-child(2) p { margin: 0; padding: 0; } -body > header:nth-child(2) img { width: 100vw; height: 20vh; object-fit: cover; margin-bottom: -.4em; } +body > header:nth-child(2) img { width: 100%; height: 20vh; object-fit: cover; margin-bottom: -.4em; } /************ * Main wrapper @@ -104,7 +110,7 @@ body > nav { display: block; background: #212121; color: #EEE; border: 1px solid #D6D6D6; border-width: 0; padding: 0 5vw; } body > nav ul { list-style: none; margin: 0; padding: 0 0; } body > nav li { position: relative; } -body > nav > ul { display: block; border-bottom: 1px solid #D6D6D6; } +body > nav > ul { display: block; } body > nav > ul > li { display: inline-block; } body > nav > ul > li ul { position: absolute; left: 0; top: 100%; background: #424242; color: #EEE; z-index: 2; animation: fade-in .4s; } diff --git a/themes/default/mdEmbeds.css b/themes/default/mdEmbeds.css new file mode 100644 index 0000000..664979e --- /dev/null +++ b/themes/default/mdEmbeds.css @@ -0,0 +1,54 @@ +/** + * Default theme for the backend of md:cms: + * Styles for data embedded from museum-digital. + */ + +/********** + * Simple tiles for displaying an object. + */ + +div.objTile { display: inline-block; width: 200px; border: 1px solid #D6D6D6; text-align: center; + transition: background .4s, box-shadow .4s; } +div.objTile > div { position: relative; padding: .5em .5em 1.5em .5em; } +div.objTile h4 { margin: 0; padding: 0 .5em; } +div.objTile img { max-width: 198px; } +div.objTile:hover { background: #F2F2F2; box-shadow: 1px 1px 3px #D6D6D6, -1px -1px 3px #D6D6D6; } + +div.objTile > div > div { color: #888; font-size: .8em; } +div.objTile > div > div > a { position: absolute; top: 100%; left: -1px; + display: none; max-width: 50%; padding: .3em; + background: #FFF; text-align: left; border: 1px solid #D6D6D6; + transition: background .4s, color .4s; } + +div.objTile:hover > div > div > a { display: block; animation: fade-in .4s; } +div.objTile > div > div > a:last-child { left: initial; right: -1px; text-align: right; } +div.objTile > div > div > a:hover { background: #EEE; color: #212121; } + +/********** + * Object details. + */ + +div.objDetails { } +div.objDetails dt { font-weight: bold; margin-top: .5em; } +div.objDetails dd { margin-left: 0; padding-left: 0; } +div.objDetails .objMainImg { max-width: 200px; margin: .5em 1em 1em 0; float: left; + border: 1px solid #D6D6D6; transition: max-width .4s; } +div.objDetails .objMainImg:hover { max-width: 100vw; max-height: 80vh; float: initial; } + +.metadataLine { margin-top: 2.5em; color: #666; border-top: 1px solid #D6D6D6; } +.metadataLine > span { margin-right: 2em; font-size: .95em; font-style: italic; } + +/***** + * Events on object detail page. + */ + +.events { display: block; max-width: 100%; width: auto; margin: 0 0 1em 0; } +.events > * { display: inline-block; margin: 0 1em 1em 0; padding: 0 1em; border: 1px solid #D6D6D6; + vertical-align: top; transition: box-shadow .4s; } +.events h5 { margin: 0; padding: .5em 1em; font-size: .9em; background: #F2F2F2; color: #646464; } +.events tr * { padding: .1em .5em; vertical-align: middle; font-size: .95em; } +.events th { font-weight: normal; color: #646464; } +.events table { margin: .5em 0; } + +.events > *:hover { box-shadow: 1px 1px 3px #D6D6D6, -1px -1px 3px #D6D6D6; } +.events tr:hover { background: initial; } diff --git a/themes/imports.css b/themes/imports.css new file mode 100644 index 0000000..81ef84f --- /dev/null +++ b/themes/imports.css @@ -0,0 +1 @@ +@import 'mdCalendar.css'; diff --git a/themes/mdCalendar.css b/themes/mdCalendar.css new file mode 100644 index 0000000..fc1ba5c --- /dev/null +++ b/themes/mdCalendar.css @@ -0,0 +1,151 @@ +/** + * CSS file for mdCalendar. + */ + +.mdCalendar { display: block; } +.mdCTable { width: 100%; table-layout: fixed; font-size: .9em; } + +/** + * Table + */ + +.mdCTable thead tr { border: 1px solid #D6D6D6; } + +.mdCTable th { color: #646464; font-weight: normal; text-align: center; } +.mdCTable td { padding: 0; height: 10em; background: #FFF; border: 1px solid #D6D6D6; vertical-align: top; } +.mdCTable *:hover { z-index: 2; } + +.mdCTable time { display: block; color: #646464; border-bottom: 1px dashed #D6D6D6; font-weight: bold; + cursor: pointer; transition: background .4s, color .4s; } +.mdCTable time:hover { background: #03A9F4; color: #FFF; } +.mdCTable td > * { display: block; padding: .3em .5em; line-height: 1.2em; } + +.mdCTable td > a { position: relative; margin: .2em; padding-left: .7em; + background: rgba(240, 240, 240, .2); color: #424242; border: 1px solid #D6D6D6; + cursor: pointer; transition: background .4s, color .4s; } +.mdCTable td > a:hover { background: #333; color: #FFF; z-index: 2; } + +.mdCTable td > a:before { content: " "; position: absolute; left: 0; top: 0; width: .5em; height: 100%; } + +.mdCTable .mdCToday { background: #FAFAFA; } +.mdCTable .mdCOtherMonth { color: #646464; background: #EEE; } + +/** + * Table head. + */ + +.mdCTitleLine { display: table; width: 100%; margin: .5em 0; padding: 0; } +.mdCTitleLine > * { display: table-cell; vertical-align: middle; } + +.mdCTitleLine > h3 { font-weight: normal; color: #424242; } + +.mdCNav { width: 200px; color: #424242; text-align: right; } + +.mdCNav > * { transition: background .4s, color .4s; font-size: 1em; padding: .5em; } +.mdCNav > *:hover { background: #F2F2F2; color: #000; } +.mdCNavPrev:before { content: "<"; } +.mdCNavNext:before { content: ">"; } + +/** + * Alternative display of events in case there are too many for a day. + */ + +.mdCTable td.mdCManyElements > a { display: inline-block; height: 1.1em; width: 1.1em; } +.mdCTable td.mdCManyElements > a:before { width: 100%; } +.mdCTable td.mdCManyElements > a span { display: none; } + +@media screen and (max-width: 75em) { + .mdCTable td > a { display: inline-block; height: 1.1em; width: 1.1em; } + .mdCTable td > a span { display: none; } + .mdCTable td > a:before { width: 100%; } +} + +/** + * Tooltips + */ +.mdCToolTip { position: absolute; top: calc(100% + 22px); left: 50%; transform: translate(-50%, 0); + width: 300px; padding: 1em 0; + background: #FFF; border: 1px solid #646464; + z-index: 1000; animation: fade-in-tooltip .4s; } + +.mdCToolTip:before, +.mdCToolTip:after { content: " "; position: absolute; bottom: calc(100% - 1px); left: 50%; transform: translate(-50%, 0); + display: block; width: 0; height: 0; + border-left: 21px solid transparent; border-right: 21px solid transparent; + border-bottom: 21px solid #FFF; z-index: 1002; } +.mdCToolTip:after { border-left: 22px solid transparent; border-right: 22px solid transparent; + border-bottom: 22px solid #646464; z-index: 1001; } +.mdCToolTip tr * { height: auto; padding: .5em; vertical-align: top; text-align: left; color: #424242; border: 0; } +.mdCToolTip tr:first-child * { padding-top: 1em; } +.mdCToolTip tr:last-child * { padding-bottom: 1em; } + +/** + * Overlay + */ + +#mdCOverlay { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); + max-height: 80vh; width: 600px; max-width: 80vw; background: #FFF; + box-shadow: 2px 2px 4px #D6D6D6, -2px -2px 4px #D6D6D6; + overflow-y: auto; z-index: 1003; } + +.mdCOverlayTitleBar { display: table; width: 100%; } +.mdCOverlayTitleBar > * { display: table-cell; padding: .5rem 1.5rem; background: #EEE; color: #000; } + +.mdCOverlayClose { width: 5em; text-align: right; } +.mdCOverlayClose:before { content: "x"; display: inline-block; padding: .1em .7em .3em .7em; + background: #03A9F4; color: #FFF; text-align: center; + border-radius: 100%; cursor: pointer; transition: background .4s; color .4s; } +.mdCOverlayClose:hover:before { background: #FFF; color: #03A9F4; } + +#mdCOverlay ul { list-style: none; margin: 1em 1.5rem; padding: 0; } +#mdCOverlay li { margin: 0; padding: 1em 0; border-bottom: 1px solid #D6D6D6; } +#mdCOverlay li > a:first-child { color: #424242; font-weight: bold; } + +/** + * @var {string[]} colorScheme Material design colors as taken from https://material.io/design/color/the-color-system.html#tools-for-picking-colors + */ + +.color0:before { background: #F44336; } +.color1:before { background: #E53935; } +.color2:before { background: #D32F2F; } +.color3:before { background: #C62828; } +.color4:before { background: #B71C1C; } + +.color5:before { background: #3F51B5; } +.color6:before { background: #3949AB; } +.color7:before { background: #303F9F; } +.color8:before { background: #283593; } +.color9:before { background: #1A237E; } + +.color10:before { background: #2196F3; } +.color11:before { background: #1E88E5; } +.color12:before { background: #1976D2; } +.color13:before { background: #1565C0; } +.color14:before { background: #0D47A1; } + +.color15:before { background: #03A9F4; } +.color16:before { background: #039BE5; } +.color17:before { background: #0288D1; } +.color18:before { background: #0277BD; } +.color19:before { background: #01579B; } + +.color20:before { background: #00BCD4; } +.color21:before { background: #00ACC1; } +.color22:before { background: #0097A7; } +.color23:before { background: #00838F; } +.color24:before { background: #006064; } + +.color25:before { background: #4CAF50; } +.color26:before { background: #43A047; } +.color27:before { background: #388E3C; } +.color28:before { background: #2E7D32; } +.color29:before { background: #1B5E20; } + +/** + * Animations + */ + +@keyframes fade-in-tooltip { + from { top: calc(100% + 19px); opacity: 0.1; } + to { top: calc(100% + 22px); opacity: 1.0; } +}