From 81feec677d6c8a96b79c7b2a534e842035302ce8 Mon Sep 17 00:00:00 2001 From: Frode Petterson Date: Wed, 26 Mar 2014 08:43:29 +0100 Subject: [PATCH] Synced library with module 6.x-1.x. --- fonts/h5p.eot | Bin 0 -> 2852 bytes fonts/h5p.svg | 12 + fonts/h5p.ttf | Bin 0 -> 2672 bytes fonts/h5p.woff | Bin 0 -> 1956 bytes h5p-development.class.php | 177 ++++++++ h5p.classes.php | 583 ++++++++++++++++++++------ js/h5p-embed.js | 230 +++++++++++ js/h5p-library-details.js | 300 ++++++++++++++ js/h5p-library-list.js | 73 ++++ js/h5p-utils.js | 69 ++++ js/h5p.js | 845 ++++++++++++++++++++++++++++++-------- styles/h5p-admin.css | 227 ++++++++++ styles/h5p.css | 219 +++++++++- 13 files changed, 2430 insertions(+), 305 deletions(-) create mode 100644 fonts/h5p.eot create mode 100644 fonts/h5p.svg create mode 100644 fonts/h5p.ttf create mode 100644 fonts/h5p.woff create mode 100644 h5p-development.class.php create mode 100644 js/h5p-embed.js create mode 100644 js/h5p-library-details.js create mode 100644 js/h5p-library-list.js create mode 100644 js/h5p-utils.js create mode 100644 styles/h5p-admin.css diff --git a/fonts/h5p.eot b/fonts/h5p.eot new file mode 100644 index 0000000000000000000000000000000000000000..d74adc1500a528b307c8c57422f3503e3a52073c GIT binary patch literal 2852 zcma)8O^h5z6|Vo^?w;Rgza5y zCyH|+2O^z1LN>=?U3)giylBqm7bxXpo#b{irXAoLv~31L5He}Lrex~Kmj6KaFrLye{nF%|h0 zLhda2?L++dZ36CoGQ#j6ysAWo8FS1VB9#+CzJ%TpgE0o#;Lh^!--m~g!RF4zil&Eu z@4dIOs0Q$&ghH!Pq0}-I$?VGk^#7J|a*`bK57_s~9LfF>47d1`w7sF{d^V>i=w#ey zojz~p3DXG=&t!Kpp0nAU@9u1|5hPDiNFHd(Ayh}RjrPXwMh4~fWQ&jLXV&MzHW+U3 zcTL{?e0uZ!M0n?gu-3@rNVjcmVg-|03ZFC#*IM(RKHD4|=@=VZDNJM+!bOSgZNpjE58E?dy6HEvgQvqO` z7v01*n@wxd*h|Ver#}gknO5gWBk2__{Tdg7KF_$|LTf9e0r(e$@hbVtty>4LzxgH; zN(pWZymUTN4TVW;((++i2;yCNGlT(4WzNBGTosRHGbkjqv29D%%LJLQuh$ zb2bSn;GJnPE)clRPB9&WpY4pdi{s$&6sj0jHbYhFC+4(IJAIlfeWIoks*7nc?S$kM zIXJ*4yq(Ztxk|_upJAeaS{WC4k#{8K8+TNp@}OVl!(yZE^Noa6h5?3d(SQL1fWzonbL7M$pKCw)?OG zAf}_~&YU6+)8alzh(=V1VvFw#ItO=+PrQb1w!XREge>c| zBSfRJmsk-d5mHGZeTWv9kupiE6~h9t!JzMUx<2Xlx(U2t^tE7#DQWkqt*5#WpeYgJ z!Hf{f8I5pgg~e$K0GJ?BK5$U2VneapH4@W0Rvv4GVVzhT0O>Q>G?W%TYA9v2One9- zG?a1xc3M*87%P@xn-I$PI!7X#W(|iZIuRDZ5jN~=f;wF->O!F6W*V}D z-eZ9QTyTZ>32AL*DGo#;aL^xkjLm~$fHKBLyNK~X;t-3zZw<~H8kzK z)96{eHXP;Jhe|e5v&t=|QGTk?XxeNr=%-P)ont4j;)^2EmMM+c!utL>_OH7ie(h}I znNNuq)k*iK)4%v#Anx@z!9E5XPSCo+G@Qc(8*ga*gNIJUNi&6D`QRY@Gq z)+!qdXLpz?(UzxzPESj yg~ip+xJy2Ik9;f$`8sZvSE}3P0yohMI2H%EiQd5bB!adFr^D?ugdg7j&-NcfY7JWe literal 0 HcmV?d00001 diff --git a/fonts/h5p.svg b/fonts/h5p.svg new file mode 100644 index 0000000..3364cd8 --- /dev/null +++ b/fonts/h5p.svg @@ -0,0 +1,12 @@ + + + +Generated by IcoMoon + + + + + + + + \ No newline at end of file diff --git a/fonts/h5p.ttf b/fonts/h5p.ttf new file mode 100644 index 0000000000000000000000000000000000000000..904d19b9974e7fdb8725ad0616fc30df8b84c194 GIT binary patch literal 2672 zcmaJ@O^6)F6|Vo^?w;OiKN{~ zvax)L4>8HXj)Mpe3HYQ-PQDsKAi5Y|67qBKDYt+y1RGs^3fc8n-6Kl};p(2M_g=mC zy;rYZR}GdKAR9U-59|0DR`<^2mw z{vNpt{to!$%JTIC;^{r`{{TO{y1a7n{7;`f0Uk2?;=%s)8;BFlJs0K)7li(XNJ7}} z*<$QBR?Lco{`0NbMMB{t7DhrG|Pu}{g% z^fP3-8dYjspw~!GQK_nf^eQA3Y_(w1um!w3D<>rq*WE2=6Y%rh$xe9+Jl;~3quOSq zD*f1k4rq5k3#CstR6=z*D`(xXIb{J3@ColEbX2W3WSh_7D4^EHWl{$Rsh6oJlkDRAivc8Fb7oS`22)9JL&7_)ghcecpeR+@T>Ymsga zH&W@GR}{s=(v6Lclrc_yw^zoTDQmT~?KC&QVKyy_SUBUvVP@jQE1|fwJ~9$TBtvQ9 zby`zK6$2?4m0an#6)Ru1JJv+8*Gw@1Fe*@9T1lB_nLu##Bc+{IFr~~zxr2oAI4dI% zs_Y(*7?+4k3(caUlUrt$MSSoYz?hMS=`@RF;J{Ox<*DNo$bzEA8IzHZgm#fv%xKHC zF%mP3wKg|4TZ{sRaij31z&IO@h8d^acxVT>ta_}<@ChL%1c5IGmcWd^(LWwZW0ed5 zF1Gm2pmTW7_{3}IW}7EAbI7uOCqg!Adx;gniI7SP=|i@-jFd^*?HCrw4JLiB+w)1U z-%Ah;qi+ODxTL)&wx8%hfTl#q2Qxw_XEf5G6&9x{0APYl`M^Q7&JD$B*Gf$LWOcF~ zrgdU%0Hjah&`?_VsHK$AGVvjW&``<&*l9_z$5^op$AnP6-#r%DG;29z(TT7Kj|(IdCc-vynpY>rS-o+nOGH+uI_zU~ zJnW-JtwW2lC~4RAPQM=(4>dIHywez2yfGRV+J{OuQnNZNrcr*X)yi!)91hZ`*C}w4 z*ZD;eY0H#GZejh^{rSIpAN}B5>!~k@7xhi|_vilbmq6Sfaf5vdHr$|1gK4;j2@c-S z_=gYOlC^}WW@!3g2F)}0y;DmZ?&Sgb7rzQF+@*?szJ|K*p;lXW3He}+Pf5A9z+VWG zkPB-$hy2@XT7dr9nwFr;HLdWk>FG7?$Vu|caNi3HNx*FA2D70X%!Y0-8@j=4Xa(D6 z*R~DjitLdCa*o^}`*<#sm&lS_!`*)o{dMvRWLL?MoFdy~hdfeuwRdpt#{T~0mzLKa wtw7EK>=Jnu-~sxPAmoS8xmr72B9x1`7KaGsCi*i-+CG8~;f3@g`mb$Y0?8Em1ONa4 literal 0 HcmV?d00001 diff --git a/fonts/h5p.woff b/fonts/h5p.woff new file mode 100644 index 0000000000000000000000000000000000000000..de26840f77b90c25fffa49d6e5392867805ddef4 GIT binary patch literal 1956 zcmaJ>U2GIp6h5;*?X=*6+C>_WY=jU(`~eI<8XpYC3PI8;4FrO;+futo|LE?v-A=oI zvp+L)XJ)6nJGk(+K0w-_niBk zbMO7my=V5`-rAa)+WPuhfbtKa5>U#|YIZGoqo&4&@rMA$6-|#lef{g&6EE!pFkuWD zHMPWz@#c1W7l64Q{kJu({_Wn*R=ZP+jiXPObfcrieja1f=pWFOa(kK`7{u65=Y&5k-hwW!qu$fB@#66vU7b!Bo+*TL zY(+KV2$)PjQPg_J{;mdBXJ=cJy&EV4#$R39YFOH0T-t2fRtXioD}+4m*O!xH%gN<> z^RvjpZP2Xrq-HD782@+*%8~Dx>Mh&0?^?yW3cB~W_qzA(ald)sz(LI%0x(cKn4nBc zY=UyE{uA)Pc>3?8MgKiYT9$V&J+$_WUsM|iUD!#;D}}>k@Evnn85v5;X(>h0#50!+(deYd_r#cr`=cR-35y^ zz%o%6q1y@hd5)MTNH&^a6Tw)3_(X3c5M=4Xmrd3l;_(Ih>Ft@%@+~hAQaD(sM%IIc zNq?@I43&@ZTU04 zWU=~1mas%1yu^z~?$+Era#s+e=sUyC@TE|9n5 z$XPx{6iyN5nOg(%j3~s2EY2im)vF0v6a|UT^s7CoK6>iZ#Hq=XB8NGmJ??IATiDC{ z1RtRv1;4RgPL6RIX4p%ZK<^bf6H=K}KgSF(3K=2_pXE{kWiU3#d4qvSI24J7=}1WH z1_qt{V5aSA_s3n=JQK_~Gsz|+sxV9vVj@12R1-;69UZ+I&GgWCE>7a;r89@DX+BNT zJRUeGu%Wj8zK$SI1fKALi_YE)QH~Hn;5{ESGZ)fPDI!NCIvirdEZ6C6AL!@VY=RwSrsdQ)(L|yJLPVapC!IOh1Sj)~giV$eDJCj{9LWxhFqy&Z z;K)FR!IJVRiO$N&L_8zPST3n?q4Ab|k&`(^9Fo*jObufm+|)wjr{@+L<)}ntLXSx! z8F>s5p6kr*)3?uj5tXs>_$B1HB4Q?@M34dXx?`?&&OxXAsetO21eq75Tr4{k$jSO% z3sdFd{>O3L9r-q?CA?WkW0*WG;Iz z3sd}JeE!PKv1=0_&E{gUm>82qN!#RXV8%Ziyyipo)1p=wp0+F}tCo{ivL0U#BVK<6 z%yg|jvkS+k2kjK>0;V6L=| zf=xI@Kd}KU&|Jbsj6Yw(Cd8LY*o=5n375e-I9$S&unVHvSxprf(KvOibLv><)UnQ~ zW1UmSWw`cuX|2xbfc?+~4d8-KY;Dj4cIbwa(1O+p7ckZVHrNXx$u;UVN#HG<(@{)n$E~!$c}&^_9<)cXXq~ueZF?PB<+Xpx|Hwa9oo7@4 literal 0 HcmV?d00001 diff --git a/h5p-development.class.php b/h5p-development.class.php new file mode 100644 index 0000000..89b9c5f --- /dev/null +++ b/h5p-development.class.php @@ -0,0 +1,177 @@ +h5pF = $H5PFramework; + $this->language = $language; + if ($libraries !== NULL) { + $this->libraries = $libraries; + } + else { + $this->findLibraries($filesPath . '/development'); + } + } + + /** + * Get contents of file. + * + * @param string File path. + * @return mixed String on success or NULL on failure. + */ + private function getFileContents($file) { + if (file_exists($file) === FALSE) { + return NULL; + } + + $contents = file_get_contents($file); + if ($contents === FALSE) { + return NULL; + } + + return $contents; + } + + /** + * Scans development directory and find all libraries. + * + * @param string $path Libraries development folder + */ + private function findLibraries($path) { + $this->libraries = array(); + + if (is_dir($path) === FALSE) { + return; + } + + $contents = scandir($path); + + for ($i = 0, $s = count($contents); $i < $s; $i++) { + if ($contents[$i]{0} === '.') { + continue; // Skip hidden stuff. + } + + $libraryPath = $path . '/' . $contents[$i]; + $libraryJSON = $this->getFileContents($libraryPath . '/library.json'); + if ($libraryJSON === NULL) { + continue; // No JSON file, skip. + } + + $library = json_decode($libraryJSON, TRUE); + if ($library === FALSE) { + continue; // Invalid JSON. + } + + // TODO: Validate props? Not really needed, is it? this is a dev site. + + // Save/update library. + $library['libraryId'] = $this->h5pF->getLibraryId($library['machineName'], $library['majorVersion'], $library['minorVersion']); + $this->h5pF->saveLibraryData($library, $library['libraryId'] === FALSE); + + $library['path'] = $libraryPath; + $this->libraries[H5PDevelopment::libraryToString($library['machineName'], $library['majorVersion'], $library['minorVersion'])] = $library; + } + + // TODO: Should we remove libraries without files? Not really needed, but must be cleaned up some time, right? + + // Go trough libraries and insert dependencies. Missing deps. will just be ignored and not available. (I guess?!) + foreach ($this->libraries as $library) { + $this->h5pF->deleteLibraryDependencies($library['libraryId']); + // This isn't optimal, but without it we would get duplicate warnings. + $types = array('preloaded', 'dynamic', 'editor'); + foreach ($types as $type) { + if (isset($library[$type . 'Dependencies'])) { + $this->h5pF->saveLibraryDependencies($library['libraryId'], $library[$type . 'Dependencies'], $type); + } + } + } + // TODO: Deps must be inserted into h5p_nodes_libraries as well... ? But only if they are used?! + } + + /** + * @return array Libraris in development folder. + */ + public function getLibraries() { + return $this->libraries; + } + + /** + * Get library + * + * @param string $name of the library. + * @param int $majorVersion of the library. + * @param int $minorVersion of the library. + * @return array library. + */ + public function getLibrary($name, $majorVersion, $minorVersion) { + $library = H5PDevelopment::libraryToString($name, $majorVersion, $minorVersion); + return isset($this->libraries[$library]) === TRUE ? $this->libraries[$library] : NULL; + } + + /** + * Get semantics for the given library. + * + * @param string $name of the library. + * @param int $majorVersion of the library. + * @param int $minorVersion of the library. + * @return string Semantics + */ + public function getSemantics($name, $majorVersion, $minorVersion) { + $library = H5PDevelopment::libraryToString($name, $majorVersion, $minorVersion); + + if (isset($this->libraries[$library]) === FALSE) { + return NULL; + } + + return $this->getFileContents($this->libraries[$library]['path'] . '/semantics.json'); + } + + /** + * Get translations for the given library. + * + * @param string $name of the library. + * @param int $majorVersion of the library. + * @param int $minorVersion of the library. + * @return string Translation + */ + public function getLanguage($name, $majorVersion, $minorVersion) { + $library = H5PDevelopment::libraryToString($name, $majorVersion, $minorVersion); + + if (isset($this->libraries[$library]) === FALSE) { + return NULL; + } + + return $this->getFileContents($this->libraries[$library]['path'] . '/language/' . $this->language . '.json'); + } + + /** + * Writes library as string on the form "name majorVersion.minorVersion" + * + * @param string $name Machine readable library name + * @param integer $majorVersion + * @param integer $majorVersion + * @return string Library identifier. + */ + public static function libraryToString($name, $majorVersion, $minorVersion) { + return $name . ' ' . $majorVersion . '.' . $minorVersion; + } +} + diff --git a/h5p.classes.php b/h5p.classes.php index 85079c5..c3ef213 100644 --- a/h5p.classes.php +++ b/h5p.classes.php @@ -3,6 +3,7 @@ * Interface defining functions the h5p library needs the framework to implement */ interface H5PFrameworkInterface { + /** * Show the user an error message * @@ -79,7 +80,7 @@ interface H5PFrameworkInterface { * @param string $defaultLibraryWhitelist */ public function getWhitelist($isLibrary, $defaultContentWhitelist, $defaultLibraryWhitelist); - + /** * Is the library a patched version of an existing library? * @@ -187,6 +188,15 @@ interface H5PFrameworkInterface { */ public function saveLibraryUsage($contentId, $librariesInUse); + /** + * Get number of content/nodes using a library, and the number of + * dependencies to other libraries + * + * @param int $library_id + * @return array The array contains two elements, keyed by 'content' and 'libraries'. + * Each element contains a number + */ + public function getLibraryUsage($libraryId); /** * Loads a library @@ -201,16 +211,24 @@ interface H5PFrameworkInterface { public function loadLibrary($machineName, $majorVersion, $minorVersion); /** - * Loads and decodes library semantics. + * Loads library semantics. * - * @param string $machineName - * @param int $majorVersion - * @param int $minorVersion - * @return array|FALSE - * Array representing the library with dependency descriptions - * FALSE if the library doesn't exist + * @param string $name library identifier. + * @param int $majorVersion library identifier. + * @param int $minorVersion library identifier. + * @return string semantics. */ - public function getLibrarySemantics($machineName, $majorVersion, $minorVersion); + public function loadLibrarySemantics($name, $majorVersion, $minorVersion); + + /** + * Makes it possible to alter the semantics, adding custom fields, etc. + * + * @param array $semantics + * @param string $name library identifier. + * @param int $majorVersion library identifier. + * @param int $minorVersion library identifier. + */ + public function alterLibrarySemantics(&$semantics, $name, $majorVersion, $minorVersion); /** * Delete all dependencies belonging to given library @@ -219,6 +237,13 @@ interface H5PFrameworkInterface { * Library Id */ public function deleteLibraryDependencies($libraryId); + + /** + * Delete a library from database and file system + * + * @param int $libraryId Library Id + */ + public function deleteLibrary($libraryId); /** * Get all the data we need to export H5P @@ -246,6 +271,22 @@ interface H5PFrameworkInterface { * Check if export is enabled. */ public function isExportEnabled(); + + /** + * Load content. + * + * @return object Content, null if not found. + */ + public function loadContent($id); + + /** + * Load dependencies for the given content of the given type. + * + * @param int $id content. + * @param int $type dependency. + * @return array + */ + public function loadContentDependencies($id, $type = NULL); } /** @@ -349,25 +390,25 @@ class H5PValidator { * @return boolean * TRUE if the .h5p file is valid */ - public function isValidPackage() { + public function isValidPackage($skipContent = FALSE) { // Create a temporary dir to extract package in. $tmpDir = $this->h5pF->getUploadedH5pFolderPath(); - $tmp_path = $this->h5pF->getUploadedH5pPath(); + $tmpPath = $this->h5pF->getUploadedH5pPath(); $valid = TRUE; // Extract and then remove the package file. $zip = new ZipArchive; - if ($zip->open($tmp_path) === true) { + if ($zip->open($tmpPath) === true) { $zip->extractTo($tmpDir); $zip->close(); } else { $this->h5pF->setErrorMessage($this->h5pF->t('The file you uploaded is not a valid HTML5 Package.')); - $this->h5pC->delTree($tmpDir); + H5PCore::deleteFileTree($tmpDir); return; } - unlink($tmp_path); + unlink($tmpPath); // Process content and libraries $libraries = array(); @@ -382,6 +423,10 @@ class H5PValidator { $filePath = $tmpDir . DIRECTORY_SEPARATOR . $file; // Check for h5p.json file. if (strtolower($file) == 'h5p.json') { + if ($skipContent === TRUE) { + continue; + } + $mainH5pData = $this->getJsonData($filePath); if ($mainH5pData === FALSE) { $valid = FALSE; @@ -404,6 +449,10 @@ class H5PValidator { } // Content directory holds content. elseif ($file == 'content') { + // We do a separate skipContent check to avoid having the content folder being treated as a library + if ($skipContent) { + continue; + } if (!is_dir($filePath)) { $this->h5pF->setErrorMessage($this->h5pF->t('Invalid content folder')); $valid = FALSE; @@ -421,6 +470,7 @@ class H5PValidator { } if (!$this->h5pCV->validateContentFiles($filePath)) { + // validateContentfiles prints error messages itself $valid = FALSE; continue; } @@ -443,20 +493,25 @@ class H5PValidator { } } } - if (!$contentExists) { - $this->h5pF->setErrorMessage($this->h5pF->t('A valid content folder is missing')); - $valid = FALSE; - } - if (!$mainH5pExists) { - $this->h5pF->setErrorMessage($this->h5pF->t('A valid main h5p.json file is missing')); - $valid = FALSE; + if ($skipContent === FALSE) { + if (!$contentExists) { + $this->h5pF->setErrorMessage($this->h5pF->t('A valid content folder is missing')); + $valid = FALSE; + } + if (!$mainH5pExists) { + $this->h5pF->setErrorMessage($this->h5pF->t('A valid main h5p.json file is missing')); + $valid = FALSE; + } } if ($valid) { $this->h5pC->librariesJsonData = $libraries; - $this->h5pC->mainJsonData = $mainH5pData; - $this->h5pC->contentJsonData = $contentJsonData; - - $libraries['mainH5pData'] = $mainH5pData; // Check for the dependencies in h5p.json as well as in the libraries + + if ($skipContent === FALSE) { + $this->h5pC->mainJsonData = $mainH5pData; + $this->h5pC->contentJsonData = $contentJsonData; + $libraries['mainH5pData'] = $mainH5pData; // Check for the dependencies in h5p.json as well as in the libraries + } + $missingLibraries = $this->getMissingLibraries($libraries); foreach ($missingLibraries as $missing) { if ($this->h5pF->getLibraryId($missing['machineName'], $missing['majorVersion'], $missing['minorVersion'])) { @@ -465,7 +520,7 @@ class H5PValidator { } if (!empty($missingLibraries)) { foreach ($missingLibraries as $library) { - $this->h5pF->setErrorMessage($this->h5pF->t('Missing required library @library', array('@library' => $this->h5pC->libraryToString($library)))); + $this->h5pF->setErrorMessage($this->h5pF->t('Missing required library @library', array('@library' => H5PCore::libraryToString($library)))); } if (!$this->h5pF->mayUpdateLibraries()) { $this->h5pF->setInfoMessage($this->h5pF->t("Note that the libraries may exist in the file you uploaded, but you're not allowed to upload new libraries. Contact the site administrator about this.")); @@ -474,7 +529,7 @@ class H5PValidator { $valid = empty($missingLibraries) && $valid; } if (!$valid) { - $this->h5pC->delTree($tmpDir); + H5PCore::deleteFileTree($tmpDir); } return $valid; } @@ -900,7 +955,7 @@ class H5PStorage { * TRUE if one or more libraries were updated * FALSE otherwise */ - public function savePackage($contentId, $contentMainId = NULL) { + public function savePackage($contentId = NULL, $contentMainId = NULL, $skipContent = FALSE) { // Save the libraries we processed during validation $library_saved = FALSE; $mayUpdateLibraries = $this->h5pF->mayUpdateLibraries(); @@ -929,8 +984,8 @@ class H5PStorage { $this->h5pF->saveLibraryData($library, $new); $current_path = $this->h5pF->getUploadedH5pFolderPath() . DIRECTORY_SEPARATOR . $key; - $destination_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'libraries' . DIRECTORY_SEPARATOR . $this->h5pC->libraryToString($library, TRUE); - $this->h5pC->delTree($destination_path); + $destination_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'libraries' . DIRECTORY_SEPARATOR . H5PCore::libraryToString($library, TRUE); + H5PCore::deleteFileTree($destination_path); rename($current_path, $destination_path); $library_saved = TRUE; @@ -950,25 +1005,29 @@ class H5PStorage { } } } - // Move the content folder - $current_path = $this->h5pF->getUploadedH5pFolderPath() . DIRECTORY_SEPARATOR . 'content'; - $destination_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId; - rename($current_path, $destination_path); + + if (!$skipContent) { + // Move the content folder + $current_path = $this->h5pF->getUploadedH5pFolderPath() . DIRECTORY_SEPARATOR . 'content'; + $destination_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId; + rename($current_path, $destination_path); - // Save what libraries is beeing used by this package/content - $librariesInUse = array(); - $this->getLibraryUsage($librariesInUse, $this->h5pC->mainJsonData); - $this->h5pF->saveLibraryUsage($contentId, $librariesInUse); - $this->h5pC->delTree($this->h5pF->getUploadedH5pFolderPath()); + // Save what libraries is beeing used by this package/content + $librariesInUse = array(); + $this->h5pC->findLibraryDependencies($librariesInUse, $this->h5pC->mainJsonData); + + $this->h5pF->saveLibraryUsage($contentId, $librariesInUse); + H5PCore::deleteFileTree($this->h5pF->getUploadedH5pFolderPath()); - // Save the data in content.json - $contentJson = file_get_contents($destination_path . DIRECTORY_SEPARATOR . 'content.json'); - $mainLibraryId = $librariesInUse[$this->h5pC->mainJsonData['mainLibrary']]['library']['libraryId']; - $this->h5pF->saveContentData($contentId, $contentJson, $this->h5pC->mainJsonData, $mainLibraryId, $contentMainId); + // Save the data in content.json + $contentJson = file_get_contents($destination_path . DIRECTORY_SEPARATOR . 'content.json'); + $mainLibraryId = $librariesInUse['preloaded-' . $this->h5pC->mainJsonData['mainLibrary']]['library']['libraryId']; + $this->h5pF->saveContentData($contentId, $contentJson, $this->h5pC->mainJsonData, $mainLibraryId, $contentMainId); + } return $library_saved; } - + /** * Delete an H5P package * @@ -976,7 +1035,7 @@ class H5PStorage { * The content id */ public function deletePackage($contentId) { - $this->h5pC->delTree($this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId); + H5PCore::deleteFileTree($this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId); $this->h5pF->deleteContentData($contentId); } @@ -1012,57 +1071,10 @@ class H5PStorage { public function copyPackage($contentId, $copyFromId, $contentMainId = NULL) { $source_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $copyFromId; $destination_path = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId; - $this->h5pC->copyTree($source_path, $destination_path); + $this->h5pC->copyFileTree($source_path, $destination_path); $this->h5pF->copyLibraryUsage($contentId, $copyFromId, $contentMainId); } - - /** - * Identify what libraries are beeing used taking all dependencies into account - * - * @param array $librariesInUse - * List of libraries in use, indexed by machineName - * @param array $jsonData - * library.json og h5p.json data holding dependency information - * @param boolean $dynamic - * Whether or not the current library is a dynamic dependency - */ - public function getLibraryUsage(&$librariesInUse, $jsonData, $dynamic = FALSE, $editor = FALSE) { - if (isset($jsonData['preloadedDependencies'])) { - foreach ($jsonData['preloadedDependencies'] as $preloadedDependency) { - $library = $this->h5pF->loadLibrary($preloadedDependency['machineName'], $preloadedDependency['majorVersion'], $preloadedDependency['minorVersion']); - $librariesInUse[$preloadedDependency['machineName']] = array( - 'library' => $library, - 'preloaded' => $dynamic ? 0 : 1, - ); - $this->getLibraryUsage($librariesInUse, $library, $dynamic, $editor); - } - } - if (isset($jsonData['dynamicDependencies'])) { - foreach ($jsonData['dynamicDependencies'] as $dynamicDependency) { - if (!isset($librariesInUse[$dynamicDependency['machineName']])) { - $library = $this->h5pF->loadLibrary($dynamicDependency['machineName'], $dynamicDependency['majorVersion'], $dynamicDependency['minorVersion']); - $librariesInUse[$dynamicDependency['machineName']] = array( - 'library' => $library, - 'preloaded' => 0, - ); - } - $this->getLibraryUsage($librariesInUse, $library, TRUE, $editor); - } - } - if (isset($jsonData['editorDependencies']) && $editor) { - foreach ($jsonData['editorDependencies'] as $editorDependency) { - if (!isset($librariesInUse[$editorDependency['machineName']])) { - $library = $this->h5pF->loadLibrary($editorDependency['machineName'], $editorDependency['majorVersion'], $editorDependency['minorVersion']); - $librariesInUse[$editorDependency['machineName']] = array( - 'library' => $library, - 'preloaded' => $dynamic ? 0 : 1, - ); - } - $this->getLibraryUsage($librariesInUse, $library, $dynamic, TRUE); - } - } - } } /** @@ -1109,7 +1121,7 @@ Class H5PExport { @mkdir($tempPath); $exportData = $this->h5pF->getExportData($contentId); // Create content folder - if ($this->h5pC->copyTree($h5pDir . 'content' . DIRECTORY_SEPARATOR . $contentId, $tempPath . DIRECTORY_SEPARATOR . 'content') === FALSE) { + if ($this->h5pC->copyFileTree($h5pDir . 'content' . DIRECTORY_SEPARATOR . $contentId, $tempPath . DIRECTORY_SEPARATOR . 'content') === FALSE) { return FALSE; } file_put_contents($tempPath . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . 'content.json', $exportData['jsonContent']); @@ -1121,7 +1133,7 @@ Class H5PExport { // Build h5p.json $h5pJson = array ( 'title' => $title, - 'language' => $exportData['language'] ? $exportData['language'] : 'und', + 'language' => $language ? $language : 'und', 'mainLibrary' => $exportData['mainLibrary'], 'embedTypes' => $embedTypes, ); @@ -1129,13 +1141,13 @@ Class H5PExport { // Copies libraries to temp dir and create mention in h5p.json foreach($exportData['libraries'] as $library) { // Set preloaded and dynamic dependencies - if ($library['preloaded']) { + if ($library['dependencyType'] == 'preloaded') { $preloadedDependencies[] = array( 'machineName' => $library['machineName'], 'majorVersion' => $library['majorVersion'], 'minorVersion' => $library['minorVersion'], ); - } else { + } elseif ($library['dependencyType'] == 'dynamic') { $dynamicDependencies[] = array( 'machineName' => $library['machineName'], 'majorVersion' => $library['majorVersion'], @@ -1145,22 +1157,28 @@ Class H5PExport { } // Add preloaded and dynamic dependencies if they exist - if ($preloadedDependencies) { $h5pJson['preloadedDependencies'] = $preloadedDependencies; } - if ($dynamicDependencies) { $h5pJson['dynamicDependencies'] = $dynamicDependencies; } + if (isset($preloadedDependencies)) { $h5pJson['preloadedDependencies'] = $preloadedDependencies; } + if (isset($dynamicDependencies)) { $h5pJson['dynamicDependencies'] = $dynamicDependencies; } // Save h5p.json $results = print_r(json_encode($h5pJson), true); file_put_contents($tempPath . DIRECTORY_SEPARATOR . 'h5p.json', $results); - // Add the editor libraries to the list of libraries - // TODO: Add support for dependencies or editor libraries - $exportData['libraries'] = $this->addEditorLibraries($exportData['libraries'], $exportData['editorLibraries']); - // Copies libraries to temp dir and create mention in h5p.json foreach($exportData['libraries'] as $library) { - $source = $h5pDir . 'libraries' . DIRECTORY_SEPARATOR . $library['machineName'] . '-' . $library['majorVersion'] . '.' . $library['minorVersion']; + $source = NULL; + if ($this->h5pC->development_mode & H5PDevelopment::MODE_LIBRARY) { + $devlib = $this->h5pC->h5pD->getLibrary($library['machineName'], $library['majorVersion'], $library['minorVersion']); + if ($devlib !== NULL) { + $source = $devlib['path']; + } + } + + if ($source === NULL) { + $source = $h5pDir . 'libraries' . DIRECTORY_SEPARATOR . $library['machineName'] . '-' . $library['majorVersion'] . '.' . $library['minorVersion']; + } $destination = $tempPath . DIRECTORY_SEPARATOR . $library['machineName']; - $this->h5pC->copyTree($source, $destination); + $this->h5pC->copyFileTree($source, $destination); } // Create new zip instance. @@ -1182,7 +1200,7 @@ Class H5PExport { } // Close zip and remove temp dir $zip->close(); - $this->h5pC->delTree($tempPath); + H5PCore::deleteFileTree($tempPath); } return str_replace(DIRECTORY_SEPARATOR, '/', $zipPath); @@ -1198,7 +1216,7 @@ Class H5PExport { $h5pDir = $this->h5pF->getH5pPath() . DIRECTORY_SEPARATOR; $zipPath = $h5pDir . 'exports' . DIRECTORY_SEPARATOR . $contentId . '.h5p'; if (file_exists($zipPath)) { - file_delete($zipPath); + unlink($zipPath); } } @@ -1226,9 +1244,10 @@ Class H5PExport { * Functions and storage shared by the other H5P classes */ class H5PCore { + public static $coreApi = array( 'majorVersion' => 1, - 'minorVersion' => 0 + 'minorVersion' => 2 ); public static $styles = array( 'styles/h5p.css', @@ -1237,23 +1256,205 @@ class H5PCore { 'js/jquery.js', 'js/h5p.js', ); + public static $adminScripts = array( + 'js/jquery.js', + 'js/h5p-utils.js', + ); public static $defaultContentWhitelist = 'json png jpg jpeg gif bmp tif tiff svg eot ttf woff otf webm mp4 ogg mp3 txt pdf rtf doc docx xls xlsx ppt pptx odt ods odp xml csv diff patch swf'; public static $defaultLibraryWhitelistExtras = 'js css'; - public $h5pF; - public $librariesJsonData; - public $contentJsonData; - public $mainJsonData; + public $librariesJsonData, $contentJsonData, $mainJsonData, $h5pF, $path, $development_mode, $h5pD; /** * Constructor for the H5PCore * * @param object $H5PFramework * The frameworks implementation of the H5PFrameworkInterface + * @param string $path H5P file storage directory. + * @param string $language code. Defaults to english. + * @param int $development_mode mode. */ - public function __construct($H5PFramework) { + public function __construct($H5PFramework, $path, $language = 'en', $development_mode = H5PDevelopment::MODE_NONE) { $this->h5pF = $H5PFramework; + + $this->h5pF = $H5PFramework; + $this->path = $path; + $this->development_mode = $development_mode; + + if ($development_mode & H5PDevelopment::MODE_LIBRARY) { + $this->h5pD = new H5PDevelopment($this->h5pF, $path, $language); + } + } + + /** + * Load content. + * + * @param int $id for content. + * @return object + */ + public function loadContent($id) { + $content = $this->h5pF->loadContent($id); + + if ($content !== NULL) { + $content['library'] = array( + 'id' => $content['libraryId'], + 'name' => $content['libraryName'], + 'majorVersion' => $content['libraryMajorVersion'], + 'minorVersion' => $content['libraryMinorVersion'], + 'embedTypes' => $content['libraryEmbedTypes'], + 'fullscreen' => $content['libraryFullscreen'], + ); + unset($content['libraryId'], $content['libraryName'], $content['libraryEmbedTypes'], $content['libraryFullscreen']); + + if ($this->development_mode & H5PDevelopment::MODE_CONTENT) { + // TODO: Remove Drupal specific stuff + $json_content_path = file_create_path(file_directory_path() . '/' . variable_get('h5p_default_path', 'h5p') . '/content/' . $id . '/content.json'); + if (file_exists($json_content_path) === TRUE) { + $json_content = file_get_contents($json_content_path); + if (json_decode($json_content, TRUE) !== FALSE) { + drupal_set_message(t('Invalid json in json content'), 'warning'); + } + $content['params'] = $json_content; + } + } + } + + return $content; + } + + /** + * Find the files required for this content to work. + * + * @param int $id for content. + * @return array + */ + public function loadContentDependencies($id, $type = NULL) { + $dependencies = $this->h5pF->loadContentDependencies($id, $type); + + if ($this->development_mode & H5PDevelopment::MODE_LIBRARY) { + $developmentLibraries = $this->h5pD->getLibraries(); + + foreach ($dependencies as $key => $dependency) { + $libraryString = H5PCore::libraryToString($dependency); + if (isset($developmentLibraries[$libraryString])) { + $dependencies[$key] = $developmentLibraries[$libraryString]; + } + } + } + + return $this->getDependenciesFiles($dependencies); + } + + /** + * Return file paths for all dependecies files. + * + * @param array $dependencies + * @return array files. + */ + public function getDependenciesFiles($dependencies) { + $files = array( + 'scripts' => array(), + 'styles' => array(), + ); + foreach ($dependencies as $dependency) { + if (isset($dependency['path']) === FALSE) { + $dependency['path'] = $this->path . '/libraries/' . H5PCore::libraryToString($dependency, TRUE); + $dependency['preloadedJs'] = explode(',', $dependency['preloadedJs']); + $dependency['preloadedCss'] = explode(',', $dependency['preloadedCss']); + } + + if (!empty($dependency['preloadedJs']) && $dependency['preloadedJs'][0] !== '') { + foreach ($dependency['preloadedJs'] as $file) { + $files['scripts'][] = $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file); + } + } + if ($dependency['dropCss'] !== '1' && !empty($dependency['preloadedCss']) && $dependency['preloadedCss'][0] !== '') { + foreach ($dependency['preloadedCss'] as $file) { + $files['styles'][] = $dependency['path'] . '/' . trim(is_array($file) ? $file['path'] : $file); + } + } + } + return $files; + } + + /** + * Load library semantics. + * + * @return string 'div' or 'iframe'. + */ + public function loadLibrarySemantics($name, $majorVersion, $minorVersion) { + if ($this->development_mode & H5PDevelopment::MODE_LIBRARY) { + // Try to load from dev lib + $semantics = $this->h5pD->getSemantics($name, $majorVersion, $minorVersion); + } + + if ($semantics === NULL) { + // Try to load from DB. + $semantics = $this->h5pF->loadLibrarySemantics($name, $majorVersion, $minorVersion); + } + + if ($semantics !== NULL) { + $semantics = json_decode($semantics); + $this->h5pF->alterLibrarySemantics($semantics, $name, $majorVersion, $minorVersion); + } + + return $semantics; + } + + /** + * Load library. + * + * @return array or null. + */ + public function loadLibrary($name, $majorVersion, $minorVersion) { + if ($this->development_mode & H5PDevelopment::MODE_LIBRARY) { + // Try to load from dev + $library = $this->h5pD->getLibrary($name, $majorVersion, $minorVersion); + } + + if ($library === NULL) { + // Try to load from DB. + $library = $this->h5pF->loadLibrary($name, $majorVersion, $minorVersion); + } + + return $library; + } + + /** + * Recusive. Goes through the dependency tree for the given library and + * adds all the dependencies to the given array in a flat format. + * + * @param array $librariesUsed Flat list of all dependencies. + * @param array $library To find all dependencies for. + * @param bool $editor Used interally to force all preloaded sub dependencies of an editor dependecy to be editor dependencies. + */ + public function findLibraryDependencies(&$dependencies, $library, $editor = FALSE) { + foreach (array('dynamic', 'preloaded', 'editor') as $type) { + $property = $type . 'Dependencies'; + if ($library[$property] === NULL) { + continue; // Skip, no such dependencies. + } + + if ($type === 'preloaded' && $editor === TRUE) { + // All preloaded dependencies of an editor library is set to editor. + $type = 'editor'; + } + + foreach ($library[$property] as $dependency) { + $dependencyKey = $type . '-' . $dependency['machineName']; + if (isset($dependencies[$dependencyKey]) === TRUE) { + continue; // Skip, already have this. + } + + $dependencyLibrary = $this->loadLibrary($dependency['machineName'], $dependency['majorVersion'], $dependency['minorVersion']); + $dependencies[$dependencyKey] = array( + 'library' => $dependencyLibrary, + 'type' => $type + ); + $this->findLibraryDependencies($dependencies, $dependencyLibrary, $type === 'editor'); + } + } } /** @@ -1287,13 +1488,13 @@ class H5PCore { * @return boolean * Indicates if the directory existed. */ - public function delTree($dir) { + public static function deleteFileTree($dir) { if (!is_dir($dir)) { return; } $files = array_diff(scandir($dir), array('.','..')); foreach ($files as $file) { - (is_dir("$dir/$file")) ? $this->delTree("$dir/$file") : unlink("$dir/$file"); + (is_dir("$dir/$file")) ? self::deleteFileTree("$dir/$file") : unlink("$dir/$file"); } return rmdir($dir); } @@ -1306,7 +1507,7 @@ class H5PCore { * @return boolean * Indicates if the directory existed. */ - public function copyTree($source, $destination) { + public function copyFileTree($source, $destination) { $dir = opendir($source); if ($dir === FALSE) { @@ -1318,7 +1519,7 @@ class H5PCore { while (false !== ($file = readdir($dir))) { if (($file != '.') && ($file != '..')) { if (is_dir($source . DIRECTORY_SEPARATOR . $file)) { - $this->copyTree($source . DIRECTORY_SEPARATOR . $file, $destination . DIRECTORY_SEPARATOR . $file); + $this->copyFileTree($source . DIRECTORY_SEPARATOR . $file, $destination . DIRECTORY_SEPARATOR . $file); } else { copy($source . DIRECTORY_SEPARATOR . $file,$destination . DIRECTORY_SEPARATOR . $file); @@ -1338,8 +1539,8 @@ class H5PCore { * @return string * On the form {machineName} {majorVersion}.{minorVersion} */ - public function libraryToString($library, $folderName = FALSE) { - return $library['machineName'] . ($folderName ? '-' : ' ') . $library['majorVersion'] . '.' . $library['minorVersion']; + public static function libraryToString($library, $folderName = FALSE) { + return (isset($library['machineName']) ? $library['machineName'] : $library['name']) . ($folderName ? '-' : ' ') . $library['majorVersion'] . '.' . $library['minorVersion']; } /** @@ -1365,6 +1566,28 @@ class H5PCore { } return FALSE; } + + /** + * Determine the correct embed type to use. + * TODO: Use constants. + * + * @return string 'div' or 'iframe'. + */ + public static function determineEmbedType($contentEmbedType, $libraryEmbedTypes) { + // Detect content embed type + $embedType = strpos(strtolower($contentEmbedType), 'div') !== FALSE ? 'div' : 'iframe'; + + if ($libraryEmbedTypes !== NULL && $libraryEmbedTypes !== '') { + // Check that embed type is available for library + $embedTypes = strtolower($libraryEmbedTypes); + if (strpos($embedTypes, $embedType) === FALSE) { + // Not available, pick default. + $embedType = strpos($embedTypes, 'div') !== FALSE ? 'div' : 'iframe'; + } + } + + return $embedType; + } } /** @@ -1469,14 +1692,14 @@ class H5PContentValidator { } // Check if string is according to optional regexp in semantics - if (isset($semantics->regexp)) { + if (!($text === '' && $semantics->optional) && isset($semantics->regexp)) { // Escaping '/' found in patterns, so that it does not break regexp fencing. $pattern = '/' . str_replace('/', '\\/', $semantics->regexp->pattern) . '/'; $pattern .= isset($semantics->regexp->modifiers) ? $semantics->regexp->modifiers : ''; if (preg_match($pattern, $text) === 0) { // Note: explicitly ignore return value FALSE, to avoid removing text // if regexp is invalid... - $this->h5pF->setErrorMessage($this->h5pF->t('Provided string is not valid according to regexp in semantics.')); + $this->h5pF->setErrorMessage($this->h5pF->t('Provided string is not valid according to regexp in semantics. (value: "%value", regexp: "%regexp")', array('%value' => $text, '%regexp' => $pattern))); $text = ''; } } @@ -1646,7 +1869,7 @@ class H5PContentValidator { // Remove attributes that should not exist, they may contain JSON escape // code. - $validkeys = array_merge(array('path', 'mime'), $typevalidkeys); + $validkeys = array_merge(array('path', 'mime', 'copyright'), $typevalidkeys); if (isset($semantics->extraAttributes)) { $validkeys = array_merge($validkeys, $semantics->extraAttributes); // TODO: Validate extraAttributes } @@ -1674,6 +1897,10 @@ class H5PContentValidator { $file->quality->label = htmlspecialchars($file->quality->label, ENT_QUOTES, 'UTF-8', FALSE); } } + + if (isset($file->copyright)) { + $this->validateGroup($file->copyright, H5PContentValidator::getCopyrightSemantics()); + } } /** @@ -1774,7 +2001,7 @@ class H5PContentValidator { } else { $libspec = $this->h5pC->libraryFromString($value->library); - $librarySemantics = $this->h5pF->getLibrarySemantics($libspec['machineName'], $libspec['majorVersion'], $libspec['minorVersion']); + $librarySemantics = $this->h5pC->loadLibrarySemantics($libspec['machineName'], $libspec['majorVersion'], $libspec['minorVersion']); $this->semanticsCache[$value->library] = $librarySemantics; } $this->validateBySemantics($value->params, $librarySemantics); @@ -2047,9 +2274,11 @@ class H5PContentValidator { return $attrarr; } +// TODO: Remove Drupal related stuff in docs. + /** * Processes an HTML attribute value and strips dangerous protocols from URLs. - * + * * @param $string * The string with the attribute value. * @param $decode @@ -2122,6 +2351,108 @@ class H5PContentValidator { return $uri; } - + + public static function getCopyrightSemantics() { + static $semantics; + + if ($semantics === NULL) { + $semantics = (object) array( + 'name' => 'copyright', + 'type' => 'group', + 'label' => 'Copyright information', + 'fields' => array( + (object) array( + 'name' => 'title', + 'type' => 'text', + 'label' => 'Title', + 'placeholder' => 'La Gioconda', + 'optional' => TRUE + ), + (object) array( + 'name' => 'author', + 'type' => 'text', + 'label' => 'Author', + 'placeholder' => 'Leonardo da Vinci', + 'optional' => TRUE + ), + (object) array( + 'name' => 'year', + 'type' => 'text', + 'label' => 'Year(s)', + 'placeholder' => '1503 - 1517', + 'optional' => TRUE + ), + (object) array( + 'name' => 'source', + 'type' => 'text', + 'label' => 'Source', + 'placeholder' => 'http://en.wikipedia.org/wiki/Mona_Lisa', + 'optional' => true, + 'regexp' => (object) array( + 'pattern' => '^http[s]?://.+', + 'modifiers' => 'i' + ) + ), + (object) array( + 'name' => 'license', + 'type' => 'select', + 'label' => 'License', + 'default' => 'U', + 'options' => array( + (object) array( + 'value' => 'U', + 'label' => 'Undisclosed' + ), + (object) array( + 'value' => 'CC BY', + 'label' => 'Attribution' + ), + (object) array( + 'value' => 'CC BY-SA', + 'label' => 'Attribution-ShareAlike' + ), + (object) array( + 'value' => 'CC BY-ND', + 'label' => 'Attribution-NoDerivs' + ), + (object) array( + 'value' => 'CC BY-NC', + 'label' => 'Attribution-NonCommercial' + ), + (object) array( + 'value' => 'CC BY-NC-SA', + 'label' => 'Attribution-NonCommercial-ShareAlike' + ), + (object) array( + 'value' => 'CC BY-NC-ND', + 'label' => 'Attribution-NonCommercial-NoDerivs' + ), + (object) array( + 'value' => 'GNU GPL', + 'label' => 'General Public License' + ), + (object) array( + 'value' => 'PD', + 'label' => 'Public Domain' + ), + (object) array( + 'value' => 'ODC PDDL', + 'label' => 'Public Domain Dedication and Licence' + ), + (object) array( + 'value' => 'CC PDM', + 'label' => 'Public Domain Mark' + ), + (object) array( + 'value' => 'C', + 'label' => 'Copyright' + ) + ) + ) + ) + ); + } + + return $semantics; + } } -?> diff --git a/js/h5p-embed.js b/js/h5p-embed.js new file mode 100644 index 0000000..ecbef5e --- /dev/null +++ b/js/h5p-embed.js @@ -0,0 +1,230 @@ +/** + * + */ +var H5P = H5P || (function () { + var head = document.getElementsByTagName('head')[0]; + var contentId = 0; + var contents = {}; + + /** + * Wraps multiple content between a prefix and a suffix. + */ + var wrap = function (prefix, content, suffix) { + var result = ''; + for (var i = 0; i < content.length; i++) { + result += prefix + content[i] + suffix; + } + return result; + } + + /** + * + */ + var loadContent = function (id, script) { + var url = script.getAttribute('data-h5p'); + var data, callback = 'H5P' + id; + + // Prevent duplicate loading. + script.removeAttribute('data-h5p') + + // Callback for when content data is loaded. + window[callback] = function (content) { + contents[id] = content; + + var iframe = document.createElement('iframe'); + var parent = script.parentNode; + parent.insertBefore(iframe, script); + + iframe.id = 'h5p-iframe-' + id; + iframe.style.display = 'block'; + iframe.style.width = '100%'; + iframe.style.height = '1px'; + iframe.style.border = 'none'; + iframe.style.zIndex = 101; + iframe.style.top = 0; + iframe.style.left = 0; + iframe.className = 'h5p-iframe'; + iframe.setAttribute('frameBorder', '0'); + iframe.contentDocument.open(); + iframe.contentDocument.write('\ + \ + \ + \ + ' + wrap('') + '\ + ' + wrap('') + '\ + \ +
\ + '); + iframe.contentDocument.close(); + iframe.contentDocument.documentElement.style.overflow = 'hidden'; + + // Clean up + parent.removeChild(script); + head.removeChild(data); + delete window[callback]; + } + + // Create data script + data = document.createElement('script'); + data.src = url + (url.indexOf('?') === -1 ? '?' : '&') + 'callback=' + callback; + head.appendChild(data); + } + + /** + * Go throught all script tags with the data-h5p attribute and load content. + */ + function H5P() { + var scripts = document.getElementsByTagName('script'); + var h5ps = []; // Use seperate array since scripts grow in size. + for (var i = 0; i < scripts.length; i++) { + var script = scripts[i]; + if (script.hasAttribute('data-h5p')) { + h5ps.push(script); + } + } + for (i = 0; i < h5ps.length; i++) { + loadContent(contentId, h5ps[i]); + contentId++; + } + }; + + /** + * Return integration object + */ + H5P.getIntegration = function (id) { + var content = contents[id]; + return { + getJsonContent: function () { + return content.params; + }, + getContentPath: function () { + return content.path + 'content/' + content.id + '/'; + }, + getFullscreen: function () { + return content.fullscreen; + }, + getLibraryPath: function (library) { + return content.path + 'libraries/' + library; + }, + getContentData: function () { + return { + library: content.library, + jsonContent: content.params, + fullScreen: content.fullscreen, + export: content.export, + embedCode: content.embedCode + }; + }, + i18n: content.i18n + }; + }; + + // Detect if we support fullscreen, and what prefix to use. + var fullScreenBrowserPrefix; + if (document.documentElement.requestFullScreen) { + fullScreenBrowserPrefix = ''; + } + else if (document.documentElement.webkitRequestFullScreen + && navigator.userAgent.indexOf('Android') === -1 // Skip Android + && navigator.userAgent.indexOf('Version/') === -1 // Skip Safari + ) { + fullScreenBrowserPrefix = 'webkit'; + } + else if (document.documentElement.mozRequestFullScreen) { + fullScreenBrowserPrefix = 'moz'; + } + else if (document.documentElement.msRequestFullscreen) { + fullScreenBrowserPrefix = 'ms'; + } + + /** + * Enter fullscreen mode. + */ + H5P.fullScreen = function ($element, instance, exitCallback, body) { + var iframe = document.getElementById('h5p-iframe-' + $element.parent().data('content-id')); + var $classes = $element.add(body); + var $body = $classes.eq(1); + + var done = function (c) { + $classes.removeClass(c); + + if (H5P.fullScreenBrowserPrefix === undefined) { + // Resize content. + if (instance.$ !== undefined) { + instance.$.trigger('resize'); + } + } + + if (exitCallback !== undefined) { + exitCallback(); + } + }; + + if (fullScreenBrowserPrefix === undefined) { + // Create semi fullscreen. + + $classes.addClass('h5p-semi-fullscreen'); + iframe.style.position = 'fixed'; + + var $disable = $element.prepend('Disable fullscreen').children(':first'); + var keyup, disableSemiFullscreen = function () { + $disable.remove(); + $body.unbind('keyup', keyup); + iframe.style.position = 'static'; + done('h5p-semi-fullscreen'); + return false; + }; + keyup = function (event) { + if (event.keyCode === 27) { + disableSemiFullscreen(); + } + }; + $disable.click(disableSemiFullscreen); + $body.keyup(keyup); // TODO: Does not work with iframe's $! + + } + else { + // Create real fullscreen. + + var first, eventName = (fullScreenBrowserPrefix === 'ms' ? 'MSFullscreenChange' : fullScreenBrowserPrefix + 'fullscreenchange'); + document.addEventListener(eventName, function () { + if (first === undefined) { + first = false; + return; + } + done('h5p-fullscreen'); + document.removeEventListener(eventName, arguments.callee, false); + }); + + if (fullScreenBrowserPrefix === '') { + iframe.requestFullScreen(); + } + else { + var method = (fullScreenBrowserPrefix === 'ms' ? 'msRequestFullscreen' : fullScreenBrowserPrefix + 'RequestFullScreen'); + var params = (fullScreenBrowserPrefix === 'webkit' ? Element.ALLOW_KEYBOARD_INPUT : undefined); + iframe[method](params); + } + + $classes.addClass('h5p-fullscreen'); + } + + iframe.style.height = '100%'; + if (H5P.fullScreenBrowserPrefix === undefined) { + // Resize content. + if (instance.$ !== undefined) { + instance.$.trigger('resize', [true]); + } + } + + // Allow H5P to set focus when entering fullscreen mode + if (instance.focus !== undefined) { + instance.focus(); + } + }; + + return H5P; +})(); + +new H5P(); diff --git a/js/h5p-library-details.js b/js/h5p-library-details.js new file mode 100644 index 0000000..4341654 --- /dev/null +++ b/js/h5p-library-details.js @@ -0,0 +1,300 @@ +var H5PLibraryDetails= H5PLibraryDetails || {}; + +(function ($) { + + H5PLibraryDetails.PAGER_SIZE = 20; + /** + * Initializing + */ + H5PLibraryDetails.init = function () { + H5PLibraryDetails.$adminContainer = H5PIntegration.getAdminContainer(); + H5PLibraryDetails.library = H5PIntegration.getLibraryInfo(); + + // currentContent holds the current list if data (relevant for filtering) + H5PLibraryDetails.currentContent = H5PLibraryDetails.library.content; + + // The current page index (for pager) + H5PLibraryDetails.currentPage = 0; + + // The current filter + H5PLibraryDetails.currentFilter = ''; + + // We cache the filtered results, so we don't have to do unneccessary searches + H5PLibraryDetails.filterCache = []; + + // Append library info + H5PLibraryDetails.$adminContainer.append(H5PLibraryDetails.createLibraryInfo()); + + // Append node list + H5PLibraryDetails.$adminContainer.append(H5PLibraryDetails.createContentElement()); + }; + + /** + * Create the library details view + */ + H5PLibraryDetails.createLibraryInfo = function () { + var $libraryInfo = $('
'); + + $.each(H5PLibraryDetails.library.info, function (title, value) { + $libraryInfo.append(H5PUtils.createLabeledField(title, value)); + }); + + // List counter: + $libraryInfo.append(H5PUtils.createLabeledField(H5PLibraryDetails.library.translations.contentCount, H5PLibraryDetails.currentContent ? H5PLibraryDetails.currentContent.length : 0)); + + return $libraryInfo; + }; + + /** + * Create the content list with searching and paging + */ + H5PLibraryDetails.createContentElement = function () { + if (H5PLibraryDetails.currentContent === undefined) { + H5PLibraryDetails.$content = $('
' + H5PLibraryDetails.library.translations.noContent + '
'); + } + else { + H5PLibraryDetails.$content = $('

' + H5PLibraryDetails.library.translations.contentHeader + '

'); + H5PLibraryDetails.createSearchElement(); + H5PLibraryDetails.createPageSizeSelector(); + H5PLibraryDetails.createContentTable(); + H5PLibraryDetails.createPagerElement(); + return H5PLibraryDetails.$content; + } + }; + + /** + * Creates the content list + */ + H5PLibraryDetails.createContentTable = function () { + // Remove it if it exists: + if(H5PLibraryDetails.$contentTable) { + H5PLibraryDetails.$contentTable.remove(); + } + + H5PLibraryDetails.$contentTable = H5PUtils.createTable(); + + var i = (H5PLibraryDetails.currentPage*H5PLibraryDetails.PAGER_SIZE); + var lastIndex = (i+H5PLibraryDetails.PAGER_SIZE); + + if(lastIndex > H5PLibraryDetails.currentContent.length) { + lastIndex = H5PLibraryDetails.currentContent.length; + } + for(; i' + content.title + ''])); + } + + // Appends it to the browser DOM + H5PLibraryDetails.$contentTable.insertAfter(H5PLibraryDetails.$search); + }; + + /** + * Creates the pager element on the bottom of the list + */ + H5PLibraryDetails.createPagerElement = function () { + + // Only create pager if needed: + if(H5PLibraryDetails.currentContent.length > H5PLibraryDetails.PAGER_SIZE) { + + H5PLibraryDetails.$previous = $(''); + H5PLibraryDetails.$next = $(''); + + H5PLibraryDetails.$previous.on('click', function () { + if(H5PLibraryDetails.$previous.hasClass('disabled')) { + return; + } + + H5PLibraryDetails.currentPage--; + H5PLibraryDetails.updatePager(); + H5PLibraryDetails.createContentTable(); + }); + + H5PLibraryDetails.$next.on('click', function () { + if(H5PLibraryDetails.$next.hasClass('disabled')) { + return; + } + + H5PLibraryDetails.currentPage++; + H5PLibraryDetails.updatePager(); + H5PLibraryDetails.createContentTable(); + }); + + // This is the Page x of y widget: + H5PLibraryDetails.$pagerInfo = $(''); + + H5PLibraryDetails.$pager = $('
').append(H5PLibraryDetails.$previous, H5PLibraryDetails.$pagerInfo, H5PLibraryDetails.$next); + H5PLibraryDetails.$content.append(H5PLibraryDetails.$pager); + + H5PLibraryDetails.$pagerInfo.on('click', function () { + var width = H5PLibraryDetails.$pagerInfo.innerWidth(); + H5PLibraryDetails.$pagerInfo.hide(); + + // User has updated the pageNumber + var pageNumerUpdated = function() { + var newPageNum = $gotoInput.val()-1; + var intRegex = /^\d+$/; + + $goto.remove(); + H5PLibraryDetails.$pagerInfo.css({display: 'inline-block'}); + + // Check if input value is valid, and that it has actually changed + if(!(intRegex.test(newPageNum) && newPageNum >= 0 && newPageNum < H5PLibraryDetails.getNumPages() && newPageNum != H5PLibraryDetails.currentPage)) { + return; + } + + H5PLibraryDetails.currentPage = newPageNum; + H5PLibraryDetails.updatePager(); + H5PLibraryDetails.createContentTable(); + }; + + // We create an input box where the user may type in the page number + // he wants to be displayed. + // Reson for doing this is when user has ten-thousands of elements in list, + // this is the easiest way of getting to a specified page + var $gotoInput = $('', { + type: 'number', + min : 1, + max: H5PLibraryDetails.getNumPages(), + on: { + // Listen to blur, and the enter-key: + 'blur': pageNumerUpdated, + 'keyup': function (event) { + if (event.keyCode === 13) { + pageNumerUpdated(); + } + } + } + }).css({width: width}); + var $goto = $('', { + 'class': 'h5p-pager-goto' + }).css({width: width}).append($gotoInput).insertAfter(H5PLibraryDetails.$pagerInfo); + + $gotoInput.focus(); + }); + + H5PLibraryDetails.updatePager(); + } + }; + + /** + * Calculates number of pages + */ + H5PLibraryDetails.getNumPages = function () { + return Math.ceil(H5PLibraryDetails.currentContent.length / H5PLibraryDetails.PAGER_SIZE); + }; + + /** + * Update the pager text, and enables/disables the next and previous buttons as needed + */ + H5PLibraryDetails.updatePager = function () { + H5PLibraryDetails.$pagerInfo.css({display: 'inline-block'}); + + if(H5PLibraryDetails.getNumPages() > 0) { + var message = H5PUtils.translateReplace(H5PLibraryDetails.library.translations.pageXOfY, { + '$x': (H5PLibraryDetails.currentPage+1), + '$y': H5PLibraryDetails.getNumPages() + }); + H5PLibraryDetails.$pagerInfo.html(message); + } + else { + H5PLibraryDetails.$pagerInfo.html(''); + } + + H5PLibraryDetails.$previous.toggleClass('disabled', H5PLibraryDetails.currentPage <= 0); + H5PLibraryDetails.$next.toggleClass('disabled', H5PLibraryDetails.currentContent.length < (H5PLibraryDetails.currentPage+1)*H5PLibraryDetails.PAGER_SIZE); + }; + + /** + * Creates the search element + */ + H5PLibraryDetails.createSearchElement = function () { + + H5PLibraryDetails.$search = $(''); + + var performSeach = function () { + var searchString = $('.h5p-content-search > input').val(); + + // If search string same as previous, just do nothing + if(H5PLibraryDetails.currentFilter === searchString) { + return; + } + + if (searchString.trim().length === 0) { + // If empty search, use the complete list + H5PLibraryDetails.currentContent = H5PLibraryDetails.library.content; + } + else if(H5PLibraryDetails.filterCache[searchString]) { + // If search is cached, no need to filter + H5PLibraryDetails.currentContent = H5PLibraryDetails.filterCache[searchString]; + } + else { + var listToFilter = H5PLibraryDetails.library.content; + + // Check if we can filter the already filtered results (for performance) + if(searchString.length > 1 && H5PLibraryDetails.currentFilter === searchString.substr(0, H5PLibraryDetails.currentFilter.length)) { + listToFilter = H5PLibraryDetails.currentContent; + } + H5PLibraryDetails.currentContent = $.grep(listToFilter, function(content) { + return content.title && content.title.match(new RegExp(searchString, 'i')); + }); + } + + H5PLibraryDetails.currentFilter = searchString; + // Cache the current result + H5PLibraryDetails.filterCache[searchString] = H5PLibraryDetails.currentContent; + H5PLibraryDetails.currentPage = 0; + H5PLibraryDetails.createContentTable(); + + // Display search results: + if (H5PLibraryDetails.$searchResults) { + H5PLibraryDetails.$searchResults.remove(); + } + if (searchString.trim().length > 0) { + H5PLibraryDetails.$searchResults = $('' + H5PLibraryDetails.currentContent.length + ' hits on ' + H5PLibraryDetails.currentFilter + ''); + H5PLibraryDetails.$search.append(H5PLibraryDetails.$searchResults); + } + H5PLibraryDetails.updatePager(); + }; + + var inputTimer = undefined; + $('input', H5PLibraryDetails.$search).on('change keypress paste input', function () { + // Here we start the filtering + // We wait at least 500 ms after last input to perform search + if(inputTimer) { + clearTimeout(inputTimer); + } + + inputTimer = setTimeout( function () { + performSeach(); + }, 500); + }); + + H5PLibraryDetails.$content.append(H5PLibraryDetails.$search); + }; + + /** + * Creates the page size selector + */ + H5PLibraryDetails.createPageSizeSelector = function () { + H5PLibraryDetails.$search.append('
' + H5PLibraryDetails.library.translations.pageSizeSelectorLabel + ':102050100200
'); + + // Listen to clicks on the page size selector: + $('.h5p-admin-pager-size-selector > span', H5PLibraryDetails.$search).on('click', function () { + H5PLibraryDetails.PAGER_SIZE = $(this).data('page-size'); + $('.h5p-admin-pager-size-selector > span', H5PLibraryDetails.$search).removeClass('selected'); + $(this).addClass('selected'); + H5PLibraryDetails.currentPage = 0; + H5PLibraryDetails.createContentTable(); + H5PLibraryDetails.updatePager(); + }); + }; + + // Initialize me: + $(document).ready(function () { + if (!H5PLibraryDetails.initialized) { + H5PLibraryDetails.initialized = true; + H5PLibraryDetails.init(); + } + }); + +})(H5P.jQuery); \ No newline at end of file diff --git a/js/h5p-library-list.js b/js/h5p-library-list.js new file mode 100644 index 0000000..4ddfc7d --- /dev/null +++ b/js/h5p-library-list.js @@ -0,0 +1,73 @@ +var H5PLibraryList= H5PLibraryList || {}; + +(function ($) { + + /** + * Initializing + */ + H5PLibraryList.init = function () { + var $adminContainer = H5PIntegration.getAdminContainer(); + + // Create library list + $adminContainer.append(H5PLibraryList.createLibraryList(H5PIntegration.getLibraryList())); + }; + + /** + * Create the library list + * + * @param {object} libraries List of libraries and headers + */ + H5PLibraryList.createLibraryList = function (libraries) { + + if(libraries.listData === undefined || libraries.listData.length === 0) { + return; + } + + // Create table + var $table = H5PUtils.createTable(libraries.listHeaders); + $table.addClass('libraries'); + + // Add libraries + $.each (libraries.listData, function (index, library) { + var $libraryRow = H5PUtils.createTableRow([ + library.name, + library.machineName, + library.contentCount, + library.libraryDependencyCount, + '' + + '' + ]); + + // Open details view when clicked + $('.h5p-admin-view-library', $libraryRow).on('click', function () { + window.location.href = library.detailsUrl; + }); + + var $deleteButton = $('.h5p-admin-delete-library', $libraryRow); + if (library.contentCount !== 0 || library.libraryDependencyCount !== 0) { + // Disabled delete if content. + $deleteButton.attr('disabled', true); //.addClass('disabled'); + } + else { + // Go to delete page om click. + $deleteButton.on('click', function () { + window.location.href = library.deleteUrl; + }); + } + + $table.append($libraryRow); + }); + + return $table; + }; + + + // Initialize me: + $(document).ready(function () { + if (!H5PLibraryList.initialized) { + H5PLibraryList.initialized = true; + H5PLibraryList.init(); + } + }); + +})(H5P.jQuery); diff --git a/js/h5p-utils.js b/js/h5p-utils.js new file mode 100644 index 0000000..dae71e2 --- /dev/null +++ b/js/h5p-utils.js @@ -0,0 +1,69 @@ +var H5PUtils = H5PUtils || {}; + +(function ($) { + /** + * Generic function for creating a table including the headers + * + * @param {array} headers List of headers + */ + H5PUtils.createTable = function (headers) { + var $table = $('
'); + + if(headers) { + var $thead = $(''); + var $tr = $(''); + + $.each(headers, function (index, value) { + $tr.append('' + value + ''); + }); + + $table.append($thead.append($tr)) + } + + return $table; + }; + + /** + * Generic function for creating a table row + * + * @param {array} rows Value list. Object name is used as class name in + */ + H5PUtils.createTableRow = function (rows) { + var $tr = $(''); + + $.each(rows, function (index, value) { + $tr.append('' + value + ''); + }); + + return $tr; + }; + + /** + * Generic function for creating a field containing label and value + * + * @param {string} label The label displayed in front of the value + * @param {string} value The value + */ + H5PUtils.createLabeledField = function (label, value) { + var $field = $('
'); + + $field.append('
' + label + '
'); + $field.append('
' + value + '
'); + + return $field; + } + + /** + * Replaces placeholder fields in translation strings + * + * @param {string} template The translation template string in the following format: "$name is a $sex" + * @param {array} replacors An js object with key and values. Eg: {'$name': 'Frode', '$sex': 'male'} + */ + H5PUtils.translateReplace = function (template, replacors) { + $.each(replacors, function (key, value) { + template = template.replace(new RegExp('\\'+key, 'g'), value) + }); + return template; + } + +})(H5P.jQuery); \ No newline at end of file diff --git a/js/h5p.js b/js/h5p.js index 5edcafa..9b9087e 100644 --- a/js/h5p.js +++ b/js/h5p.js @@ -1,175 +1,217 @@ var H5P = H5P || {}; -// This needs to be determined before init is run. -H5P.isFramed = (window.self !== window.top); // (window.parent !== window); +// Determine if we're inside an iframe. +H5P.isFramed = (window.self !== window.top); + +// Useful jQuery object. +H5P.$window = H5P.jQuery(window); + +// Detect if we support fullscreen, and what prefix to use. +if (document.documentElement.requestFullScreen) { + H5P.fullScreenBrowserPrefix = ''; +} +else if (document.documentElement.webkitRequestFullScreen + && navigator.userAgent.indexOf('Android') === -1 // Skip Android + && navigator.userAgent.indexOf('Version/') === -1 // Skip Safari + ) { + H5P.fullScreenBrowserPrefix = 'webkit'; +} +else if (document.documentElement.mozRequestFullScreen) { + H5P.fullScreenBrowserPrefix = 'moz'; +} +else if (document.documentElement.msRequestFullscreen) { + H5P.fullScreenBrowserPrefix = 'ms'; +} /** * Initialize H5P content. * Scans for ".h5p-content" in the document and initializes H5P instances where found. */ H5P.init = function () { - if (H5P.$window === undefined) { - H5P.$window = H5P.jQuery(window); - } - if (H5P.$body === undefined) { - H5P.$body = H5P.jQuery('body'); - } + // Useful jQuery object. + H5P.$body = H5P.jQuery('body'); - // Is this H5P being run in a frame? - if (H5P.isFramed) { - H5P.$body.addClass('h5p-iframe-content'); - } - - if (H5P.fullScreenBrowserPrefix === undefined) { - if (document.documentElement.requestFullScreen) { - H5P.fullScreenBrowserPrefix = ''; - } - else if (document.documentElement.webkitRequestFullScreen - && navigator.userAgent.indexOf('Android') === -1 // Skip Android - && navigator.userAgent.indexOf('Version/') === -1 // Skip Safari - ) { - H5P.fullScreenBrowserPrefix = 'webkit'; - } - else if (document.documentElement.mozRequestFullScreen) { - H5P.fullScreenBrowserPrefix = 'moz'; - } - else if (document.documentElement.msRequestFullscreen) { - H5P.fullScreenBrowserPrefix = 'ms'; - } - } + // Prepare internal resizer for content. + var $window = H5P.jQuery(window.top); // H5Ps added in normal DIV. - H5P.jQuery(".h5p-content").each(function (idx, el) { - var $el = H5P.jQuery(el), - contentId = $el.data('content-id'), - mainLibrary = $el.data('class'), - obj = new (H5P.classFromName(mainLibrary))(H5P.jQuery.parseJSON(H5PIntegration.getJsonContent(contentId)), contentId); + var $containers = H5P.jQuery(".h5p-content").each(function () { + var $element = H5P.jQuery(this); + var $container = H5P.jQuery('
').appendTo($element); + var contentId = $element.data('content-id'); + var contentData = H5PIntegration.getContentData(contentId); + var library = { + library: contentData.library, + params: H5P.jQuery.parseJSON(contentData.jsonContent) + }; - // Render H5P in container. - obj.attach($el); - - // Add Fullscreen button if relevant. - if (H5PIntegration.getFullscreen(contentId)) { - H5P.jQuery('
' + H5PIntegration.fullscreenText + '
').insertBefore($el).children().click(function () { - H5P.fullScreen($el, obj); - return false; + // Create new instance. + var instance = H5P.newRunnable(library, contentId); + instance.attach($container); // Not sent to newRunnable to avoid resize. + + // Check if we should add and display a fullscreen button for this H5P. + if (contentData.fullScreen == 1) { + H5P.jQuery('
' + H5P.t('fullscreen') + '
').insertBefore($container).children().click(function () { + H5P.fullScreen($container, instance); }); }; + + var $actions = H5P.jQuery('
    '); + if (contentData.export !== '') { + // Display export button + H5P.jQuery('
  • ' + H5P.t('download') + '
  • ').appendTo($actions).click(function () { + window.location.href = contentData.export; + }); + } + if (instance.getCopyrights !== undefined) { + // Display copyrights button + H5P.jQuery('
  • ' + H5P.t('copyrights') + '
  • ').appendTo($actions).click(function () { + H5P.openCopyrightsDialog($actions, instance); + }); + } + if (contentData.embedCode !== undefined) { + // Display embed button + H5P.jQuery('
  • ' + H5P.t('embed') + '
  • ').appendTo($actions).click(function () { + H5P.openEmbedDialog($actions, contentData.embedCode); + }); + } + H5P.jQuery('
  • ').appendTo($actions); + $actions.insertAfter($container); + + if (H5P.isFramed) { + // Make it possible to resize the iframe when the content changes size. This way we get no scrollbars. + var iframe = window.parent.document.getElementById('h5p-iframe-' + contentId); + var resizeIframe = function () { + // Use timeout to make sure the iframe is resized + setTimeout(function () { + var fullscreen = $container.hasClass('h5p-fullscreen') || $container.hasClass('h5p-semi-fullscreen'); + if (!fullscreen) { + // Retain parent size to avoid jumping/scrolling + var parentHeight = iframe.parentElement.style.height; + iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px'; + + // Reset iframe height, incase content has shrinked. + iframe.style.height = '1px'; + + // Resize iframe so all content is visible. + iframe.style.height = (iframe.contentDocument.body.scrollHeight) + 'px'; + + // Free parent + iframe.parentElement.style.height = parentHeight; + } + }, 1); + }; + + if (instance.$ !== undefined) { + instance.$.on('resize', resizeIframe); + } + } + + var resize = function () { + if (instance.$ !== undefined) { + // Resize content. + instance.$.trigger('resize'); + } + }; + resize(); + + // Resize everything when window is resized. + $window.resize(resize); }); - // H5Ps living in iframes. Note: Fullscreen button will be added - // inside iFrame if relevant - var $h5pIframes = H5P.jQuery(".h5p-iframe"); - if ($h5pIframes.length !== 0) { - $h5pIframes.each(function (idx, iframe) { - var $iframe = H5P.jQuery(iframe), - contentId = $iframe.data('content-id'), - mainLibrary = $iframe.data('class'); + // Insert H5Ps that should be in iframes. + H5P.jQuery("iframe.h5p-iframe").each(function () { + var $iframe = H5P.jQuery(this); + var contentId = $iframe.data('content-id'); + + // DEPRECATED AND WILL BE REMOVED. MAKE SURE YOUR H5Ps EXPOSES A $ AND A resize FUNCTION. + $iframe.ready(function () { + resizeIframeInterval = setInterval(function () { + if (H5P.isFullscreen) { + return; + } - $iframe.ready(function () { - // This is a bit hacky but necessary until libraries runs callbacks or similar when "done" or resizing or something. - resizeIframeInterval = setInterval(function () { - var $doc = $iframe.contents(); - var contentHeight = $doc.height(); - var frameHeight = $iframe.innerHeight(); - - if (frameHeight !== contentHeight) { - H5P.resizeIframe(contentId, contentHeight); - $doc[0].documentElement.style.margin = '0 0 1px 0'; - } - else { - // Small trick to make scrollbars go away in ie. - $doc[0].documentElement.style.margin = '0 0 0 0'; - } - - }, 300); - }); + var $doc = $iframe.contents(); + var contentHeight = $doc.height(); + var frameHeight = $iframe.innerHeight(); + - iframe.contentDocument.open(); - iframe.contentDocument.write('' + H5PIntegration.getHeadTags(contentId) + '
    '); - iframe.contentDocument.close(); + if (frameHeight !== contentHeight) { + $iframe.css('height', contentHeight + 'px'); + $doc[0].documentElement.style.overflow = 'hidden'; + } + }, 500); }); - } -}; + // END DEPRECATION -/** - * Fullscreen iframe container - * - * @param {string} contentId Content id of H5P in iframe - * @param {object} obj H5P object - * @param {function} exitCallback Callback function called when user exits fullscreen. - * @returns {undefined} - */ -H5P.fullScreenIframe = function (contentId, obj, exitCallback, $body) { - H5P.fullScreen(H5P.jQuery('#h5p-iframe-' + contentId + '-wrapper'), obj, exitCallback, $body); -}; - -/** - * Resize iframe height. - * - * @param {string} contentId Content id of H5P in iframe - * @param {integer} height New height in pixels. - * @returns {undefined} - */ -H5P.resizeIframe = function (contentId, height) { - var iframe = document.getElementById('h5p-iframe-' + contentId); - iframe.style.height = (H5P.isFullscreen) ? '100%' : height + 'px'; + this.contentDocument.open(); + this.contentDocument.write('' + H5PIntegration.getHeadTags(contentId) + '
    '); + this.contentDocument.close(); + }); }; /** * Enable full screen for the given h5p. * - * @param {jQuery} $el Container - * @param {object} obj H5P + * @param {jQuery} $element Content container. + * @param {object} instance * @param {function} exitCallback Callback function called when user exits fullscreen. + * @param {jQuery} $body For internal use. Gives the body of the iframe. * @returns {undefined} */ -H5P.fullScreen = function ($el, obj, exitCallback, $body) { - if ($body === undefined) { - $body = H5P.$body; - } - +H5P.fullScreen = function ($element, instance, exitCallback, body) { if (H5P.isFramed) { - var $classes = H5P.jQuery('html').add(H5P.$body).add($el); - $classes.addClass('h5p-fullscreen'); - window.parent.H5P.fullScreenIframe($el.data('content-id'), obj, function () { - $classes.removeClass('h5p-fullscreen'); - }, $body); - + // Trigger resize on wrapper in parent window. + window.parent.H5P.fullScreen($element, instance, exitCallback, H5P.$body.get()); return; } + + var $container = $element; + var $classes, $iframe; + if (body === undefined) { + $body = H5P.$body; + } + else { + // We're called from an iframe. + $body = H5P.jQuery(body); + $classes = $body.add($element.get()); + var iframeSelector = '#h5p-iframe-' + $element.parent().data('content-id'); + $iframe = H5P.jQuery(iframeSelector); + $element = $iframe.parent(); // Put iframe wrapper in fullscreen, not container. + } + + $classes = $element.add(H5P.$body).add($classes); + + var done = function (c) { + H5P.isFullscreen = false; + $classes.removeClass(c); + + if (H5P.fullScreenBrowserPrefix === undefined) { + // Resize content. + if (instance.$ !== undefined) { + instance.$.trigger('resize'); + } + else if (instance.resize !== undefined) { + instance.resize(); + } + } + + if (exitCallback !== undefined) { + exitCallback(); + } + }; if (H5P.fullScreenBrowserPrefix === undefined) { // Create semi fullscreen. - $el.add(H5P.$body).addClass('h5p-semi-fullscreen'); - // Move H5P content to top of body to make sure it is above other page - // content. Insert placeholder in original position to be able to move it - // back. - // THIS DOES NOT WORK WITH IFRAMED CONTENT, iframe will reload/fail. - // $el.after('
    ').prependTo(H5P.$body); - + + $classes.addClass('h5p-semi-fullscreen'); H5P.isFullscreen = true; - var $disable = H5P.jQuery('Disable fullscreen').appendTo($el); + var $disable = $container.prepend('Disable fullscreen').children(':first'); var keyup, disableSemiFullscreen = function () { - $el.add(H5P.$body).removeClass('h5p-semi-fullscreen'); - // H5P.jQuery('#h5pfullscreenreplacementplaceholder').before($el).remove(); - $disable.remove(); - H5P.isFullscreen = false; + $disable.remove(); $body.unbind('keyup', keyup); - - H5P.jQuery(".h5p-iframe").each(function (idx, el) { - H5P.resizeIframe(H5P.jQuery(el).data('content-id'), 0); - }); - - if (exitCallback) { - exitCallback(); - } - - if (obj.resize !== undefined) { - obj.resize(false); - } - + done('h5p-semi-fullscreen'); return false; }; keyup = function (event) { @@ -181,6 +223,8 @@ H5P.fullScreen = function ($el, obj, exitCallback, $body) { $body.keyup(keyup); } else { + // Create real fullscreen. + var first, eventName = (H5P.fullScreenBrowserPrefix === 'ms' ? 'MSFullscreenChange' : H5P.fullScreenBrowserPrefix + 'fullscreenchange'); H5P.isFullscreen = true; document.addEventListener(eventName, function () { @@ -188,39 +232,40 @@ H5P.fullScreen = function ($el, obj, exitCallback, $body) { first = false; return; } - H5P.isFullscreen = false; - $el.add(H5P.$body).removeClass('h5p-fullscreen'); - - H5P.jQuery(".h5p-iframe").each(function (idx, el) { - H5P.resizeIframe(H5P.jQuery(el).data('content-id'), 0); - }); - - if (exitCallback) { - exitCallback(); - } - - if (obj.resize !== undefined) { - obj.resize(false); - } + done('h5p-fullscreen'); document.removeEventListener(eventName, arguments.callee, false); }); if (H5P.fullScreenBrowserPrefix === '') { - $el[0].requestFullScreen(); + $element[0].requestFullScreen(); } else { var method = (H5P.fullScreenBrowserPrefix === 'ms' ? 'msRequestFullscreen' : H5P.fullScreenBrowserPrefix + 'RequestFullScreen'); var params = (H5P.fullScreenBrowserPrefix === 'webkit' ? Element.ALLOW_KEYBOARD_INPUT : undefined); - $el[0][method](params); + $element[0][method](params); } - $el.add(H5P.$body).addClass('h5p-fullscreen'); + $classes.addClass('h5p-fullscreen'); } - H5P.jQuery(".h5p-iframe").each(function (idx, el) { - H5P.resizeIframe(H5P.jQuery(el).data('content-id'), 0); - }); - if (obj.resize !== undefined) { - obj.resize(true); + + if ($iframe !== undefined) { + // Set iframe to its default size(100%). + $iframe.css('height', ''); + } + + if (H5P.fullScreenBrowserPrefix === undefined) { + // Resize content. + if (instance.$ !== undefined) { + instance.$.trigger('resize'); + } + else if (instance.resize !== undefined) { + instance.resize(); + } + } + + // Allow H5P to set focus when entering fullscreen mode + if (instance.focus !== undefined) { + instance.focus(); } }; @@ -238,7 +283,7 @@ H5P.getPath = function (path, contentId) { if (path.substr(0, 7) === 'http://' || path.substr(0, 8) === 'https://') { return path; } - + return H5PIntegration.getContentPath(contentId) + path; }; @@ -256,6 +301,8 @@ H5P.getContentPath = function (contentId) { /** * Get library class constructor from H5P by classname. + * Note that this class will only work for resolve "H5P.NameWithoutDot". + * Also check out: H5P.newRunnable * * Used from libraries to construct instances of other libraries' objects by name. * @@ -267,7 +314,479 @@ H5P.classFromName = function (name) { return this[arr[arr.length-1]]; }; -// Helper object for keeping coordinates in the same format all over. +/** + * A safe way of creating a new instance of a runnable H5P. + * + * TODO: Should we check if version matches the library? + * TODO: Dynamically try to load libraries currently not loaded? That will require a callback. + * + * @param {Object} library Library/action object form params. + * @param {Number} contentId + * @param {jQuery} $attachTo The element to attach the new instance to. + * @return {Object} Instance. + */ +H5P.newRunnable = function (library, contentId, $attachTo) { + try { + var nameSplit = library.library.split(' ', 2); + var versionSplit = nameSplit[1].split('.', 2); + } + catch (err) { + return H5P.error('Invalid library string: ' + library.library); + } + + if ((library.params instanceof Object) !== true || (library.params instanceof Array) === true) { + H5P.error('Invalid library params for: ' + library.library); + return H5P.error(library.params); + } + + // Find constructor function + try { + nameSplit = nameSplit[0].split('.'); + var constructor = window; + for (var i = 0; i < nameSplit.length; i++) { + constructor = constructor[nameSplit[i]]; + }; + if (typeof constructor !== 'function') { + throw null; + } + } + catch (err) { + return H5P.error('Unable to find constructor for: ' + library.library); + } + + var instance = new constructor(library.params, contentId); + if ($attachTo !== undefined) { + instance.attach($attachTo); + if (instance.$ !== undefined) { + // Resize content. + instance.$.trigger('resize'); + } + } + return instance; +}; + +/** + * Used to print useful error messages. + * + * @param {mixed} err Error to print. + * @returns {undefined} + */ +H5P.error = function (err) { + if (window['console'] !== undefined && console.error !== undefined) { + console.error(err); + } +} + +/** + * Translate text strings. + * + * @param {String} key Translation identifier, may only contain a-zA-Z0-9. No spaces or special chars. + * @param {Object} vars Data for placeholders. + * @param {String} ns Translation namespace. Defaults to H5P. + * @returns {String} Text + */ +H5P.t = function (key, vars, ns) { + if (ns === undefined) { + ns = 'H5P'; + } + + if (H5PIntegration.i18n[ns] === undefined) { + return '[Missing translation namespace "' + ns + '"]'; + } + + if (H5PIntegration.i18n[ns][key] === undefined) { + return '[Missing translation "' + key + '" in "' + ns + '"]'; + } + + var translation = H5PIntegration.i18n[ns][key]; + + if (vars !== undefined) { + // Replace placeholder with variables. + for (var placeholder in vars) { + translation = translation.replace(placeholder, vars[placeholder]); + } + } + + return translation; +}; + +H5P.Dialog = function (name, title, content, $element) { + var self = this; + var $dialog = H5P.jQuery('
    \ +
    \ +

    ' + title + '

    \ +
    ' + content + '
    \ +
    \ +
    \ +
    ') + .insertAfter($element) + .click(function () { + self.close(); + }) + .children('.h5p-inner') + .click(function () { + return false; + }) + .find('.h5p-close') + .click(function () { + self.close(); + }) + .end() + .end(); + + this.open = function () { + setTimeout(function () { + $dialog.addClass('h5p-open'); // Fade in + // Triggering an event, in case something has to be done after dialog has been opened. + H5P.jQuery(self).trigger('dialog-opened', [$dialog]); + }, 1); + }; + + this.close = function () { + $dialog.removeClass('h5p-open'); // Fade out + setTimeout(function () { + $dialog.remove(); + }, 200); + }; +}; + +/** + * Gather copyright information and display in a dialog over the content. + * + * @param {jQuery} $element to insert dialog after. + * @param {object} instance to get copyright information from. + * @returns {undefined} + */ +H5P.openCopyrightsDialog = function ($element, instance) { + var copyrights = instance.getCopyrights(); + if (copyrights !== undefined) { + copyrights = copyrights.toString(); + } + if (copyrights === undefined || copyrights === '') { + copyrights = H5P.t('noCopyrights'); + } + + var dialog = new H5P.Dialog('copyrights', H5P.t('copyrightInformation'), copyrights, $element); + dialog.open(); +}; + +/** + * Display a dialog containing the embed code. + * + * @param {jQuery} $element to insert dialog after. + * @param {string} embed code. + * @returns {undefined} + */ +H5P.openEmbedDialog = function ($element, embedCode) { + var dialog = new H5P.Dialog('embed', H5P.t('embed'), '', $element); + + // Selecting embed code when dialog is opened + H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { + $dialog.find('.h5p-embed-code-container').select(); + }); + + dialog.open(); +}; + +/** + * Copyrights for a H5P Content Library. + */ +H5P.ContentCopyrights = function () { + var label; + var media = []; + var content = []; + + /** + * Public. Set label. + * + * @param {String} newLabel + */ + this.setLabel = function (newLabel) { + label = newLabel; + }; + + /** + * Public. Add sub content. + * + * @param {H5P.MediaCopyright} newMedia + */ + this.addMedia = function (newMedia) { + if (newMedia !== undefined) { + media.push(newMedia); + } + }; + + /** + * Public. Add sub content. + * + * @param {H5P.ContentCopyrights} newContent + */ + this.addContent = function (newContent) { + if (newContent !== undefined) { + content.push(newContent); + } + }; + + /** + * Public. Print content copyright. + * + * @returns {String} HTML. + */ + this.toString = function () { + var html = ''; + + // Add media rights + for (var i = 0; i < media.length; i++) { + html += media[i]; + } + + // Add sub content rights + for (var i = 0; i < content.length; i++) { + html += content[i]; + } + + + if (html !== '') { + // Add a label to this info + if (label !== undefined) { + html = '

    ' + label + '

    ' + html; + } + + // Add wrapper + html = '
    ' + html + '
    '; + } + + return html; + }; +} + +/** + * A ordered list of copyright fields for media. + * + * @param {Object} copyright information fields. + * @param {Object} labels translation. Optional. + * @param {Array} order of fields. Optional. + * @param {Object} extraFields for copyright. Optional. + */ +H5P.MediaCopyright = function (copyright, labels, order, extraFields) { + var thumbnail; + var list = new H5P.DefinitionList(); + + /** + * Private. Get translated label for field. + * + * @param {String} fieldName + * @return {String} + */ + var getLabel = function (fieldName) { + if (labels === undefined || labels[fieldName] === undefined) { + return H5P.t(fieldName); + } + + return labels[fieldName]; + }; + + /** + * Private. Get humanized value for field. + * + * @param {String} fieldName + * @return {String} + */ + var humanizeValue = function (fieldName, value) { + if (fieldName === 'license') { + return H5P.copyrightLicenses[value]; + } + + return value; + }; + + if (copyright !== undefined) { + // Add the extra fields + for (var field in extraFields) { + if (extraFields.hasOwnProperty(field)) { + copyright[field] = extraFields[field]; + } + } + + if (order === undefined) { + // Set default order + order = ['title', 'author', 'year', 'source', 'license']; + } + + for (var i = 0; i < order.length; i++) { + var fieldName = order[i]; + if (copyright[fieldName] !== undefined) { + list.add(new H5P.Field(getLabel(fieldName), humanizeValue(fieldName, copyright[fieldName]))); + } + } + } + + /** + * Public. Set thumbnail. + * + * @param {H5P.Thumbnail} newThumbnail + */ + this.setThumbnail = function (newThumbnail) { + thumbnail = newThumbnail; + }; + + /** + * Public. Checks if this copyright is undisclosed. + * I.e. only has the license attribute set, and it's undisclosed. + * + * @returns {Boolean} + */ + this.undisclosed = function () { + if (list.size() === 1) { + var field = list.get(0); + if (field.getLabel() === getLabel('license') && field.getValue() === humanizeValue('license', 'U')) { + return true; + } + } + return false; + }; + + /** + * Public. Print media copyright. + * + * @returns {String} HTML. + */ + this.toString = function () { + var html = ''; + + if (this.undisclosed()) { + return html; // No need to print a copyright with a single undisclosed license. + } + + if (thumbnail !== undefined) { + html += thumbnail; + } + html += list; + + if (html !== '') { + html = ''; + } + + return html; + }; +} + +// Translate table for copyright license codes. +H5P.copyrightLicenses = { + 'U': 'Undisclosed', + 'CC BY': 'Attribution', + 'CC BY-SA': 'Attribution-ShareAlike', + 'CC BY-ND': 'Attribution-NoDerivs', + 'CC BY-NC': 'Attribution-NonCommercial', + 'CC BY-NC-SA': 'Attribution-NonCommercial-ShareAlike', + 'CC BY-NC-ND': 'Attribution-NonCommercial-NoDerivs', + 'GNU GPL': 'General Public License', + 'PD': 'Public Domain', + 'ODC PDDL': 'Public Domain Dedication and Licence', + 'CC PDM': 'Public Domain Mark', + 'C': 'Copyright' +}; + +/** + * Simple class for creating thumbnails for images. + * + * @param {String} source + * @param {Number} width + * @param {Number} height + */ +H5P.Thumbnail = function (source, width, height) { + var thumbWidth, thumbHeight = 100; + if (width !== undefined) { + thumbWidth = Math.round(thumbHeight * (width / height)); + } + + /** + * Public. Print thumbnail. + * + * @returns {String} HTML. + */ + this.toString = function () { + return '' + H5P.t('thumbnail') + ''; + }; +} + +/** + * Simple data class for storing a single field. + */ +H5P.Field = function (label, value) { + /** + * Public. Get field label. + * + * @returns {String} + */ + this.getLabel = function () { + return label; + }; + + /** + * Public. Get field value. + * + * @returns {String} + */ + this.getValue = function () { + return value; + }; +} + +/** + * Simple class for creating a definition list. + */ +H5P.DefinitionList = function () { + var fields = []; + + /** + * Public. Add field to list. + * + * @param {H5P.Field} field + */ + this.add = function (field) { + fields.push(field); + }; + + /** + * Public. Get Number of fields. + * + * @returns {Number} + */ + this.size = function () { + return fields.length; + }; + + /** + * Public. Get field at given index. + * + * @param {Number} index + * @returns {Object} + */ + this.get = function (index) { + return fields[index]; + }; + + /** + * Public. Print definition list. + * + * @returns {String} HTML. + */ + this.toString = function () { + var html = ''; + for (var i = 0; i < fields.length; i++) { + var field = fields[i]; + html += '
    ' + field.getLabel() + '
    ' + field.getValue() + '
    '; + } + return (html === '' ? html : '
    ' + html + '
    '); + }; +} + +/** + * THIS FUNCTION/CLASS IS DEPRECATED AND WILL BE REMOVED. + * + * Helper object for keeping coordinates in the same format all over. + */ H5P.Coords = function (x, y, w, h) { if ( !(this instanceof H5P.Coords) ) return new H5P.Coords(x, y, w, h); @@ -326,9 +845,9 @@ H5P.libraryFromString = function (library) { /** * Get the path to the library * - * @param {string} library - * The library identifier in the format "machineName-majorVersion.minorVersion" - * @returns {string} The full path to the library + * @param {String} library + * The library identifier in the format "machineName-majorVersion.minorVersion". + * @returns {String} The full path to the library. */ H5P.getLibraryPath = function (library) { return H5PIntegration.getLibraryPath(library); @@ -336,6 +855,7 @@ H5P.getLibraryPath = function (library) { /** * Recursivly clone the given object. + * TODO: Consider if this needs to be in core. Doesn't $.extend do the same? * * @param {object} object Object to clone. * @param {type} recursive @@ -360,6 +880,7 @@ H5P.cloneObject = function (object, recursive) { /** * Remove all empty spaces before and after the value. + * TODO: Only include this or String.trim(). What is best? * * @param {String} value * @returns {@exp;value@call;replace} @@ -402,6 +923,7 @@ H5P.cssLoaded = function (path) { /** * Shuffle an array in place. + * TODO: Consider if this should be a part of core. I'm guessing very few libraries are going to use it. * * @param {array} array Array to shuffle * @returns {array} The passed array is returned for chaining. @@ -425,6 +947,7 @@ H5P.shuffleArray = function (array) { /** * Post finished results for user. + * TODO: Should we use events instead? That way the parent can handle the results of the child. * * @param {Number} contentId * @param {Number} points diff --git a/styles/h5p-admin.css b/styles/h5p-admin.css new file mode 100644 index 0000000..599afe0 --- /dev/null +++ b/styles/h5p-admin.css @@ -0,0 +1,227 @@ +/* Font Awesome font licensed under SIL OFL 1.1 · Code licensed under MIT License */ +@font-face { + font-family: 'H5PFontAwesome4'; /* Named so to avoid collisions and preserve backwards compatibility! */ + src: url('../fonts/fontawesome-webfont.eot?v=4.0.3'); + src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff?v=4.0.3') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.0.3') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg'); + font-weight: normal; + font-style: normal; +} + +.h5p-content { + border: 1px solid #DDD; + border-radius: 3px; + padding: 10px; +} + +.h5p-admin-table, +.h5p-admin-table > tbody { + border: none; +} + +.h5p-admin-table tr:nth-child(odd) { + background-color: #F9F9F9; +} +.h5p-admin-table tbody tr:hover { + background-color: #EEE; +} +.h5p-admin-table.empty { + padding: 1em; + background-color: #EEE; + font-size: 1.2em; + font-weight: bold; +} + +.h5p-admin-table.libraries th:last-child, +.h5p-admin-table.libraries td:last-child { + text-align: right; +} + +.h5p-admin-table.libraries button { + font-family: H5PFontAwesome4; + font-size: 1.6em; + font-style: normal; + font-weight: normal; + margin-right: 10px; + cursor: pointer; + border: 1px solid #AAA; + border-radius: .2em; + line-height: 1.6em; + width: 1.6em; + background-color: #e0e0e0; + text-shadow: 0 0 0.5em #fff; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.h5p-admin-table.libraries button:hover, +.h5p-admin-table.libraries .h5p-admin-delete-library:hover { + background-color: #d0d0d0; +} + +.h5p-admin-table.libraries .h5p-admin-delete-library:disabled:hover { + background-color: #e0e0e0; +} + +.h5p-admin-table.libraries .h5p-admin-delete-library { + color: #f9010f; +} +.h5p-admin-table.libraries .h5p-admin-delete-library:disabled { + cursor: default; + color: #c0c0c0; +} + +.h5p-library-info { + padding: 1em 1em; + margin: 1em 0; + + width: 350px; + + border: 1px solid #DDD; + border-radius: 3px; +} + +/* Labeled field (label + value) */ +.h5p-labeled-field { + border-bottom: 1px solid #ccc; +} +.h5p-labeled-field:last-child { + border-bottom: none; +} + +.h5p-labeled-field .h5p-label { + display: inline-block; + min-width: 150px; + font-size: 1.2em; + font-weight: bold; + padding: 0.2em; +} + +.h5p-labeled-field .h5p-value { + display: inline-block; + padding: 0.2em; +} + +/* Search element */ +.h5p-content-search { + display: inline-block; + position: relative; + + width: 100%; + padding: 5px 0; + margin-top: 10px; + + border: 1px solid #CCC; + border-radius: 3px; + box-shadow: 2px 2px 5px #888888; +} +.h5p-content-search:before { + font-family: H5PFontAwesome4; + margin: 0 10px; + content: "\f002"; + font-size: 1.4em; +} +.h5p-content-search input { + font-size: 120%; + line-height: 120%; +} +.h5p-admin-search-results { + margin-left: 10px; + color: #888; +} + +.h5p-admin-pager-size-selector { + position: absolute; + right: 10px; + top: .75em; + display: inline-block; +} +.h5p-admin-pager-size-selector > span { + padding: 5px; + margin-left: 10px; + cursor: pointer; + border: 1px solid #CCC; + border-radius: 3px; +} +.h5p-admin-pager-size-selector > span.selected { + background-color: #edf5fa; +} +.h5p-admin-pager-size-selector > span:hover { + background-color: #555; + color: #FFF; +} + +/* Generic "javascript"-action button */ +button.h5p-admin { + border: 1px solid #AAA; + border-radius: 5px; + padding: 3px 10px; + background-color: #EEE; + cursor: pointer; + display: inline-block; + text-align: center; + color: #222; +} +button.h5p-admin:hover { + background-color: #555; + color: #FFF; +} +button.h5p-admin.disabled, +button.h5p-admin.disabled:hover { + cursor: auto; + color: #CCC; + background-color: #FFF; +} + +/* Pager element */ +.h5p-content-pager { + display: inline-block; + border: 1px solid #CCC; + border-radius: 3px; + box-shadow: 2px 2px 5px #888888; + width: 100%; + text-align: center; + padding: 3px 0; +} +.h5p-content-pager > button { + min-width: 80px; + font-size: 130%; + line-height: 130%; + border: none; + background: none; + font-family: H5PFontAwesome4; + font-size: 1.4em; +} +.h5p-content-pager > button:focus { + outline: 0; +} +.h5p-content-pager > button:last-child { + margin-left: 10px; +} +.h5p-content-pager > .pager-info { + cursor: pointer; + padding: 5px; + border-radius: 3px; +} +.h5p-content-pager > .pager-info:hover { + background-color: #555; + color: #FFF; +} +.h5p-content-pager > .pager-info, +.h5p-content-pager > .h5p-pager-goto { + margin: 0 10px; + line-height: 130%; + display: inline-block; +} + +.h5p-admin-header { + margin-top: 1.5em; +} +#h5p-library-upload-form.h5p-admin-upload-libraries-form { + position: relative; + margin: 0; + +} +.h5p-admin-upload-libraries-form .form-submit { + position: absolute; + top: 0; + right: 0; +} diff --git a/styles/h5p.css b/styles/h5p.css index 31a0b18..ea2c6b4 100644 --- a/styles/h5p.css +++ b/styles/h5p.css @@ -7,22 +7,42 @@ font-style: normal; } -body.h5p-iframe-content { - font-family: Arial, Helvetica, sans-serif; - margin: 0; +@font-face { + font-family: 'H5P'; + src:url('../fonts/h5p.eot?-r12eb7'); + src:url('../fonts/h5p.eot?#iefix-r12eb7') format('embedded-opentype'), + url('../fonts/h5p.woff?-r12eb7') format('woff'), + url('../fonts/h5p.ttf?-r12eb7') format('truetype'), + url('../fonts/h5p.svg?-r12eb7#h5p_toolbar') format('svg'); + font-weight: normal; + font-style: normal; } -body.h5p-semi-fullscreen { +html.h5p-iframe, html.h5p-iframe > body { + font-family: Arial, Helvetica, sans-serif; + width: 100%; + height: 100%; + margin: 0; + padding: 0; +} +.h5p-semi-fullscreen, .h5p-fullscreen, html.h5p-iframe .h5p-container { overflow: hidden; } .h5p-content { - display: block; - width: auto; - height: auto; + position: relative; background: #fefefe; font-size: 16px; + line-height: 1.5em; } -div.h5p-semi-fullscreen { +html.h5p-iframe .h5p-content { + width: 100%; + height: 100%; +} +.h5p-container { + position: relative; + z-index: 1; +} +.h5p-container.h5p-semi-fullscreen { position: fixed; top: 0; left: 0; @@ -34,7 +54,11 @@ div.h5p-semi-fullscreen { .h5p-content-controls { margin: 0; text-align: right; - margin: 0; + position: relative; + z-index: 3; +} +.h5p-iframe > .h5p-fullscreen .h5p-content-controls { + display: none; } .h5p-content-controls > a:link, .h5p-content-controls > a:visited, a.h5p-disable-fullscreen:link, a.h5p-disable-fullscreen:visited { @@ -50,6 +74,7 @@ div.h5p-semi-fullscreen { .h5p-enable-fullscreen, .h5p-disable-fullscreen { display: inline-block; + line-height: 1.25em; padding: 6px 12px; border: 0.2em solid #fff; border-radius: 0.4em; @@ -86,6 +111,7 @@ div.h5p-semi-fullscreen { background: -ms-linear-gradient(top, rgba(4,104,206,1) 0%,rgba(100,152,254,1) 100%); /* IE10+ */ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#0468ce', endColorstr='#6498fe',GradientType=0 ); /* IE6-9 */ } + div.h5p-fullscreen { width: 100%; height: 100%; @@ -110,16 +136,13 @@ div.h5p-fullscreen { left: 0; z-index: 100; } -.h5p-iframe-wrapper .buttons { - text-align: right; -} .h5p-iframe-wrapper.h5p-semi-fullscreen .buttons { position: absolute; top: 0; right: 0; z-index: 20; } -.h5p-iframe-wrapper .h5p-iframe { +.h5p-iframe-wrapper iframe.h5p-iframe { width: 100%; height: 100%; z-index: 10; @@ -156,16 +179,176 @@ div.h5p-fullscreen { cursor: pointer; } -html.h5p-fullscreen, body.h5p-fullscreen { - height: 100%; +.h5p-actions { overflow: hidden; + list-style: none; + padding: 4px; + margin: 0.25em 0; + position: relative; + z-index: 2; + font-size: 12px; } -.h5p-fullscreen .h5p-content-controls { +.h5p-fullscreen .h5p-actions, .h5p-semi-fullscreen .h5p-actions { display: none; } -body.h5p-iframe-content div.h5p-content { +.h5p-actions > .h5p-button { + float: left; + cursor: pointer; + margin: 0 1.5em 0 0; + background: none; + padding: 0; + vertical-align: top; + color: #999; + text-decoration: none; + outline: none; +} +.h5p-actions > .h5p-button:hover { + color: #666; +} +.h5p-actions > .h5p-button:before { + font-family: H5PFontAwesome4; + font-size: 1em; + padding-right: 0.3em; +} +.h5p-actions > .h5p-button.h5p-export:before { + content: "\f019"; +} +.h5p-actions > .h5p-button.h5p-copyrights:before { + font-family: H5P; + content: "\e668"; + font-size: 1.7em; + padding-right: 0; + vertical-align: bottom; +} +.h5p-actions > .h5p-button.h5p-embed:before { + content: "\f121"; +} +.h5p-actions .h5p-link { + float: right; + margin-right: 0; + font-size: 2.0em; overflow: hidden; + color: #999; + text-decoration: none; + outline: none; } -body.h5p-iframe-content.h5p-fullscreen div.h5p-content { +.h5p-actions .h5p-link:before { + font-family: H5P; + content: "\e667"; +} +.h5p-popup-dialog { + position: absolute; + top: 0; + left: 0; + width: 100%; height: 100%; + z-index: 100; + padding: 2em; + box-sizing: border-box; + -moz-box-sizing: border-box; +/* background: rgba(50,50,50,0.5);*/ + opacity: 0; + -webkit-transition: opacity 0.2s; + -moz-transition: opacity 0.2s; + -o-transition: opacity 0.2s; + transition: opacity 0.2s; +} +.h5p-popup-dialog.h5p-open { + opacity: 1; +} +.h5p-popup-dialog .h5p-inner { + box-sizing: border-box; + box-shadow: 0 0 2em #000; + background: #fff; + height: 90%; + max-height: 100%; + padding: 0.75em; + position: relative; +} +.h5p-popup-dialog .h5p-inner > h2 { + font-size: 1.5em; + margin: 0.25em 0; + position: absolute; +} +.h5p-embed-dialog .h5p-inner { + width: 50%; + left: 25%; + top: 25%; + height: auto; +} +.h5p-embed-dialog .h5p-embed-code-container { + width: 90%; + padding: .3em; + min-height: 10em; + resize: none; + outline: none; +} +.h5p-popup-dialog .h5p-scroll-content { + border-top: 2.75em solid transparent; + box-sizing: border-box; + -moz-box-sizing: border-box; + height: 100%; + overflow: auto; + overflow-x: hidden; + overflow-y: auto; +} +.h5p-popup-dialog .h5p-scroll-content::-webkit-scrollbar { + width: 8px; +} +.h5p-popup-dialog .h5p-scroll-content::-webkit-scrollbar-track { + background: #e0e0e0; +} +.h5p-popup-dialog .h5p-scroll-content::-webkit-scrollbar-thumb { + box-shadow: 0 0 10px #000 inset; + border-radius: 4px; +} +.h5p-popup-dialog .h5p-close { + cursor: pointer; + outline:none +} +.h5p-popup-dialog .h5p-close:after { + font-family: H5PFontAwesome4; + content: "\f00d"; + font-size: 2em; + position: absolute; + right: 0.5em; + top: 0.5em; + cursor: pointer; + -webkit-transition: -webkit-transform 0.2s; + -moz-transition: -moz-transform 0.2s; + -o-transition: -o-transform 0.2s; + transition: transform 0.2s; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; +} +.h5p-popup-dialog .h5p-close:hover:after { + -webkit-transform: scale(1.1, 1.1); + -moz-transform: scale(1.1, 1.1); + -ms-transform: scale(1.1, 1.1); + -o-transform: scale(1.1, 1.1); + transform: scale(1.1, 1.1); +} +.h5p-poopup-dialog h2 { + margin: 0.25em 0 0.5em; +} +.h5p-popup-dialog h3 { + margin: 0.75em 0 0.25em; +} +.h5p-popup-dialog dl { + margin: 0.25em 0 0.75em; +} +.h5p-popup-dialog dt { + float: left; + margin: 0 0.75em 0 0; +} +.h5p-popup-dialog dt:after { + content: ':'; +} +.h5p-popup-dialog dd { + margin: 0; +} +.h5p-content-copyrights { + border-left: 0.25em solid #d0d0d0; + margin-left: 0.25em; + padding-left: 0.25em; }