소스 검색

copy from a local branch (initial commit)

tags/v1.0
부모
커밋
9e0a0e04a0
64개의 변경된 파일8400개의 추가작업 그리고 0개의 파일을 삭제
  1. +9
    -0
      .gitignore
  2. +10
    -0
      .htaccess
  3. +14
    -0
      .rsyncignore
  4. +33
    -0
      app/controller/admin.php
  5. +374
    -0
      app/controller/page.php
  6. +345
    -0
      app/controller/page.php.orig
  7. +83
    -0
      app/controller/user.php
  8. +45
    -0
      app/modules/cachedimage.php
  9. +239
    -0
      app/modules/cconcert.php
  10. +71
    -0
      app/modules/cevent.php
  11. +84
    -0
      app/modules/clocation.php
  12. +59
    -0
      app/modules/cmember.php
  13. +64
    -0
      app/modules/cmultiple.php
  14. +123
    -0
      app/modules/contenttype.php
  15. +482
    -0
      app/modules/filesinfolders.php
  16. +446
    -0
      app/modules/filesinfolders.php.orig
  17. +57
    -0
      app/modules/ography.php
  18. +55
    -0
      app/modules/pictures.php
  19. +175
    -0
      app/modules/tableofcontents.php
  20. +217
    -0
      app/modules/toc.php
  21. +1685
    -0
      app/parsedown.php
  22. +1
    -0
      app/views/borderless.html
  23. +7
    -0
      app/views/header.htm
  24. +35
    -0
      app/views/login.htm
  25. +4
    -0
      app/views/maincontent.htm
  26. +9
    -0
      app/views/nav-external.htm
  27. +34
    -0
      app/views/navigation.htm
  28. +37
    -0
      app/views/register.htm
  29. +5
    -0
      app/views/sidebar.htm
  30. +25
    -0
      app/views/tpl/image-with-links.html
  31. +69
    -0
      app/views/tpl/index.html
  32. +10
    -0
      composer.json
  33. +62
    -0
      deploy
  34. +178
    -0
      index.php
  35. +45
    -0
      main.cfg
  36. +8
    -0
      rsc/featherlight.min.css
  37. +8
    -0
      rsc/featherlight.min.js
  38. +508
    -0
      rsc/featherlightREADME.md
  39. BIN
      rsc/img/back01.JPG
  40. BIN
      rsc/img/default.png
  41. +2
    -0
      rsc/jquery-3.3.1.min.js
  42. +6
    -0
      rsc/sass/_admin.scss
  43. +18
    -0
      rsc/sass/_colours.scss
  44. +7
    -0
      rsc/sass/_design.scss
  45. +13
    -0
      rsc/sass/_dev.scss
  46. +77
    -0
      rsc/sass/_extras.scss
  47. +18
    -0
      rsc/sass/_fonts.scss
  48. +130
    -0
      rsc/sass/_structure.scss
  49. +13
    -0
      rsc/sass/_susy-prefix.scss
  50. +5
    -0
      rsc/sass/_susy.scss
  51. +53
    -0
      rsc/sass/_version.scss
  52. +40
    -0
      rsc/sass/main.scss
  53. +318
    -0
      rsc/sass/susy/_api.scss
  54. +261
    -0
      rsc/sass/susy/_normalize.scss
  55. +163
    -0
      rsc/sass/susy/_parse.scss
  56. +329
    -0
      rsc/sass/susy/_settings.scss
  57. +441
    -0
      rsc/sass/susy/_su-math.scss
  58. +213
    -0
      rsc/sass/susy/_su-validate.scss
  59. +191
    -0
      rsc/sass/susy/_syntax-helpers.scss
  60. +56
    -0
      rsc/sass/susy/_unprefix.scss
  61. +167
    -0
      rsc/sass/susy/_utilities.scss
  62. +31
    -0
      rsc/script.js
  63. +126
    -0
      rsc/style.css
  64. +7
    -0
      rsc/style.css.map

+ 9
- 0
.gitignore 파일 보기

@@ -0,0 +1,9 @@
*~
*.directory
lib
composer.lock
rsc/.sass-cache
tmp
*.tar.gz
.directory
content/

+ 10
- 0
.htaccess 파일 보기

@@ -0,0 +1,10 @@
RewriteEngine On

RewriteRule ^(app|dict|ns|lib)\/|\.cfg$ - [R=404]

RewriteCond %{REQUEST_FILENAME} !-l
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* index.php [L,QSA]
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]

+ 14
- 0
.rsyncignore 파일 보기

@@ -0,0 +1,14 @@
.git
.gitignore
.rsyncignore
*~
**.directory
tmp/
rsc/.sass-cache/
rsc/img_display/
stats/
content/
*.tar.gz
.directory

DE.backup/

+ 33
- 0
app/controller/admin.php 파일 보기

@@ -0,0 +1,33 @@
<?php

namespace Controller;

class Admin {

function __construct() {

}

function menu() {
return '<a class="action" href="#">clear cache</a>';
}
function index() {
// return $this->menu();
return $this->clear_cache();
}

function clear_cache() {
$CI = new \Modules\CachedImage("rsc/img/default.png");
$cache_dir = $CI->cache_dir;

$ls = scandir($cache_dir);
$count = 0;
foreach($ls as $file) {
if(!strncmp(".",$file,1)) continue;
unlink($cache_dir."".$file);
$count++;
}
return "erased $count files<br>";
}
}

+ 374
- 0
app/controller/page.php 파일 보기

@@ -0,0 +1,374 @@
<?php

namespace Controller;

class Page {

public static $keyword = "page"; // variable in URL (query string)

function index(\Base $f3, $params) {
/////////////////////////////
// what page are we watching?
$page = $params[self::$keyword];
$f3->set('page', $page);
$f3->set('bodyClass',"page-$page");
switch ($page) {
case 'home':
default:
$f3->mset(array(
'template'=>'tpl/index.html',
'templateContent'=>'maincontent.html'
));
self::folder2();
break;
}
}


////////////////
// interfaces //
////////////////
function home(\Base $f3) {
self::index($f3, array('page'=>'home'));
}
function secondLevel(\Base $f3,$params) {
self::index($f3, array('page'=>$params['level1']."/".$params['level2']));
}
function thirdLevel(\Base $f3,$params) {
self::index($f3, array('page'=>$params['level1']."/".$params['level2']."/".$params['level3']));
}
function fourthLevel(\Base $f3,$params) {
self::index($f3, array('page'=>$params['level1']."/".$params['level2']."/".$params['level3']."/".$params['level4']));
}


//////////////////
// main program //
//////////////////
function folder2() {
$f3 = \Base::instance();
$page = $f3->get('page');
$f3->set('current_page_folder', $f3->get('CONTENT').$page."/");

$folder = new \Modules\FilesInFolders(
$f3->get('current_page_folder'),
array(
'content'=>array(
'secondary'=>'secondary',
'zzz'=>'hidden',
'unpublish'=>'hidden'
),
'keyfiles'=>array(
'banner'=>array(
'until'=>$f3->get("CONTENT"),
'type'=>'pic'
),
'background'=>array(
'until'=>$f3->get("CONTENT"),
'type'=>'pic'
),
'colors'=>array(
'until'=>$f3->get("CONTENT"),
'type'=>'txt'
)
)
)
);

$folder->prepare_files();
if($folder->extras['colors']) {
$folder->structs['default']['txt']['colors']=$folder->extras['colors'];
}
$folder->fill_content();
foreach($f3->get('siteColors') as $k=>$v){
$f3->set($k,
array_key_exists($k,$folder->config)
? $folder->config[$k]
: $v
);
}
$f3->set('hasBanner',
array_key_exists('includeBanner',$folder->config)
? $folder->config['includeBanner']
: $f3->get('templateVars.includeBanner')
);

if ($f3->get('hasBanner')) {
$banner = new \Modules\CachedImage($folder->extras['banner']);
$f3->set('banner',$banner->get_src(2000));
}
if ($folder->extras['background']
&& !$folder->config['supressBackground']) {
$background = new \Modules\CachedImage($folder->extras['background']);
$f3->set('backgroundImage',$background->get_src(3000));
}
// $f3->set('background',$background);
$f3->set('content', implode("\n", $folder->content['default']));
$f3->set('secondary_content', implode("\n", $folder->content['secondary']));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
// function folder() {
// $f3 = \Base::instance();
// $page = $f3->get('page');
// $folder = $f3->get("CONTENT").$page."/";
//
// $EXT=array(
// 'pic'=>array( 'jpg', 'jpeg', 'png', 'svg' ),
// 'tpl'=>array( 'html', 'htm', 'tpl' ),
// 'txt'=>array( 'txt', 'text', 'md' ),
// 'csv'=>array( 'csv' )
// );
//
// $background = "";
// $banner = "";
// $c1 = array(
// // 'background'=>'',
// 'pic'=>array(),
// 'txt'=>array(),
// 'tpl'=>array(),
// 'csv'=>array()
// );
// $c2 = array(
// // 'background'=>'',
// 'pic'=>array(),
// 'txt'=>array(),
// 'tpl'=>array(),
// 'csv'=>array()
// );
// $primary = array();
// $secondary = array();
//
//
// // collect contents of folder into an organised array
// if(!is_dir($folder)) $folder=$f3->get("CONTENT")."../404/";
// $ls = scandir($folder);
// foreach ($ls as $key=>$f) {
// if(!strncmp($f,'.',1)) continue; // ignore hidden files
// $ex=explode(".", $f);
// $ext=end($ex);
// if(in_array(strtolower($ext), $EXT['pic'])) {
// if($ex[0]=='background') {
// $background=pic_cache($folder.$f,836);
// } elseif($ex[0]=='banner') {
// $banner=$folder.$f;
// } elseif($ex[0]=='secondary') {
// $c2['pic'][$ex[1]] = $folder.$f;
// } else {
// $c1['pic'][$ex[0]] = $folder.$f;
// }
// } elseif(in_array(strtolower($ext), $EXT['txt'])) {
// if($ex[0]=='secondary') {
// $c2['txt'][$ex[1]] = $folder.$f;
// } else {
// $c1['txt'][$ex[0]] = $folder.$f;
// }
// } elseif(in_array(strtolower($ext), $EXT['tpl'])) {
// if($ex[0]=='secondary') {
// $c2['tpl'][$ex[1]] = $folder.$f;
// } else {
// $c1['tpl'][$ex[0]] = $folder.$f;
// }
// } elseif (in_array(strtolower($ext), $EXT['csv'])) {
// if($ex[0]=='secondary') {
// $c2['csv'][$ex[1]] = $folder.$f;
// } else {
// $c1['csv'][$ex[0]] = $folder.$f;
// }
// } else { }
// }
//
// if(!$banner) {
// $banner = self::search_up("banner", array($folder,$f3->get("CONTENT")), $EXT['pic']);
// }
//
// // compose elements based on the organised array
// $md = new \Parsedown();
// foreach($c1['txt'] as $key=>$file) {
// $str = file_get_contents($file);
// $str = self::linkify($str);
// $str = self::get_config_from_content($str);
// $str = $md->text($str);
// $str = sprintf("<div class='post'>%s</div>", $str);
// $primary[$key] = sprintf("<div class=\"item %s %s\">%s</div>", $page,$key,$str);
// }
// foreach($c1['tpl'] as $key=>$file) {
// $str = file_get_contents($file);
// $str = \Template::instance()->render($file);
// $str = self::linkify($str);
// if(array_key_exists($key,$c1['pic'])) {
// $str = sprintf("%s\n%s",
// \Modules\Pictures::makeImageLink($c1['pic'][$key]),
// $str);
// unset($c1['pic'][$key]);
// }
// $primary[$key] = sprintf("<div class=\"item %s %s\">%s</div>", $page ,$key,$str);
// }
// foreach($c1['pic'] as $key=>$file) {
// $primary[$key] = sprintf("<img src=\"$file\" />");
// }
// foreach($c1['csv'] as $key=>$file) {
// $csv = new \Modules\Ography($file,TRUE);
// // $c1sv->filter_field("file");
// $str="<table>";
// foreach($csv->entries as $entry) {
// $tmp="";
// foreach($entry as $key=>$value) {
// $tmp .= sprintf("<td class=\"%s\">%s</td>", $key, $value);
// }
// $str .= sprintf("<tr>%s</tr>",$tmp);
// }
// $str.="</table>";
// $primary[$key] = $str;
// }
//
// foreach($c2['txt'] as $key=>$file) {
// $str = file_get_contents($file);
// $str = self::linkify($str);
// $str = self::get_config_from_content($str);
// $str = $md->text($str);
// $str = sprintf("<div class='post'>%s</div>", $str);
// $secondary[$key] = sprintf("<div class=\"item %s %s\">%s</div>", $page,$key,$str);
// }
// foreach($c2['tpl'] as $key=>$file) {
// $str = file_get_contents($file);
// $str = \Template::instance()->render($file);
// $str = self::linkify($str);
// if(array_key_exists($key,$c2['pic'])) {
// $str = sprintf("%s\n%s",
// \Modules\Pictures::makeImageLink($c2['pic'][$key]),
// $str);
// unset($c2['pic'][$key]);
// }
// $secondary[$key] = sprintf("<div class=\"item %s %s\">%s</div>", $page ,$key,$str);
// }
// foreach($c2['pic'] as $key=>$file) {
// $secondary[$key] = sprintf("<img src=\"$file\" />");
// }
// foreach($c2['csv'] as $key=>$file) {
// $csv = new \Modules\Ography($file,TRUE);
// // $c2sv->filter_field("file");
// $str="<table>";
// foreach($csv->entries as $entry) {
// $tmp="";
// foreach($entry as $key=>$value) {
// $tmp .= sprintf("<td class=\"%s\">%s</td>", $key, $value);
// }
// $str .= sprintf("<tr>%s</tr>",$tmp);
// }
// $str.="</table>";
// $secondary[$key] = $str;
// }
//
// // set template variables
// $f3->set('banner',$banner);
// $f3->set('background',$background);
// $f3->set('content', implode("\n", $primary));
// $f3->set('secondary_content', implode("\n", $secondary));
// }

////////////////
// recursions //
////////////////
function search_up($key,$paths,$ext) {
$return = "";
if(count($paths) == 2) {
$current = $paths[0];
$last_try = $paths[1];
$ls=scandir($current);
foreach($ls as $f) {
if(!strncmp($f,'.',1)) continue; // ignore hidden files
$ex=explode(".", $f);
if(in_array(strtolower(end($ex)),$ext)) {
if($ex[0]==$key) {
$return = $current.$f;
break;
}
}
}
}
if ($return) {
return $return;
} elseif($current == $last_try) {
return false;
} else {
$p = explode('/',$current);
array_pop($p);
array_pop($p);
return self::search_up($key,array(implode("/",$p)."/",$last_try),$ext);
}
}




///////////////////////
// Utility functions //
///////////////////////
function linkify($string) {
$pattern = "/\s@(\w+)[=]([\w,]+)\s/";
$count = 0;
$new = preg_replace_callback
($pattern,
function($m){
$f3 = \Base::instance();
return $f3->get('SITE_URL')
.$f3->alias($m[1],self::$keyword."=".$m[2])
;},
$string);
return $new;
}
function get_config_from_content($string) {
$f3 = \Base::instance();
$f = 0;
$pattern = "/#\+(\w+):\s?(.*)/";
$f = preg_match_all($pattern, $string,$matches,PREG_PATTERN_ORDER);
foreach($matches[0] as $match) {
$string = str_replace($match,"",$string);
}
foreach($matches[1] as $key => $match) {
$f3->set('content_config_'.$match,$matches[2][$key]);
}
return $string;
}
public static function check_folder_for_backgroundimage($folder){
$EXT=array(
'pic'=>array( 'jpg', 'jpeg', 'png' ),
'tpl'=>array( 'html', 'htm', 'tpl' ),
'txt'=>array( 'txt', 'text', 'md' )
);
$ls = scandir($folder);
$background = false;
foreach ($ls as $key=>$f) {
if(!strncmp($f,'.',1)) continue;
$ex=explode(".", $f);
$ext=array_pop($ex);
if(in_array(strtolower($ext), $EXT['pic'])) {
if($ex[0]=='background') {
$background=pic_cache($folder.$f,836);
break;
}
}
}
return $background;
}
}

+ 345
- 0
app/controller/page.php.orig 파일 보기

@@ -0,0 +1,345 @@
<?php

namespace Controller;

class Page {

public static $keyword = "page"; // variable in URL (query string)

function index(\Base $f3, $params) {
/////////////////////////////
// what page are we watching?
$page = $params[self::$keyword];
$f3->set('page', $page);
$f3->set('bodyClass',"page-$page");
switch ($page) {
case 'home':
default:
$f3->mset(array(
'template'=>'tpl/index.html',
'templateContent'=>'maincontent.html'
));
self::folder2();
break;
}
}


////////////////
// interfaces //
////////////////
function home() {
$f3 = \Base::instance();
self::index($f3, array('page'=>'home'));
}
function secondLevel(\Base $f3,$params) {
self::index($f3, array('page'=>$params['level1']."/".$params['level2']));
}
function thirdLevel(\Base $f3,$params) {
self::index($f3, array('page'=>$params['level1']."/".$params['level2']."/".$params['level3']));
}
function fourthLevel(\Base $f3,$params) {
self::index($f3, array('page'=>$params['level1']."/".$params['level2']."/".$params['level3']."/".$params['level4']));
}


//////////////////
// main program //
//////////////////
function folder2() {
$f3 = \Base::instance();
$page = $f3->get('page');
$f3->set('current_page_folder', $f3->get('CONTENT').$page."/");

$folder = new \Modules\FilesInFolders(
$f3->get('current_page_folder'),
array(
'content'=>array(
'secondary'=>'secondary',
'zzz'=>'hidden',
'unpublish'=>'hidden'
),
'keyfiles'=>array(
'banner'=>array(
'until'=>$f3->get("CONTENT"),
'type'=>'pic'
))
)
);

$folder->prepare_files();
$folder->fill_content();
// set template variables
$banner = new \Modules\CachedImage($folder->extras['banner']);
$f3->set('banner',$banner->get_src(2000));
// $f3->set('background',$background);
$f3->set('content', implode("\n", $folder->content['default']));
$f3->set('secondary_content', implode("\n", $folder->content['secondary']));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////
function folder() {
$f3 = \Base::instance();
$page = $f3->get('page');
$folder = $f3->get("CONTENT").$page."/";

$EXT=array(
'pic'=>array( 'jpg', 'jpeg', 'png', 'svg' ),
'tpl'=>array( 'html', 'htm', 'tpl' ),
'txt'=>array( 'txt', 'text', 'md' ),
'csv'=>array( 'csv' )
);
$background = "";
$banner = "";
$c1 = array(
// 'background'=>'',
'pic'=>array(),
'txt'=>array(),
'tpl'=>array(),
'csv'=>array()
);
$c2 = array(
// 'background'=>'',
'pic'=>array(),
'txt'=>array(),
'tpl'=>array(),
'csv'=>array()
);
$primary = array();
$secondary = array();

// collect contents of folder into an organised array
if(!is_dir($folder)) $folder=$f3->get("CONTENT")."../404/";
$ls = scandir($folder);
foreach ($ls as $key=>$f) {
if(!strncmp($f,'.',1)) continue; // ignore hidden files
$ex=explode(".", $f);
$ext=end($ex);
if(in_array(strtolower($ext), $EXT['pic'])) {
if($ex[0]=='background') {
$background=pic_cache($folder.$f,836);
} elseif($ex[0]=='banner') {
$banner=$folder.$f;
} elseif($ex[0]=='secondary') {
$c2['pic'][$ex[1]] = $folder.$f;
} else {
$c1['pic'][$ex[0]] = $folder.$f;
}
} elseif(in_array(strtolower($ext), $EXT['txt'])) {
if($ex[0]=='secondary') {
$c2['txt'][$ex[1]] = $folder.$f;
} else {
$c1['txt'][$ex[0]] = $folder.$f;
}
} elseif(in_array(strtolower($ext), $EXT['tpl'])) {
if($ex[0]=='secondary') {
$c2['tpl'][$ex[1]] = $folder.$f;
} else {
$c1['tpl'][$ex[0]] = $folder.$f;
}
} elseif (in_array(strtolower($ext), $EXT['csv'])) {
if($ex[0]=='secondary') {
$c2['csv'][$ex[1]] = $folder.$f;
} else {
$c1['csv'][$ex[0]] = $folder.$f;
}
} else { }
}

if(!$banner) {
$banner = self::search_up("banner", array($folder,$f3->get("CONTENT")), $EXT['pic']);
}
// compose elements based on the organised array
$md = new \Parsedown();
foreach($c1['txt'] as $key=>$file) {
$str = file_get_contents($file);
$str = self::linkify($str);
$str = self::get_config_from_content($str);
$str = $md->text($str);
$str = sprintf("<div class='post'>%s</div>", $str);
$primary[$key] = sprintf("<div class=\"item %s %s\">%s</div>", $page,$key,$str);
}
foreach($c1['tpl'] as $key=>$file) {
$str = file_get_contents($file);
$str = \Template::instance()->render($file);
$str = self::linkify($str);
if(array_key_exists($key,$c1['pic'])) {
$str = sprintf("%s\n%s",
\Modules\Pictures::makeImageLink($c1['pic'][$key]),
$str);
unset($c1['pic'][$key]);
}
$primary[$key] = sprintf("<div class=\"item %s %s\">%s</div>", $page ,$key,$str);
}
foreach($c1['pic'] as $key=>$file) {
$primary[$key] = sprintf("<img src=\"$file\" />");
}
foreach($c1['csv'] as $key=>$file) {
$csv = new \Modules\Ography($file,TRUE);
// $c1sv->filter_field("file");
$str="<table>";
foreach($csv->entries as $entry) {
$tmp="";
foreach($entry as $key=>$value) {
$tmp .= sprintf("<td class=\"%s\">%s</td>", $key, $value);
}
$str .= sprintf("<tr>%s</tr>",$tmp);
}
$str.="</table>";
$primary[$key] = $str;
}

foreach($c2['txt'] as $key=>$file) {
$str = file_get_contents($file);
$str = self::linkify($str);
$str = self::get_config_from_content($str);
$str = $md->text($str);
$str = sprintf("<div class='post'>%s</div>", $str);
$secondary[$key] = sprintf("<div class=\"item %s %s\">%s</div>", $page,$key,$str);
}
foreach($c2['tpl'] as $key=>$file) {
$str = file_get_contents($file);
$str = \Template::instance()->render($file);
$str = self::linkify($str);
if(array_key_exists($key,$c2['pic'])) {
$str = sprintf("%s\n%s",
\Modules\Pictures::makeImageLink($c2['pic'][$key]),
$str);
unset($c2['pic'][$key]);
}
$secondary[$key] = sprintf("<div class=\"item %s %s\">%s</div>", $page ,$key,$str);
}
foreach($c2['pic'] as $key=>$file) {
$secondary[$key] = sprintf("<img src=\"$file\" />");
}
foreach($c2['csv'] as $key=>$file) {
$csv = new \Modules\Ography($file,TRUE);
// $c2sv->filter_field("file");
$str="<table>";
foreach($csv->entries as $entry) {
$tmp="";
foreach($entry as $key=>$value) {
$tmp .= sprintf("<td class=\"%s\">%s</td>", $key, $value);
}
$str .= sprintf("<tr>%s</tr>",$tmp);
}
$str.="</table>";
$secondary[$key] = $str;
}

// set template variables
$f3->set('banner',$banner);
$f3->set('background',$background);
$f3->set('content', implode("\n", $primary));
$f3->set('secondary_content', implode("\n", $secondary));
}

////////////////
// recursions //
////////////////
function search_up($key,$paths,$ext) {
$return = "";
if(count($paths) == 2) {
$current = $paths[0];
$last_try = $paths[1];
$ls=scandir($current);
foreach($ls as $f) {
if(!strncmp($f,'.',1)) continue; // ignore hidden files
$ex=explode(".", $f);
if(in_array(strtolower(end($ex)),$ext)) {
if($ex[0]==$key) {
$return = $current.$f;
break;
}
}
}
}
if ($return) {
return $return;
} elseif($current == $last_try) {
return false;
} else {
$p = explode('/',$current);
array_pop($p);
array_pop($p);
return self::search_up($key,array(implode("/",$p)."/",$last_try),$ext);
}
}




///////////////////////
// Utility functions //
///////////////////////
function linkify($string) {
$pattern = "/\s@(\w+)[=]([\w,]+)\s/";
$count = 0;
$new = preg_replace_callback
($pattern,
function($m){
$f3 = \Base::instance();
return $f3->get('SITE_URL')
.$f3->alias($m[1],self::$keyword."=".$m[2])
;},
$string);
return $new;
}
function get_config_from_content($string) {
$f3 = \Base::instance();
$f = 0;
$pattern = "/#\+(\w+):\s?(.*)/";
$f = preg_match_all($pattern, $string,$matches,PREG_PATTERN_ORDER);
foreach($matches[0] as $match) {
$string = str_replace($match,"",$string);
}
foreach($matches[1] as $key => $match) {
$f3->set('content_config_'.$match,$matches[2][$key]);
}
return $string;
}
public static function check_folder_for_backgroundimage($folder){
$EXT=array(
'pic'=>array( 'jpg', 'jpeg', 'png' ),
'tpl'=>array( 'html', 'htm', 'tpl' ),
'txt'=>array( 'txt', 'text', 'md' )
);
$ls = scandir($folder);
$background = false;
foreach ($ls as $key=>$f) {
if(!strncmp($f,'.',1)) continue;
$ex=explode(".", $f);
$ext=array_pop($ex);
if(in_array(strtolower($ext), $EXT['pic'])) {
if($ex[0]=='background') {
$background=pic_cache($folder.$f,836);
break;
}
}
}
return $background;
}
}

+ 83
- 0
app/controller/user.php 파일 보기

@@ -0,0 +1,83 @@
<?php

namespace Controller;

class User {

private $db;
function __construct() {
// $this->db = new \DB\SQL('sqlite:data/login.sqlite');

// $this->db->exec("CREATE TABLE IF NOT EXISTS users
// (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
// email varchar(128) NULL DEFAULT 'anonymous',
// password varchar(128) NULL,
// balance REAL
// )"
// );

$this->db = new \DB\Jig('data/');
}

function login() {
$f3 = \Base::instance();
$audit = \Audit::instance();
$username = $f3->get('GET.uname');
$password = $f3->get('GET.psw');

$mapper = new \DB\Jig\Mapper($this->db, 'users');
$auth = new \Auth(
$mapper,
array('id' => 'email', 'pw' => 'password')
);


if ( $auth->login($username,$password) ) {
$mapper->load(array("@email = ?", $username));
new \Session();
$f3->mset(array(
'SESSION.login.email' => $mapper->email,
'SESSION.login.balance' => 100
));

$f3->reroute('/dashboard');
} else {
echo "no success";
}

$f3->set('template', "login.htm");
}

function register() {
$f3 = \Base::instance();
$audit = \Audit::instance();
//var_dump($f3->get("GET"));

if ( $f3->get('GET.psw') == $f3->get('GET.psw-repeat')) {
$password = $f3->get('GET.psw');
}
$email = $f3->get('GET.email');

if ( $password && $email) {
$mapper = new \DB\Jig\Mapper($this->db, 'users');
$mapper->email = $email;
$mapper->password = $password;
$mapper->save();

$f3->reroute('/login');
}
$f3->set('template', "register.htm");
}
}

+ 45
- 0
app/modules/cachedimage.php 파일 보기

@@ -0,0 +1,45 @@
<?php

namespace Modules;

class CachedImage {

public $original;
public $cache_dir = "rsc/img_display/";
public $default_width = 500;

function __construct($path) {
$this->original = $path;
}
function get_src($inwidth = 500) {
$f3 = \Base::instance();
if($this->is_image($this->original)) {
$info = pathinfo($this->original);
$fn = basename($this->original,'.'.$info['extension']);
$width = $inwidth ? $inwidth : $this->default_width;
$name = md5($this->original.$width);
$name = sprintf("%s_%s.jpg",$fn,$name);
$out = $this->cache_dir.$name;
//if(!file_exists($this->original)) { $this->original='rsc/img/default.png'; }
if(!file_exists($out)) {
$img1 = new \Image($this->original);
$img1->resize($width);
$f3->write($out,$img1->dump('jpeg',75));
}
} else {
$out = $this->original;
}
return $out;
}

function is_image($path) {
$ex = explode('.',$path);
$ext = array_pop($ex);
return in_array(strtolower($ext),array( 'jpg', 'jpeg', 'png' ));
}
}

+ 239
- 0
app/modules/cconcert.php 파일 보기

@@ -0,0 +1,239 @@
<?php

namespace Modules;

class CConcert extends ContentType {

public $keys = array(
'DATE' => 'date',
'TIME' => 'time',
'TITLE'=> 'title',
'DESCRIPTION' => 'description',
'DESCRIPTION2' => 'description2',
'LOCATION' => 'location',
'MARKER' => 'marker'
);
public $values;
protected $layout;
protected $layouts = array(
'toc' => 'view_in_toc',
'archive' => 'view_archive',
'archive_no_link' => 'view_archive_without_link',
'with_marker' => 'view_with_marker',
'short_reference' => 'only_name',
'header' => 'view_as_page_header',
'secondary' => 'rather_small',
'name_as_link' => 'view_name_as_link'
);
function __construct($keys,$config) {
parent::__construct($keys,$config);
}


function view_in_toc() {
$v = (object) $this->values;
$TS = strtotime($v->date);
$href = $this->href;
$date = sprintf("<span class=\"month\">%s</span>"
."<span class=\"day-of-month\">%s</span>",
$this->month_name(date('n',$TS)),
date('d', $TS)
);

$description = sprintf("<h3><a href=\"/%s\">%s</a></h3>"
."<span>%s</span>"
."<footer>%s, %s</footer>",
$href,
$v->title,
$v->description,
$this->week_day_name(date('N',$TS)),
$v->time
);

if (is_object($v->location)) {
$v->location->set_layout("humble_two_liner");
}
return sprintf("<div class=\"entry concert toc\">"
."<div class=\"date\">%s</div>"
."<div class=\"description\">%s</div>"
."<div class=\"location\">%s</div>"
."</div>",
$date,
$description,
$v->location
);
}
function view_with_marker() {
$v = (object) $this->values;
$TS = strtotime($v->date);
$href = $this->href;
$date = sprintf("<span class=\"month\">%s</span>"
."<span class=\"day-of-month\">%s</span>",
$this->month_name(date('n',$TS)),
date('d', $TS)
);

$description = sprintf("<h3><a href=\"/%s\">%s</a> <span class=\"marker\">%s</span></h3>"
."<span>%s</span>"
."<footer>%s, %s</footer>",
$href,
$v->title,
$v->marker,
$v->description,
$this->week_day_name(date('N',$TS)),
$v->time
);

if (is_object($v->location)) {
$v->location->set_layout("humble_two_liner");
}
return sprintf("<div class=\"entry marker\">"
."<div class=\"date\">%s</div>"
."<div class=\"description\">%s</div>"
."<div class=\"location\">%s</div>"
."</div>",
$date,
$description,
$v->location
);
}

function view_archive_without_link() {
$v = (object) $this->values;
$TS = strtotime($v->date);
$date = sprintf("<span class=\"month\">%s</span>"
."<span class=\"day-of-month\">%s</span>",
$this->month_name(date('n',$TS)),
date('d', $TS)
);

$description = sprintf("<h3>%s</h3>"
."<span>%s</span>"
."<footer>%s</footer>",
$v->title,
$v->description,
$v->description2
);

if (is_object($v->location)) {
$v->location->set_layout("humble_two_liner");
}
return sprintf("<div class=\"entry concert archive\">"
."<div class=\"date\">%s</div>"
."<div class=\"description\">%s</div>"
."<div class=\"location\">%s</div>"
."</div>",
$date,
$description,
$v->location
);
}
function view_archive() {
$v = (object) $this->values;
$TS = strtotime($v->date);
$date = sprintf("<span class=\"month\">%s</span>"
."<span class=\"day-of-month\">%s</span>",
$this->month_name(date('n',$TS)),
date('d', $TS)
);

$description = sprintf("<h3><a href=\"/%s\">%s</a></h3>"
."<span>%s</span>"
."<footer>%s</footer>",
$this->href,
$v->title,
$v->description,
$v->description2
);

if (is_object($v->location)) {
$v->location->set_layout("humble_two_liner");
}
return sprintf("<div class=\"entry concert archive\">"
."<div class=\"date\">%s</div>"
."<div class=\"description\">%s</div>"
."<div class=\"location\">%s</div>"
."</div>",
$date,
$description,
$v->location
);
}

function view_as_page_header() {
$v = (object) $this->values;
$TS = strtotime($v->date);

$time = sprintf("%s, %s - %s",
$this->week_day_name(date('N',$TS)),
date('d.m.y',$TS),
$v->time
);
if (is_object($v->location)) {
$v->location->set_layout('one_liner');
}
return sprintf("<div class=\"concert-info\">"
."<h1>%s</h1>"
."<h5>%s</h5>"
."<span class=\"time\">%s</span>"
."<span class=\"location\">%s</span>"
."</div>",
$v->title,
$v->description,
$time,
$v->location
);
}

function rather_small() {
$v = (object) $this->values;
$TS = strtotime($v->date);
$v->location->set_layout("town");
$town = strip_tags(sprintf("%s",$v->location));
$v->location->set_layout('collected_entry');

return sprintf("<div class=\"entry-small\">"
."<h6 class=\"date-and-town\">%s %s<span class=\"time-in-header\"> - %s</span></h6>"
."<span class=\"venue\">%s</span>"
."<h5 class=\"name\"><a href=\"/%s\">%s</a></h5>"
."</div>",
date("d.m.", $TS),
$town,
$v->time,
$v->location,
$this->href,
$v->title
);
}
function only_name() {
$v = (object) $this->values;
return sprintf("<h6><a href=\"/%s\">%s</a></h6>",
$this->href,
$v->title
);
}

function view_name_as_link() {
$v = (object) $this->values;
return sprintf("<a href=\"/%s\">%s</a>",
$this->href,
$v->title
);
}

}

+ 71
- 0
app/modules/cevent.php 파일 보기

@@ -0,0 +1,71 @@
<?php

namespace Modules;

class CEvent extends ContentType {

public $keys = array(
'DATE' => 'date',
'LEFT01' => 'left01',
'LEFT02' => 'left02',
'LEFT03' => 'left03',
'RIGHT01' => 'right01',
'RIGHT02' => 'right02',
'RIGHT03' => 'right03'
);
public $values;
protected $layout;
protected $layouts = array(
'toc' => 'view_in_toc',
);
function __construct($keys,$config) {
parent::__construct($keys,$config);
}


function view_in_toc() {
$v = (object) $this->values;
$TS = strtotime($v->date);
$href = $this->href;
$date = sprintf("<span class=\"month\">%s</span>"
."<span class=\"day-of-month\">%s</span>",
$this->month_name(date('n',$TS)),
date('d', $TS)
);

if (is_object($v->left02)) {
$v->left02->set_layout('name_as_link');
}
$left = sprintf("<h4>%s</h4>"
."<h3>%s</h3>"
."<footer>%s</footer>",
$v->left01,
$v->left02,
$v->left03
);

$right = sprintf("<h6>%s</h6>"
."<span class=\"right02\">%s</span><br>"
."<span class=\"right03\">%s</span>",
$v->right01,
$v->right02,
$v->right03
);
return sprintf("<div class=\"entry\">"
."<div class=\"date\">%s</div>"
."<div class=\"left\">%s</div>"
."<div class=\"right\">%s</div>"
."</div>",
$date,
$left,
$right
);
}

}

+ 84
- 0
app/modules/clocation.php 파일 보기

@@ -0,0 +1,84 @@
<?php

namespace Modules;

class CLocation extends ContentType {

public $keys = array(
'NAME' => 'name',
'TOWN' => 'town'
);
public $values;
protected $layout;
protected $layouts = array(
'toc' => 'view_in_toc',
'only_name' => 'view_only_name',
'humble_two_liner' => 'view_small_in_two_lines',
'one_liner' => 'view_all_in_one_line',
'collected_header' => 'only_town',
'collected_entry' => 'only_name_as_link',
'town' => 'town'
);
function __construct($keys,$config) {
parent::__construct($keys,$config);
}


function view_in_toc() {
$v = (object) $this->values;
return sprintf("<div class=\"location\"><a href=\"%s\"><h3>%s</h3></a><span>%s</span></div>",
$this->href,
$v->name,
$v->town
);
}
function view_only_name() {
$v = (object) $this->values;
return sprintf("<div class=\"location\"><a href=\"%s\"><span>%s</span></a></div>",
$this->href,
$v->name
);
}
function view_all_in_one_line() {
$v = (object) $this->values;
return sprintf("<div class=\"location\">"
."<span class=\"town\">%s</span>,&nbsp;"
."<span class=\"venue-name\"><a href=\"/%s\">%s</a></span>"
."</div>",
$v->town,
$this->href,
$v->name
);
}
function view_small_in_two_lines() {
$v = (object) $this->values;
return sprintf("<div class=\"location\">"
."<h6 class=\"town\">%s</h6>"
."<span class=\"venue-name\"><a href=\"/%s\">%s</a></span>"
."</div>",
$v->town,
$this->href,
$v->name
);
}

function only_name_as_link() {
$v = (object) $this->values;
return sprintf("<h5><a href=\"/%s\">%s</a></h5>",
$this->href,
$v->name
);
}
function only_town() {
$v = (object) $this->values;
return sprintf("<h2>%s</h2>",$v->town);
}
function town() {
$v = (object) $this->values;
return $v->town;
}

}

+ 59
- 0
app/modules/cmember.php 파일 보기

@@ -0,0 +1,59 @@
<?php

namespace Modules;

class CMember extends ContentType {

public $keys = array(
'NAME' => 'name',
'FIRSTNAME' => 'firstname',
'ROLE' => 'role'
);
public $values;
protected $layout;
protected $layouts = array(
'default' => 'simple',
'toc' => 'view_in_toc'
);
function __construct($keys,$config) {

parent::__construct($keys,$config);
}

function simple() {
return sprintf("sdsd");
}
function view_in_toc() {
$f3 = \Base::instance();
$v = (object) $this->values;

$name = $v->name;
if ($v->firstname) {
$name = $v->firstname." ".$name;
//$name .= ", ".$v->firstname;
}
$v->termine = new TOC(array(
'concerts',
'/spielplan/termine/',
''
),
$f3->get('CONTENT'),
'MEMBERS=@'.$this->id
);
//var_dump($v->test);
$v->termine->dispatch();
return sprintf("<h1><a href=\"/%s\">%s</a><span class=\"role\">%s</span></h1>%s<br>",
$this->href,
$name,
$v->role,
$v->termine
);
}

}

+ 64
- 0
app/modules/cmultiple.php 파일 보기

@@ -0,0 +1,64 @@
<?php

namespace Modules;

class CMultiple {

public $elements=array();
public $first_element="";
public $last_element="";
public $ids=array();
function __construct($elements,$IDs=false) {
$this->elements = $elements;
if (!$IDs) {
$keys=array_keys($this->elements);
foreach($this->elements as $k=>$v) {
$this->ids[] = $this->elements[$k]->id;
}
} else {
$this->ids = $IDs;
}
//foreach ($this->ids as $k=>$v) {
//var_dump($this->elements[$k]);
//$this->elements[$k]->id = $v;
//}
}


function __toString() {
return sprintf("<div>%s<div>%s</div>%s</div>",
$this->first_element,
implode("\n", $this->elements),
$this->last_element
);
}

function set_layout($new,$target="") {
switch ($target) {
case "first":
break;
case "last":
break;
default:
foreach($this->elements as $el) {
$el->set_layout($new);
}
break;
}
}

function set_first($element,$layout="") {
$this->first_element=$element;
if ($layout) {
$this->first_element->set_layout($layout);
}
}

function set_last($element,$layout="") {
$this->last_element=$element;
if ($layout) {
$this->last_element->set_layout($layout);
}
}
}

+ 123
- 0
app/modules/contenttype.php 파일 보기

@@ -0,0 +1,123 @@
<?php

namespace Modules;

class ContentType {

protected $href;
protected $config;
public $keys=array();
public $values=array();
protected $layout;
protected $layouts = array(
'default' => 'view_default'
);
public $id = "";
function __construct($keys,$config) {
$f3 = \Base::instance();
$available_layouts = array_keys($this->layouts);
$this->layout = $available_layouts[0];
$this->config = $config;

$this->href= $f3->get('SITE_URL').str_replace($f3->get('CONTENT'),"",$this->config['path']);

foreach ($this->keys as $k=>$v) {
$this->values[$v] = $keys[$k];
}

$ke = explode("/",$this->config['path']);
array_pop($ke);
$id = array_pop($ke);
$this->id = $id;
}

function __toString() {
return $this->{$this->layouts[$this->layout]}();
}

function set_layout($new) {
if(array_key_exists($new,$this->layouts)) {
$this->layout = $new;
//echo "changed layout to: ".$new."<br>";
}
}

function view_default() {
return "";
}

function month_name($n) {
$f3 = \Base::instance();
switch (strtolower($f3->get('LANG'))) {
case 'de':
$month_names = array('',
'Jan',
'Feb',
'Mrz',
'Apr',
'Mai',
'Jun',
'Jul',
'Aug',
'Sep',
'Okt',
'Nov',
'Dez'
);
return $month_names[$n];
break;
case 'en':
$month_names = array('',
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
);
return $month_names[$n];
break;
default:
return "";
}
}
function week_day_name($n) {
$f3 = \Base::instance();
switch (strtolower($f3->get('LANG'))) {
case 'de':
$wd_names = array('',
'Montag',
'Dienstag',
'Mittwoch',
'Donnerstag',
'Freitag',
'Samstag',
'Sonntag'
);
return $wd_names[$n];
break;
case 'en':
$wd_names = array('',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday'
);
return $wd_names[$n];
break;
default:
return "";
}
}

}

+ 482
- 0
app/modules/filesinfolders.php 파일 보기

@@ -0,0 +1,482 @@
<?php

namespace Modules;

class FilesInFolders {

private $folder;
public $content = array();
public $extras = array();
private $domains = array('default'=>'default');
private $keyfiles = array();
public $structs = array();
private $EXT=array(
'txt'=>array( 'txt', 'text', 'md' ),
'pic'=>array( 'jpg', 'jpeg', 'png', 'svg' ),
'tpl'=>array( 'html', 'htm', 'tpl' ),
'csv'=>array( 'csv' )
);
public $config = array();
private $state=array();

function __construct($folder,$conf=array()) {
$f3 = \Base::instance();
if(is_dir($folder)) { // are we given a valid path?
$this->folder = $folder;
} else { // and if not?
$this->folder = $f3->get('CONTENT')."./";
}
if(is_array($conf)) {
foreach($conf as $key=>$value) {
switch($key) {
case 'content':
if(is_array($value)) {
foreach($value as $k=>$v) {
$this->domains[$k] = $v;
}
}
break;
case 'keyfiles':
if(is_array($value)) {
foreach($value as $k=>$v) {
$this->keyfiles[$k] = $v;
}
}
break;
}
}
}
foreach($this->domains as $domain) {
$this->content[$domain] = array();
}
foreach($this->keyfiles as $keys=>$d) {
$this->extras[$keys] = "";
}
}


/////////////////////////////
// read folder into struct //
/////////////////////////////
function prepare_files() {

foreach ($this->domains as $k=>$v) {
foreach($this->EXT as $cat=>$endings) {
$this->structs[$k][$cat]=array();
}
}
$ls = scandir($this->folder);
foreach ($ls as $k=>$f) {
if (!strncmp($f,'.',1)) continue; // ignore hidden files
$ex=explode(".", $f);
$ext=strtolower(end($ex));
if (array_key_exists($ex[0],$this->domains)) {
$domain_key=$ex[0];
$sort_key=1;
} elseif (array_key_exists($ex[0], $this->keyfiles)) {
if(in_array($ext,$this->EXT[$this->keyfiles[$ex[0]]['type']])) {
$this->extras[$ex[0]]=$this->folder.$f;
continue;
}
$domain_key='default';
$sort_key=0;
} else {
$domain_key='default';
$sort_key=0;
}
foreach ($this->EXT as $cat=>$endings) {
if (in_array($ext, $endings)) {
$this->structs[$domain_key][$cat][$ex[$sort_key]] = $this->folder.$f;
break;
}
}
}

foreach($this->keyfiles as $key=>$param) {
if(!$this->extras[$key]) {
$this->extras[$key] = self::search_up(
$key,
array($this->folder,$param['until']),
$this->EXT[$param['type']]
);
}
}
}

///////////////////////////////////////
// prepare content as per the struct //
///////////////////////////////////////
function fill_content() {
$md = new \Parsedown();
foreach($this->domains as $domain_key=>$domain) {
$this->state['current_domain'] = $domain_key;
foreach($this->structs[$domain_key]['txt'] as $key=>$file) {
$str = $this->read_textfile($file);
$str = self::content_element_dispatcher($str);
$str = $md->text($str);
$str = sprintf("<div class='post'>%s</div>", $str);
$this->content[$domain][$key] = sprintf(
"<div class=\"item %s %s\">%s</div>",
$page,
$key,
$str
);
}
foreach($this->structs[$domain_key]['tpl'] as $key=>$file) {
$str = file_get_contents($file);
$str = \Template::instance()->render($file);
$str = self::linkify($str);
$this->content[$domain][$key] = sprintf(
"<div class=\"item %s %s\">%s</div>",
$page,
$key,
$str
);
}
foreach($this->structs[$domain_key]['pic'] as $key=>$file) {
$this->content[$domain][$key] = sprintf(
"<img class=\"direct\" src=\"/$file\" />"
);
}
foreach($this->structs[$domain_key]['csv'] as $key=>$file) {
$csv = new \Modules\Ography($file,TRUE);
$str="<table>";
foreach($csv->entries as $entry) {
$tmp="";
foreach($entry as $key=>$value) {
$tmp .= sprintf("<td class=\"%s\">%s</td>", $key, $value);
}
$str .= sprintf("<tr>%s</tr>",$tmp);
}
$str.="</table>";
$this->content[$domain][$key] = $str;
}
}
}

//////////////////////
// read config data //
//////////////////////
function read_config($domain=false) {
foreach ($this->domains as $source=>$destination) {
if (is_string($domain)) {
if ($source != $domain) { continue; }
} elseif (is_array($domain)) {
if (!in_array($source,$domain)) { continue; }
}
foreach ($this->structs[$source]['txt'] as $key=>$file) {
$this->read_textfile($file);
}
}
return $this->config;
}
////////////////
// recursions //
////////////////
function search_up($key,$paths,$ext) {
$return = "";

if(count($paths) == 2) {
$current = $paths[0];
$last_try = $paths[1];
$ls=scandir($current);
foreach($ls as $f) {
if(!strncmp($f,'.',1)) continue; // ignore hidden files
$ex=explode(".", $f);
if(in_array(strtolower(end($ex)),$ext)) {
if($ex[0]==$key) {
$return = $current.$f;
break;
}
}
}
}
if ($return) {
return $return;
} elseif($current == $last_try) {
return false;
} else {
$p = explode('/',$current);
array_pop($p);
array_pop($p);
return self::search_up($key,array(implode("/",$p)."/",$last_try),$ext);
}
}
///////////////////////
// Utility functions //
///////////////////////
function read_textfile($file) {
$str = file_get_contents($file);
$str = self::linkify($str);
$str = self::strip_comments($str);
$str = self::get_config_from_content($str);
return $str;
}
function strip_comments($str) {
$single_line_comments = "/(^;.*\R)/m";
$str = preg_replace($single_line_comments,"",$str);
$multi_line_comments = "/\/\*.*?\*\//s";
$str = preg_replace($multi_line_comments,"",$str);
return $str;
}
function linkify($string) {
$pattern = "/\s@(\w+)[=]([\w,]+)\s/";
$count = 0;
$new = preg_replace_callback
($pattern,
function($m){
$f3 = \Base::instance();
return $f3->get('SITE_URL')
.$f3->alias($m[1],self::$keyword."=".$m[2])
;},
$string);
return $new;
}
function get_config_from_content($string) {
$f3 = \Base::instance();
$f = 0;

$pattern = "/#\+(\w+):\s?(.*)/";
$f = preg_match_all($pattern, $string,$matches,PREG_PATTERN_ORDER);
for ($i=0;$i<$f;$i++) {
$string = str_replace($matches[0][$i],"",$string);
$key = $matches[1][$i];
$value = $matches[2][$i];

if(strtolower($value) == "false") {
$value = FALSE;
}
if(!strncmp(trim($value),'@',1)) {
//var_dump($f3->get('DATA'));
if (array_key_exists($key,$f3->get('DATA'))) {
$DATA = $f3->get('DATA.'.$key);
$conf = array($DATA['type'],$DATA['dir']);
$relation = new \Modules\TOC($conf,$f3->get('CONTENT'),substr($value,1));
$relation->dispatch();
$this->config[$key]=array_shift($relation->entries);
} else {
$this->config[$key]=$value;
}
} else {
$this->config[$key]=$value;
}
}

$pattern = "/§>\s*(\w+):(.*?)\R[\011\040]*\R/s";
$f = preg_match_all($pattern, $string,$matches,PREG_PATTERN_ORDER);
for ($i=0;$i<$f;$i++) {
$string = str_replace($matches[0][$i],"",$string);
$key = $matches[1][$i];
$value = trim($matches[2][$i]);

if(strtolower($value) == "false") {
$value = FALSE;
}
if (!strncmp($value,'@',1)) {
# var_dump();
if (array_key_exists($key,$f3->get('DATA'))) {
$entries = explode("@",$value);
array_shift($entries); // first entry is always empty
$DATA = $f3->get('DATA.'.$key);
$conf = array($DATA['type'],$DATA['dir']);
$relation = new \Modules\TOC($conf,$f3->get('CONTENT'),$entries);
$relation->dispatch();
if(/*count($entries) >*/ 1) {
$this->config[$key]= new CMultiple($relation->entries);
} else {
$this->config[$key]=array_shift($relation->entries);
}
} else {
$this->config[$key]=$value;
}
} else {
$this->config[$key]=$value;
}
}
return $string;
}
function content_element_dispatcher($string) {
$md = new \Parsedown();
$f0 = 0;
// find occorances of {| keyword |}
$pattern = "/\{\|(.+?)\|\}/s";

$f = preg_match_all($pattern, $string,$matches,PREG_PATTERN_ORDER);
for ($i=0;$i<$f;$i++) {
$body = preg_split("/\R/",trim($matches[1][$i]));
$request = explode(":", trim(array_shift($body)));
$new="";
switch($request[0]) {
case 'test':
$new="seems to work";
break;
case ';':
$new="";
break;
case 'path':
$new="/".$this->folder;
break;
case 'space':
$new=sprintf("<div style=\"height:%s;\"></div>",
$request[1]
);
break;
case 'small-text':
if(count($body)) {
$new=sprintf("<div class=\"f1\">%s</div>",
implode("\n",$body)
);
} else {
$new=sprintf("<span class=\"f1\">%s</span>",
$request[1]
);
}
break;
case 'TOC':
// throw away TOC part of request, we don't need it
array_shift($request);
$toc = new \Modules\TOC($request,$this->folder,$body);
$toc->dispatch();
$new=sprintf("<div class=\"TOC %s\">%s</div>",
array_shift($request),
$toc);
break;

case 'header':
array_shift($request);
$conf = array('path', $this->folder);
$v = $this->read_config();
switch (array_shift($request)) {
case 'event':
$el = new CEvent($v,$conf);
$el->set_layout('archive');
$new = $el;
break;
case 'concert':
$el = new CConcert($v,$conf);
$el->set_layout('header');
$new = $el;
break;
}

break;
case 'image':
$key=$request[1];
$image="";
if(!$key) {
$new=self::warn("Content Module \"Image\" needs name of a file (without extension)");
break;
}
foreach($this->structs as $domain=>$destination) {
if(array_key_exists($key, $this->structs[$domain]['pic'])) {
$image = $this->structs[$domain]['pic'][$key];
unset($this->structs[$domain]['pic'][$key]);
unset($this->content[$this->domains[$domain]][$key]);
break;
}
}

if ($image) {
if( in_array($request[2],array('left','right','full'))) {
$class = $request[2];
} else {
$class = 'left';
}
$img = new \Image($image);
$ratio = ($img->width() >= $img->height())
? "landscape"
: "portrait"
;

$cached = new CachedImage($image);
foreach ($body as $k=>$line) {
if (strpos($line,"©") !== FALSE
|| strpos($line,"&copy;") !== FALSE) {
$body[$k] = sprintf("<span class=\"copyright\">%s</span>",$line);
}
}
$new=sprintf("<div class='image-container %s'>"
."<div class=\"media %s\"><a href=\"/%s\" data-featherlight=\"image\"><img src=\"/%s\" alt=\"user supplied image\" /></a></div>"
."<img src=\"/%s\" style=\"display:none;\" alt=\"user supplied image\" />"
."<div class=\"caption\">%s</div>"
."</div>",
$class,
$ratio,
$cached->get_src(1400),
$cached->get_src(500),
$cached->get_src(1400),
$md->text(implode("\n",$body))
);
} else {
$new=self::warn("image \"$key\" not found");
}
break;

case 'youtube':
if( in_array($request[2],array('left','right','full'))) {
$class = $request[2];
} else {
$class = 'left';
}
$video=sprintf("<iframe width=\"700\" height=\"394\" class=\"ytvideo\" "
."src=\"https://www.youtube.com/embed/%s\"></iframe>",
$request[1]);

$thumbnail = sprintf("<div class=\"video-thumbnail\"><a href=\"%s\" data-featherlight=\"#%s\">"
."<img class=\"thumbnail\" src=\"https://img.youtube.com/vi/%s/mqdefault.jpg\" alt=\"video-preview\"/>"
."<img class=\"play-button\" src=\"%s\" alt=\"play-button\" />"
."</a></div><div class=\"lightbox\" id=\"%s\" >%s</div>",
"https://www.youtube.com/watch?v=".$request[1],
$request[1],
$request[1],
"/rsc/img/play-button.png",
$request[1],
$video
);


foreach ($body as $k=>$line) {
if (strpos($line,"©") !== FALSE
|| strpos($line,"&copy;") !== FALSE) {
$body[$k] = sprintf("<span class=\"copyright\">%s</span>",$line);
}
}
$new=sprintf("<div class='video-container %s'>"
."<div class=\"media\">%s</div>"
."<div class=\"caption\">%s</div>"
."</div>",
$class,
$thumbnail,
$md->text(implode("\n",$body)));
break;
default:
$new=self::warn("Content Module \"".$request[0]."\" unknown");
break;
}

$string = str_replace($matches[0][$i],$new,$string);
}
return $string;
}
function warn($message) {
return sprintf("<div class=\"warning\">%s</div>",$message);
}
}

+ 446
- 0
app/modules/filesinfolders.php.orig 파일 보기

@@ -0,0 +1,446 @@
<?php

namespace Modules;

class FilesInFolders {

private $folder;
public $content = array();
public $extras = array();
private $domains = array('default'=>'default');
private $keyfiles = array();
private $structs = array();
private $EXT=array(
'txt'=>array( 'txt', 'text', 'md' ),
'pic'=>array( 'jpg', 'jpeg', 'png', 'svg' ),
'tpl'=>array( 'html', 'htm', 'tpl' ),
'csv'=>array( 'csv' )
);
public $config = array();
private $state=array();

function __construct($folder,$conf=array()) {
$f3 = \Base::instance();
if(is_dir($folder)) { // are we given a valid path?
$this->folder = $folder;
} else { // and if not?
$this->folder = $f3->get('CONTENT')."./";
}
if(is_array($conf)) {
foreach($conf as $key=>$value) {
switch($key) {
case 'content':
if(is_array($value)) {
foreach($value as $k=>$v) {
$this->domains[$k] = $v;
}
}
break;
case 'keyfiles':
if(is_array($value)) {
foreach($value as $k=>$v) {
$this->keyfiles[$k] = $v;
}
}
break;
}
}
}
foreach($this->domains as $domain) {
$this->content[$domain] = array();
}
foreach($this->keyfiles as $keys=>$d) {
$this->extras[$keys] = "";
}
}


/////////////////////////////
// read folder into struct //
/////////////////////////////
function prepare_files() {

foreach ($this->domains as $k=>$v) {
foreach($this->EXT as $cat=>$endings) {
$this->structs[$k][$cat]=array();
}
}
$ls = scandir($this->folder);
foreach ($ls as $k=>$f) {
if (!strncmp($f,'.',1)) continue; // ignore hidden files
$ex=explode(".", $f);
$ext=strtolower(end($ex));
if (array_key_exists($ex[0],$this->domains)) {
$domain=$ex[0];
$sort_key=1;
} elseif (array_key_exists($ex[0], $this->keyfiles)) {
if(in_array($ext,$this->EXT[$this->keyfiles[$ex[0]]['type']])) {
$this->extras[$ex[0]]=$this->folder.$f;
continue;
}
} else {
$domain='default';
$sort_key=0;
}
foreach ($this->EXT as $cat=>$endings) {
if (in_array($ext, $endings)) {
$this->structs[$domain][$cat][$ex[$sort_key]] = $this->folder.$f;
}
}
}

foreach($this->keyfiles as $key=>$param) {
if(!$this->extras[$key]) {
$this->extras[$key] = self::search_up(
$key,
array($this->folder,$param['until']),
$this->EXT[$param['type']]);
}
}
}

///////////////////////////////////////
// prepare content as per the struct //
///////////////////////////////////////
function fill_content() {
$md = new \Parsedown();
foreach($this->domains as $source=>$destination) {
$this->state['current_domain'] = $source;
foreach($this->structs[$source]['txt'] as $key=>$file) {
$str = $this->read_textfile($file);
$str = self::content_element_dispatcher($str);
$str = $md->text($str);
$str = sprintf("<div class='post'>%s</div>", $str);
$this->content[$destination][$key] = sprintf(
"<div class=\"item %s %s\">%s</div>",
$page,
$key,
$str
);
}
// var_dump($this->structs);
foreach($this->structs[$source]['tpl'] as $key=>$file) {
$str = file_get_contents($file);
$str = \Template::instance()->render($file);
$str = self::linkify($str);
$this->content[$destination][$key] = sprintf(
"<div class=\"item %s %s\">%s</div>",
$page,
$key,
$str
);
}
foreach($this->structs[$source]['pic'] as $key=>$file) {
$this->content[$destination][$key] = sprintf(
"<img class=\"direct\" src=\"/$file\" />"
);
}
foreach($this->structs[$source]['csv'] as $key=>$file) {
$csv = new \Modules\Ography($file,TRUE);
$str="<table>";
foreach($csv->entries as $entry) {
$tmp="";
foreach($entry as $key=>$value) {
$tmp .= sprintf("<td class=\"%s\">%s</td>", $key, $value);
}
$str .= sprintf("<tr>%s</tr>",$tmp);
}
$str.="</table>";
$this->content[$destination][$key] = $str;
}
}
}

//////////////////////
// read config data //
//////////////////////
function read_config($domain=false) {
foreach ($this->domains as $source=>$destination) {
if (is_string($domain)) {
if ($source != $domain) { continue; }
} elseif (is_array($domain)) {
if (!in_array($source,$domain)) { continue; }
}
foreach ($this->structs[$source]['txt'] as $key=>$file) {
$this->read_textfile($file);
}
}
return $this->config;
}
////////////////
// recursions //
////////////////
function search_up($key,$paths,$ext) {
$return = "";

if(count($paths) == 2) {
$current = $paths[0];
$last_try = $paths[1];
$ls=scandir($current);
foreach($ls as $f) {
if(!strncmp($f,'.',1)) continue; // ignore hidden files
$ex=explode(".", $f);
if(in_array(strtolower(end($ex)),$ext)) {
if($ex[0]==$key) {
$return = $current.$f;
break;
}
}
}
}
if ($return) {
return $return;
} elseif($current == $last_try) {
return false;
} else {
$p = explode('/',$current);
array_pop($p);
array_pop($p);
return self::search_up($key,array(implode("/",$p)."/",$last_try),$ext);
}
}
///////////////////////
// Utility functions //
///////////////////////
function read_textfile($file) {
$str = file_get_contents($file);
$str = self::linkify($str);
$str = self::get_config_from_content($str);
return $str;
}
function linkify($string) {
$pattern = "/\s@(\w+)[=]([\w,]+)\s/";
$count = 0;
$new = preg_replace_callback
($pattern,
function($m){
$f3 = \Base::instance();
return $f3->get('SITE_URL')
.$f3->alias($m[1],self::$keyword."=".$m[2])
;},
$string);
return $new;
}
function get_config_from_content($string) {
$f3 = \Base::instance();
$f = 0;

$pattern = "/#\+(\w+):\s?(.*)/";
$f = preg_match_all($pattern, $string,$matches,PREG_PATTERN_ORDER);
for ($i=0;$i<$f;$i++) {
$string = str_replace($matches[0][$i],"",$string);
$key = $matches[1][$i];
$value = $matches[2][$i];
if(!strncmp(trim($value),'@',1)) {
//var_dump($f3->get('DATA'));
if (array_key_exists($key,$f3->get('DATA'))) {
$DATA = $f3->get('DATA.'.$key);
$conf = array($DATA['type'],$DATA['dir']);
$relation = new \Modules\TOC($conf,$f3->get('CONTENT'),substr($value,1));
$relation->dispatch();
$this->config[$key]=array_shift($relation->entries);
} else {
$this->config[$key]=$value;
}
} else {
$this->config[$key]=$value;
}
}

$pattern = "/§>\s*(\w+):(.*?)\R[\011\040]*\R/s";
$f = preg_match_all($pattern, $string,$matches,PREG_PATTERN_ORDER);
for ($i=0;$i<$f;$i++) {
$string = str_replace($matches[0][$i],"",$string);
$key = $matches[1][$i];
$value = trim($matches[2][$i]);
if (!strncmp($value,'@',1)) {
# var_dump();
if (array_key_exists($key,$f3->get('DATA'))) {
$entries = explode("@",$value);
array_shift($entries); // first entry is always empty
$DATA = $f3->get('DATA.'.$key);
$conf = array($DATA['type'],$DATA['dir']);
$relation = new \Modules\TOC($conf,$f3->get('CONTENT'),$entries);
$relation->dispatch();
if(/*count($entries) >*/ 1) {
$this->config[$key]= new CMultiple($relation->entries);
} else {
$this->config[$key]=array_shift($relation->entries);
}
} else {
$this->config[$key]=$value;
}
} else {
$this->config[$key]=$value;
}
}
return $string;
}
function content_element_dispatcher($string) {
$md = new \Parsedown();
$f0 = 0;
// find occorances of {| keyword |}
$pattern = "/\{\|(.+?)\|\}/s";

$f = preg_match_all($pattern, $string,$matches,PREG_PATTERN_ORDER);
for ($i=0;$i<$f;$i++) {
$body = preg_split("/\R/",trim($matches[1][$i]));
$request = explode(":", trim(array_shift($body)));
$new="";
switch($request[0]) {
case 'test':
$new="seems to work";
break;
case 'path':
$new="/".$this->folder;
break;
case 'space':
$new=sprintf("<div style=\"height:%s;\"></div>",
$request[1]
);
break;
case 'small-text':
if(count($body)) {
$new=sprintf("<div class=\"f1\">%s</div>",
implode("\n",$body)
);
} else {
$new=sprintf("<span class=\"f1\">%s</span>",
$request[1]
);
}
break;
case 'TOC':
// throw away TOC part of request, we don't need it
array_shift($request);
$toc = new \Modules\TOC($request,$this->folder,$body);
$toc->dispatch();
$new=sprintf("<div class=\"TOC %s\">%s</div>",
array_shift($request),
$toc);
break;

case 'header':
array_shift($request);
$conf = array('path', $this->folder);
$v = $this->read_config();
switch (array_shift($request)) {
case 'event':
$el = new CEvent($v,$conf);
$el->set_layout('archive');
$new = $el;
break;
case 'concert':
$el = new CConcert($v,$conf);
$el->set_layout('header');
$new = $el;
break;
}

break;
case 'image':
$key=$request[1];
$image="";
if(!$key) {
$new=self::warn("Content Module \"Image\" needs name of a file (without extension)");
break;
}
foreach($this->structs as $domain=>$destination) {
if(array_key_exists($key, $this->structs[$domain]['pic'])) {
$image = $this->structs[$domain]['pic'][$key];
unset($this->structs[$domain]['pic'][$key]);
unset($this->content[$this->domains[$domain]][$key]);
break;
}
}

if ($image) {
if( in_array($request[2],array('left','right'))) {
$class = $request[2];
} else {
$class = 'left';
}
$img = new \Image($image);
$ratio = ($img->width() >= $img->height())
? "landscape"
: "portrait"
;

$cached = new CachedImage($image);
$new=sprintf("<div class='image-container %s'>"
."<div class=\"media %s\"><a href=\"/%s\" data-featherlight=\"image\"><img src=\"/%s\" /></a></div>"
."<img src=\"/%s\" style=\"display:none;\" />"
."<div class=\"caption\">%s</div>"
."</div>",
$class,
$ratio,
$cached->get_src(1400),
$cached->get_src(500),
$cached->get_src(1400),
$md->text(implode("\n",$body))
);
} else {
$new=self::warn("image \"$key\" not found");
}
break;

case 'youtube':
if( in_array($request[2],array('left','right'))) {
$class = $request[2];
} else {
$class = 'left';
}
$video=sprintf("<iframe width=\"700vw\" height=\"394vw\" class=\"ytvideo\" "
."src=\"https://www.youtube.com/embed/%s\"></iframe>",
$request[1]);

$thumbnail = sprintf("<div class=\"video-thumbnail\"><a href=\"%s\" data-featherlight=\"#%s\">"
."<img class=\"thumbnail\" src=\"https://img.youtube.com/vi/%s/mqdefault.jpg\" />"
."<img class=\"play-button\" src=\"%s\" />"
."</a></div><div class=\"lightbox\" id=\"%s\" >%s</div>",
"https://www.youtube.com/watch?v=".$request[1],
$request[1],
$request[1],
"/rsc/img/play-button.png",
$request[1],
$video
);
$new=sprintf("<div class='video-container %s'>"
."<div class=\"media\">%s</div>"
."<div class=\"caption\">%s</div>"
."</div>",
$class,
$thumbnail,
$md->text(implode("\n",$body)));
break;
default:
$new=self::warn("Content Module \"".$request[0]."\" unknown");
break;
}

$string = str_replace($matches[0][$i],$new,$string);
}
return $string;
}
function warn($message) {
return sprintf("<div class=\"warning\">%s</div>",$message);
}
}

+ 57
- 0
app/modules/ography.php 파일 보기

@@ -0,0 +1,57 @@
<?php

namespace Modules;

class Ography {
public $entries = array();
function __construct($path="./default.csv",$header=TRUE) {
$raw = array();
$row = 0;

if(is_file($path))
$file_handle = fopen($path,"r");
if($file_handle) {
while(($data = fgetcsv($file_handle,1000, ",")) !== FALSE) {
if(!$row) {
if($header) {
$keys = $data;
$row++;
continue;
} else {
$keys = FALSE;
}
}
if(is_array($keys)) {
$num = count($data);
$temp = array();
for($c=0; $c<$num; $c++) {
$temp[$keys[$c]] = $data[$c];
}
array_push($this->entries, $temp);
$row++;
unset($temp);
} else {
array_push($this->entries, $data);
}
}
fclose($file_handle);
}
}

static function filter_field($key,$function) {
if(function_exists($function)) {
foreach ($this->entries as $k=>$entry) {
$this->entries[$k][$key]=call_user_func($function,$entry[$key]);
}
return TRUE;
} else {
return FALSE;
}
}
static function get_item() {
return array();
}
}

+ 55
- 0
app/modules/pictures.php 파일 보기

@@ -0,0 +1,55 @@
<?php

namespace Modules;

class Pictures extends \Prefab {
static function render_an_image($args) {
$f3 = \Base::instance();
$attr = $args['@attrib'];
//var_dump($attr);
$a=array(
'img'=>'',
'hover'=>'',
'link'=>''
);
$img = array_key_exists('img', $attr) ? $attr['img'] : false;
$alt = array_key_exists('hover', $attr) ? $attr['hover'] : false;
$url = array_key_exists('link', $attr) ? $attr['link'] : false;
echo $img."sss"."<br>";
var_dump($a);
if($img /*&& is_file($img)*/) {
$a['img'] = $img;
} else {
$a['img'] = $f3->get('RESOURCES')."img/default.png";
}
if($alt && is_file($alt)) {
$a['hover'] = $alt;
} else {
$a['hover'] = $a['img'];
}
if($url) {
$a['link'] = $url;
} else {
$a['link'] = '/';
}
//var_dump($a);
//var_dump($args);
//$out = sprintf("<code>%s</code>",);
return self::hover_pic_with_link($a['img'],$a['hover'],$a['link']);
}
static function hover_pic_with_link($pic1,$pic2,$link) {
return sprintf('<a href="%s"><img src="%s" alt="%s" /></a>',$link,$pic1,$pic2);
}
static function makeImageLink($url,$href=false) {
if($href) {
return sprintf("<a href=\"%s\"><img class=\"post-image\" src=\"%s\" /></a>",$href, $url);
}
return sprintf("<img class=\"post-image\" src=\"%s\" />", $url);
}
}

+ 175
- 0
app/modules/tableofcontents.php 파일 보기

@@ -0,0 +1,175 @@
<?php

namespace Modules;

class TableOfContents {

private $selector;
private $folder;
private $caller_dir;
private $body;
private $elements = array();
function __construct($configuration,$caller_dir,$body) {
$f3 = \Base::instance();
//var_dump($configuration);echo "<br>";
if(is_array($configuration)) {
$this->selector = array_shift($configuration);
$folder = array_shift($configuration);
$this->folder = strncmp("/",$folder,1)
? $caller_dir.$folder
: $f3->get('CONTENT').substr($folder,1);
if (substr_compare($this->folder,"/",-1,1)) {
$this->folder .= "/";
}
}
$this->caller_dir = $caller_dir;
$this->body = is_array($body) ? $body : array($body);
if(!$this->body) {
$this->body = null;
}
$this->prepare_elements();
}

// function __toString() {
// return $this->selector.":".$this->folder."\n".implode("\n",$this->body);
//}

function prepare_elements() {
if(is_dir($this->folder)) {
$ls=scandir($this->folder);

if (count($this->body)) {
foreach ($this->body as $f) {
if (in_array($f,$ls)
&& is_dir($this->folder.$f)) {
$this->elements[$this->folder.$f."/"] = true;
}
}
} else {
foreach($ls as $k=>$f) {
if (!strncmp($f,'.',1)) continue;
if (!is_dir($this->folder.$f)) continue;
$this->elements[$this->folder.$f."/"] = true;
}
}
}
foreach ($this->elements as $key=>$value) {
$folder = new FilesInFolders($key);
$folder->prepare_files();
$this->elements[$key] = $folder->read_config();
}
}
function dispatch() {
switch ($this->selector) {
case 'event_view':
$this->event_list();
break;
case 'locations_view':
$this->location_list();
break;
}
}

function get_location_large() {
foreach($this->elements as $key=>$e) {
$this->elements[$key] = self::location_large($e,$key);
}
return array_shift($this->elements);
}
function get_location_medium() {
foreach($this->elements as $key=>$e) {
$this->elements[$key] = self::location_medium($e,$key);
}
return array_shift($this->elements);
}
function get_location_small() {
foreach($this->elements as $key=>$e) {
$this->elements[$key] = self::location_small($e,$key);
}
return array_shift($this->elements);
}
function location_list() {
foreach($this->elements as $key=>$e) {
$this->elements[$key] = self::location_large($e,$key);
}
}
function location_large($e,$key) {
$f3 = \Base::instance();
$town = $e['TOWN'];
$name = $e['NAME'];
$href = $f3->get('SITE_URL').str_replace($f3->get('CONTENT'),"",$key);
return sprintf("<div class=\"location\">"
."<a href=\"/$href\"><h3 class=\"venue-name\">$name</h3></a>"
."<span class\"address\">$town</span>"
."</div>");
}
function location_medium($e,$key) {
$f3 = \Base::instance();
$town = $e['TOWN'];
$name = $e['NAME'];
$href = $f3->get('SITE_URL').str_replace($f3->get('CONTENT'),"",$key);
return sprintf("<div class=\"location\">"
."<a href=\"/$href\"><span class=\"town\">$town</span><br>"
."<span class\"venue-name\">$name</span></a>"
."</div>");
}
function location_small($e,$key) {
$f3 = \Base::instance();
$town = $e['TOWN'];
$name = $e['NAME'];
$href = $f3->get('SITE_URL').str_replace($f3->get('CONTENT'),"",$key);
return sprintf("<div class=\"location\">"
."<a href=\"/$href\"><span class=\"town\">$town</span>"
."<span class\"venue-name\">$name</span></a>"
."</div>");
}
function event_list() {
$f3 = \Base::instance();

foreach ($this->elements as $key=>$entry) {
$DATE = strtotime($entry['DATE']);
$date = sprintf("<span class=\"month\">%s</span><br>"
."<span class=\"day-of-month\">%s</span>",
date('M',$DATE),
date('d',$DATE));
$href = $f3->get('SITE_URL').str_replace($f3->get('CONTENT'),"",$key);
$title = $entry['TITLE'];
$description = $entry['DESCRIPTION'];
$time = sprintf("<span class=\"weekday\">%s</span>, <span class=\"time\">%s</span>",
date('l',$DATE),
$entry['TIME']);
$loc = $entry['LOCATION'];
$loc->set_layout("humble_two_liner");
$location = $loc;#is_object($loc) ? $loc->get_location_medium() : $loc;

$this->elements[$key] = sprintf("<tr><td class=\"date\">%s</td><td>%s</td><td>%s</td></tr>",
$date,
sprintf("<h3>%s</h3><span>%s</span><footer>%s</footer>",
sprintf("<a href=\"/%s\">%s</a>",
$href,
$title
),
$description,
$time
),
$location
);
}
}
function as_string() {
return sprintf("<table class=\"TOC\">%s</table>", implode("\n",$this->elements));
}
}

+ 217
- 0
app/modules/toc.php 파일 보기

@@ -0,0 +1,217 @@
<?php

namespace Modules;

// class TOC
class TOC {

private $selector;
private $folder;
private $layout;
private $caller_dir;
private $body;
private $key = "";
private $elements = array();
public $entries = array();
private $filters = array();


// $conf: array(
// $selector,
// $folder,
// ...
// )
function __construct($conf,$caller_dir,$body) {
$f3 = \Base::instance();
//var_dump($conf);
// read configuration
if (is_array($conf)) {
$this->selector = array_shift($conf);
$folder = trim(array_shift($conf));
$this->folder = strncmp("/", $folder, 1)
? $caller_dir.$folder
: $f3->get('CONTENT').substr($folder,1);
if (substr_compare($this->folder, "/", -1, 1)) {
$this->folder .= "/";
}
if (count($conf)) {
$this->layout = array_shift($conf);
}
if (count($conf)) {
$this->key = array_shift($conf);
}
}
$this->caller_dir = $caller_dir;
$this->body = is_array($body) ? $body : array($body);

$this->prepare_filters();
$this->prepare_elements();
//echo $this->folder."<br>";
}

function __toString() {
return implode("\n",$this->entries);
}
function dispatch() {
switch ($this->selector) {
case 'event':
case 'events':
$this->load('\Modules\CEvent');
break;
case 'location':
case 'locations':
$this->load('\Modules\CLocation');
break;
case 'member':
case 'members':
$this->load('\Modules\CMember');
break;
case 'concert':
case 'concerts':
$this->load('\Modules\CConcert');
break;
}
}

function prepare_filters() {

// Filters on Key value pairs
$pattern = "/(.+)=(.+)/";
foreach ($this->body as $k=>$v) {
if(preg_match($pattern,$v,$matches)) {
unset($this->body[$k]);
if(strncmp("!",$matches[1],1)) {
$this->filters['must_have'][]=array($matches[1]=>$matches[2]);
} else {
$this->filters['must_not_have'][]=array(substr($matches[1],1)=>$matches[2]);
}
}
}

}

function apply_filters() {
foreach($this->elements as $k=>$v) {
$show = true;
if (count($this->filters['must_have'])) {
$show = false;
foreach ($this->filters['must_have'] as $l=>$w) {
$ke = array_keys($w);
$key = $ke[0];
if (array_key_exists($key,$v)) {
if (is_object($v[$key]) && !strncmp("@",$w[$key],1)) {
$show = (in_array(substr($w[$key],1), $v[$key]->ids))
? true
: $show
;
} else {
$show = (trim($v[$key])==trim($w[$key]))
? true
: $show
;
}
}
}
}
if (count($this->filters['must_not_have'])) {
foreach ($this->filters['must_not_have'] as $l=>$w) {
$ke = array_keys($w);
$key = $ke[0];

if (array_key_exists($key,$v)) {
$show = (trim($v[$key])==trim($w[$key])) ? false : $show;
}
}
}

switch ($show) {
case true:
break;
case false:
unset($this->elements[$k]);
break;
}
}
}
function prepare_elements() {
if(is_dir($this->folder)) {
$ls=scandir($this->folder);
if ($this->body) {
//var_dump($this->body);
foreach ($this->body as $f) {
$f = trim($f);
if (in_array($f,$ls)
&& is_dir($this->folder.$f)) {
$this->elements[$this->folder.$f."/"] = true;
}
}
} else {
foreach($ls as $k=>$f) {
if (!strncmp($f,'.',1)) continue;
if (!is_dir($this->folder.$f)) continue;
$this->elements[$this->folder.$f."/"] = true;
}
}
}

foreach ($this->elements as $key=>$value) {
$folder = new FilesInFolders($key);
$folder->prepare_files();
$this->elements[$key] = $folder->read_config();
}
// can be moves before
$this->apply_filters();
}

function string_to_key($in) {
return md5($in);
}

function load($class) {
foreach ($this->elements as $k=>$v) {
$config=array('path'=>$k);
$this->entries[$k] = new $class($v,$config);
if ($this->layout) {
if ($this->layout != 'collect') {
//var_dump($this->entries[$k]);
$this->entries[$k]->set_layout($this->layout);
}
}
}
if ($this->layout == 'collect' && $this->key != "") {
$new = array();
foreach ($this->entries as $k=>$en) {
if (array_key_exists($this->key,$en->keys)) {
$key_orig = $en->keys[$this->key];
}
$key_new = self::string_to_key($en->values[$key_orig]);
if (!array_key_exists($key_new, $new)) {
$new[$key_new] = array();
}
$new[$key_new][] = $en;
}
//var_dump($new);
foreach ($new as $k=>$collection) {
$obj = new CMultiple($collection);
$obj->set_first(clone $obj->elements[0],'collected_header');
$obj->set_layout('collected_entry');
$new[$k] = $obj;
}
//var_dump($new);
$this->entries = $new;
}
//var_dump($this->entries);
}
}

+ 1685
- 0
app/parsedown.php
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
파일 보기


+ 1
- 0
app/views/borderless.html 파일 보기

@@ -0,0 +1 @@
{{ @content | raw }}

+ 7
- 0
app/views/header.htm 파일 보기

@@ -0,0 +1,7 @@
<a href="{{ 0 ? @SITE_URL : "/" }}">
<check if="{{ false }}">
<img class="logo" src="{{@RESOURCES}}img/logo.png" />
</check>
<h1><img class="text" src="/{{ @banner }}" alt="{{ @title }}" /></h1>
<span class="login-button"><a href="/login">login</a></span>
</a>

+ 35
- 0
app/views/login.htm 파일 보기

@@ -0,0 +1,35 @@
<form action="/login">
<div class="imgcontainer">
<img src="img_avatar2.png" alt="Avatar" class="avatar">
</div>

<div class="container">
<label for="uname"><b>Email</b></label>
<input
type="text"
placeholder="Enter Username"
name="uname"
value="{{ @FORM.username }}"
required>

<label for="psw"><b>Password</b></label>
<input
type="password"
placeholder="Enter Password"
name="psw"
value="{{ @FORM.password }}"
required>

<button type="submit">Login</button>
<label>
<input type="checkbox" checked="checked" name="remember"> Remember me
</label>
</div>

<div class="container" style="background-color:#f1f1f1">
<button type="button" class="cancelbtn">Cancel</button>
<span class="psw">Forgot <a href="#">password?</a></span>
</div>
<div class="container">
<span class=""><a href="/register">register</a></span>
</form>

+ 4
- 0
app/views/maincontent.htm 파일 보기

@@ -0,0 +1,4 @@
<div id="main-content" class="{{ @secondary_content ? 'primaryNarrow' : 'primaryWide' }}">
{{ @content | raw }}
</div>


+ 9
- 0
app/views/nav-external.htm 파일 보기

@@ -0,0 +1,9 @@
<ul>
<repeat group="{{ @menu }}" key="{{ @key }}" value="{{ @item }}">

<li class="away">
<a href="{{ @item.href }}" class="nav-{{ @key }}">{{ @item.name }}</a>
</li>

</repeat>
</ul>

+ 34
- 0
app/views/navigation.htm 파일 보기

@@ -0,0 +1,34 @@
<check if="{{ @menuAddHome }}">
<a class="home-link" href="{{ @SITE_URL }}/{{ (@LANG=='de' ? '' : '?lang='.@LANG )}}">HOME</a>
</check>

<ul>
<repeat group="{{ @menu }}" key="{{ @key }}" value="{{ @item }}">
<check if="{{ is_array(@item) }}">
<true>
<li class="{{ in_array(@key,$url)?'active':'away' }}">
<a href="{{ @SITE_URL }}/{{ @key }}{{ (@LANG=='de' ? '' : '?lang='.@LANG )}}">{{ @item.index }}</a>
<check if="{{ count(@item) > 1 }}">
<ul>
<repeat group="{{ @item }}" key="{{ @key2 }}" value="{{ @item2 }}">
<check if="{{ @key2 != 'index'}}">
<li class="{{ (in_array(@key2,$url) && in_array(@key,$url))?'active':'away' }}">
<a href="{{ @SITE_URL }}/{{ @key }}/{{ @key2 }}{{ (@LANG=='de' ? '' : '?lang='.@LANG )}}">{{ @item2 }}</a>
</li>
</check>
</repeat>
</ul>
</check>
</li>
</true>
<false>
<li class="{{ in_array(@key,$url)?'active':'away' }}">
<a href="{{ @SITE_URL }}/{{ @key }}{{ (@LANG=='de' ? '' : '?lang='.@LANG )}}">{{ @item }}</a>
</li>
</false>
</check>
</repeat>
</ul>


+ 37
- 0
app/views/register.htm 파일 보기

@@ -0,0 +1,37 @@
<form action="/register" style="border:1px solid #ccc">
<div class="container">
<h1>Sign Up</h1>
<p>Please fill in this form to create an account.</p>
<hr>

<label for="email"><b>Email</b></label>
<input
type="text"
placeholder="Enter Email"
name="email"
value="{{ @FORM.email }}"
required>

<label for="psw"><b>Password</b></label>
<input
type="password"
placeholder="Enter Password"
name="psw"
value="{{ @FORM.password }}"
required>

<label for="psw-repeat"><b>Repeat Password</b></label>
<input type="password" placeholder="Repeat Password" name="psw-repeat" required>

<label>
<input type="checkbox" checked="checked" name="remember"> Remember me
</label>

<p>By creating an account you agree to our <a href="/terms" >Terms & Privacy</a>.</p>

<div class="clearfix">
<button type="button" class="cancelbtn">Cancel</button>
<button type="submit" class="signupbtn">Sign Up</button>
</div>
</div>
</form>

+ 5
- 0
app/views/sidebar.htm 파일 보기

@@ -0,0 +1,5 @@
<check if="{{ @secondary_content }}">
<aside id="sidebar">
{{ @secondary_content | raw }}
</aside>
</check>

+ 25
- 0
app/views/tpl/image-with-links.html 파일 보기

@@ -0,0 +1,25 @@
<!--{~
/*
*part of gunther-rost.com (c)2018
*/ ~} -->
<repeat group="{{ @images }}" value="{{ @image }}">
<div class="card">
<check if="{{ @image.href && (! @no_links ) }}">
<true>
<a href="{{ @image.href }}" {{ @image.download?'download':'' }}>
<img src="{{ @image.src1 }}" />
<check if="{{ @image.text }}">
<true>
<div class="img-hover">{{ @image.text | raw}}</div>
</true>
</check>
<!--<img src="{{ @image.src2 }}" class="img-hover" />-->
</a>
</true>
<false>
<img src="{{ @image.src1 }}" />
<!--<img src="{{ @image.src2 }}" class="img-hover" />-->
</false>
</check>
</div>
</repeat>

+ 69
- 0
app/views/tpl/index.html 파일 보기

@@ -0,0 +1,69 @@
<!doctype html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta content="noindex, nofollow" name="robots">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="/rsc/img/favicon OFS.png" />
<link href="/rsc/featherlight.min.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="/{{ @RESOURCES }}jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="/{{ @RESOURCES }}script.js"></script>
<link type="text/css" href="/{{ @RESOURCES }}style.css" rel="stylesheet" />
<title>{{ @title }}</title>
</head>
<body class="{{ @bodyClass }}">
<div id="mobile-nav">
<a class="menu-toggle" onClick="javascript:toggle_menu();"></a>
<div class="the_list">
<include href="navigation.htm" />
<include href="navigation.htm" with="menu={{ @menu2 }},menuAddHome={{ @menu2AddHome }}" />
<include href="nav-external.htm" with="menu={{ @navi.languages }}" />
</div>
</div>

<div id="page-wrap">
<header>
<include href="header.htm">
</header>

<nav id="main-nav">
<include href="navigation.htm" />
<div class="network">
<include href="nav-external.htm" with="menu={{ @navi.network }},menuAddHome={{ @navi.network._config.addHome }}">
</div>
</nav>
<div id="platzhalter"></div>
<section id="content">
<include href="maincontent.htm" />
<include href="sidebar.htm" />
</section>
<footer id="main-footer">
<nav>
<include href="navigation.htm" with="menu={{ @menu2 }},menuAddHome={{ @menu2AddHome }}" />
</nav>
<nav id="languages">
<include href="nav-external.htm" with="menu={{ @navi.languages }}" />
</nav>
</footer>

</div>
<check if="{{ @backend }}">
<div id="admin-panel">{{ @backend | raw }}</div>
</check>
<script type="text/javascript" src="/{{ @RESOURCES }}featherlight.min.js"></script>
</body>
</html>

+ 10
- 0
composer.json 파일 보기

@@ -0,0 +1,10 @@
{
"name":"Freakarias Fat Free Webapp",
"description":"Easy Homepage Build",
"config": {
"vendor-dir":"lib"
},
"require": {
"bcosca/fatfree-core": "dev-master"
}
}

+ 62
- 0
deploy 파일 보기

@@ -0,0 +1,62 @@
#!/bin/bash

USER=root
SERVER=brain.norganoid.com

LIVE_DIR=../var/www/norganoid.at/
DEV_DIR=../var/www/dev.norganoid.at/

CONTENT=content/

ERROR="Error. Please make sure you've indicated correct parameters"

if [ $# -eq 0 ]
then
echo $ERROR;
exit 1
elif [ $1 == "dev" ]
then
DIR=$DEV_DIR
elif [ $1 == "live" ]
then
DIR=$LIVE_DIR
else
echo $ERROR
exit 1
fi

if [[ -z $2 ]]
then
echo "Running App Sync (dry-run)"
rsync --dry-run -az --force --delete --progress --exclude-from=.rsyncignore ./ $USER@$SERVER:$DIR;
elif [[ $2 == "go" ]]
then
echo "Running App Sync"
rsync -az --force --delete --progress --exclude-from=.rsyncignore ./ $USER@$SERVER:$DIR;
elif [[ $2 == "content" ]]
then
if [[ -z $3 ]]
then
echo "Fetching Content (dry-run)"
rsync --dry-run -azv --force --delete --progress --exclude-from=.rsyncignore $USER@$SERVER:$DIR$CONTENT ./$CONTENT;
echo "done"
elif [[ $3 == "go" ]]
then
echo "Fetching Content"
rsync -azv --force --delete --progress --exclude-from=.rsyncignore $USER@$SERVER:$DIR$CONTENT ./$CONTENT;
echo "done"
elif [[ $3 == "up" ]]
then
if [[ -z $4 ]]
then
echo "Uploading Content (dry-run)"
rsync --dry-run -azv --progress --exclude-from=.rsyncignore ./$CONTENT $USER@$SERVER:$DIR$CONTENT;
echo "done"
elif [[ $4 == "go" ]]
then
echo "Uploading Content"
rsync -azv --progress --exclude-from=.rsyncignore ./$CONTENT $USER@$SERVER:$DIR$CONTENT;
echo "done"
fi
fi
fi

+ 178
- 0
index.php 파일 보기

@@ -0,0 +1,178 @@
<?php

/////////////////////////////
// configure installation: //
/////////////////////////////

define("ROOT", "./");
require_once( ROOT.'lib/autoload.php' );

/** @var \Base $f3 */
$f3 = \Base::instance();
$f3->set('DEBUG',3);
$f3->set('CACHE',FALSE);
$f3->set('AUTOLOAD', ROOT.'app/');
$f3->set('UI', implode(';',array(
ROOT.'app/views/',
ROOT.'content/' // content folders can contain .html templates
)));



/////////////////////////
// main configuration: //
/////////////////////////

$f3->config( ROOT.'main.cfg' );
if(!setlocale(LC_TIME, 'de_DE.UTF-8')) {

//echo "locale not set";
}

///////////////////////////////////////////////
// configuration based on configuration file //
///////////////////////////////////////////////
// set language
$languages = $f3->get('languages');
$f3->set('default_lang', array_shift($languages));
if (in_array(strtolower($f3->get('GET.lang')), $languages)) {
$f3->set('LANG', strtolower($f3->get('GET.lang')));
} else {
$f3->set('LANG', $f3->get('default_lang'));
}
// set content dir
if(array_key_exists($f3->get('LANG'), $f3->get('content'))) {
$content_dir=$f3->get('content.'.$f3->get('LANG'));
} else {
$content_dir=$f3->get('content.'.$f3->get('default_lang'));
}
$f3->set('CONTENT', $content_dir);

// set menu
if(array_key_exists($f3->get('LANG'), $f3->get('nav.main'))) {
$menu=$f3->get('nav.main.'.$f3->get('LANG'));
} else {
$menu=$f3->get('nav.main.'.$f3->get('default_lang'));
}
$f3->set('menuAddHome',$f3->get('nav.main.config.addHome'));
$f3->set('menu', $menu);
// set menu2

if(array_key_exists($f3->get('LANG'), $f3->get('nav.footer'))) {
$menu=$f3->get('nav.footer.'.$f3->get('LANG'));
} else {
$menu=$f3->get('nav.footer.'.$f3->get('default_lang'));
}
$f3->set('menu2AddHome',$f3->get('nav.footer.config.addHome'));
$f3->set('menu2', $menu);

if(array_key_exists($f3->get('LANG'), $f3->get('nav.network'))) {
$menu=$f3->get('nav.network.'.$f3->get('LANG'));
} else {
$menu=$f3->get('nav.network.'.$f3->get('default_lang'));
}
foreach($menu as $k=>$v) {
$ex = explode(": ", $menu[$k]);
$menu[$k] = array(
'name' => trim($ex[0]),
'href' => count($ex) > 1 ? $ex[1] : $ex
);
}
$f3->set('navi.network._config.addHome', $f3->get('nav.network.config.addHome'));
$f3->set('navi.network', $menu);

if(array_key_exists($f3->get('LANG'), $f3->get('nav.languages'))) {
$menu=$f3->get('nav.languages.'.$f3->get('LANG'));
} else {
$menu=$f3->get('nav.languages.'.$f3->get('default_lang'));
}
if(is_array($menu)){
foreach($menu as $k=>$v) {
$ex = explode(": ", $menu[$k]);
$menu[$k] = array(
'name' => trim($ex[0]),
'href' => count($ex) > 1 ? $ex[1] : $ex
);
}
$f3->set('navi.languages._config.addHome', $f3->get('nav.languages.config.addHome'));
$f3->set('navi.languages', $menu);
}


// this is needed so the menu can compare the current page with a link to it
// and use this information to determine the active state
$tmp_url = substr($_SERVER['REQUEST_URI'],strlen($f3->get('SITE_URL'))+1);
$url=substr($tmp_url,0,(strpos($tmp_url,'?') === false) ? 999 : strpos($tmp_url,'?'));
$f3->set('url', explode("/", $url));


///////////////////////////////////
// development utility functions //
///////////////////////////////////

function debug($Message=".") {
if(DEBUG > 0) {
echo "<br>".$Message;
}
}


##############################################################################
// this need to go away from here -----------------------------------------
// just to be more tidy etc.
function is_image($path) {
$ex = explode('.',$path);
$ext = array_pop($ex);
return in_array($ext,array( 'jpg', 'jpeg', 'png' ));
}
function pic_cache($in1,$inwidth=360){
$f3 = \Base::instance();
if(is_image($in1)) {
$info = pathinfo($in1);
$fn = basename($in1,'.'.$info['extension']);
$width=$inwidth;
$name = md5($in1.$width);
$name = sprintf("%s%s.png",$fn,$name);
$out ='rsc/img_display/s'.$name;
if(!file_exists($in1)) { $in1='rsc/img/default.png'; }
if(!file_exists($out)) {
$img1 = new Image($in1);
$img1->resize($width);
$f3->write($out,$img1->dump('png',9));
}
} elseif(!strncmp("locallink:",$in1, strlen("locallink:"))) {
$key = substr($in1, strlen("locallink:"));
// localize internal links
if($f3->get('LANG') != 'DE') {
$key .= "?lang=".$f3->get('LANG');
}
$out = $key;
} else {
$out = $in1;
}
return $out;
}
//------------------------------------------------------------------------
###############################################################################

if ($f3->get("GET.admin")) {
$admin = new \Controller\Admin;
$f3->set('backend',$admin->index());
} else {
$f3->set('backend',false);
}

// HTML preloading of images
// this could also find some better place
$f3->mset(array(
'cached_images' => array(\Controller\Page::check_folder_for_backgroundimage('content/'))
));



$f3->run();
echo \Template::instance()->render($f3->get('template'));

+ 45
- 0
main.cfg 파일 보기

@@ -0,0 +1,45 @@
SITE_URL =

[routes]
GET / = Controller\Page->home
GET /home = Controller\Page->home
GET /login = Controller\User->login
GET /register = Controller\User->register
GET @page: /@page = Controller\Page->index
GET /@level1/@level2 = Controller\Page->secondLevel
GET /@level1/@level2/@level3 = Controller\Page->thirdLevel
GET /@level1/@level2/@level3/@level4 = Controller\Page->fourthLevel


[configs]
content/navi.cfg=false
content/data.cfg=false

[globals]
title=A paid service

;; (multilingual) content configuration
;; ------------------------------------

; comma seperated list of language identifiers
; at least one language need to be defined here
; fist mention is default language
languages=de,en

; content directories for different languages
content.de=content/DE/
content.en=content/NO/
content.no=content/NO/

; include a link to the Homepage above the menu
; [dev: translates to menuAddHome in Template (index.php)]
nav.main.config.addHome=true
nav.footer.config.addHome=false

;; HTML Template related:
;; ----------------------
;public directory for css, javascript, images etc.
RESOURCES=rsc/
;default site template:
template=tpl/index.html


+ 8
- 0
rsc/featherlight.min.css 파일 보기

@@ -0,0 +1,8 @@
/**
* Featherlight - ultra slim jQuery lightbox
* Version 1.7.13 - http://noelboss.github.io/featherlight/
*
* Copyright 2018, Noël Raoul Bossart (http://www.noelboss.com)
* MIT Licensed.
**/
html.with-featherlight{overflow:hidden}.featherlight{display:none;position:fixed;top:0;right:0;bottom:0;left:0;z-index:2147483647;text-align:center;white-space:nowrap;cursor:pointer;background:#333;background:rgba(0,0,0,0)}.featherlight:last-of-type{background:rgba(0,0,0,.8)}.featherlight:before{content:'';display:inline-block;height:100%;vertical-align:middle}.featherlight .featherlight-content{position:relative;text-align:left;vertical-align:middle;display:inline-block;overflow:auto;padding:25px 25px 0;border-bottom:25px solid transparent;margin-left:5%;margin-right:5%;max-height:95%;background:#fff;cursor:auto;white-space:normal}.featherlight .featherlight-inner{display:block}.featherlight link.featherlight-inner,.featherlight script.featherlight-inner,.featherlight style.featherlight-inner{display:none}.featherlight .featherlight-close-icon{position:absolute;z-index:9999;top:0;right:0;line-height:25px;width:25px;cursor:pointer;text-align:center;font-family:Arial,sans-serif;background:#fff;background:rgba(255,255,255,.3);color:#000;border:0;padding:0}.featherlight .featherlight-close-icon::-moz-focus-inner{border:0;padding:0}.featherlight .featherlight-image{width:100%}.featherlight-iframe .featherlight-content{border-bottom:0;padding:0;-webkit-overflow-scrolling:touch}.featherlight iframe{border:0}.featherlight *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@media only screen and (max-width:1024px){.featherlight .featherlight-content{margin-left:0;margin-right:0;max-height:98%;padding:10px 10px 0;border-bottom:10px solid transparent}}@media print{html.with-featherlight>*>:not(.featherlight){display:none}}

+ 8
- 0
rsc/featherlight.min.js
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
파일 보기


+ 508
- 0
rsc/featherlightREADME.md 파일 보기

@@ -0,0 +1,508 @@
Featherlight - ultra slim jQuery lightbox [![Build Status](https://travis-ci.org/noelboss/featherlight.svg?branch=master)](https://travis-ci.org/noelboss/featherlight)
------------------------

**Featherlight is a very lightweight jQuery lightbox plugin. For more information and demos, visit the [official website](http://noelboss.github.io/featherlight/).**

* Simple yet flexible
* Image, Ajax, iFrame and custom content support
* [Gallery Extension](https://github.com/noelboss/featherlight/#featherlight-gallery)
* Minimal CSS
* Name-spaced CSS and JavaScript
* Responsive
* Accessible
* Customizable via javascript or attributes



## [» Download Current Release 1.7.13](https://github.com/noelboss/featherlight/archive/1.7.13.zip)

Here you'll find a [list of all the changes](https://github.com/noelboss/featherlight/blob/master/CHANGELOG.md) and you can also download [old releases](https://github.com/noelboss/featherlight/releases) or [the master including all the latest bling](https://github.com/noelboss/featherlight/archive/master.zip).


# Installation

All styling is done using CSS so you'll want to include the Featherlight CSS in your head.

<link href="//cdn.rawgit.com/noelboss/featherlight/1.7.13/release/featherlight.min.css" type="text/css" rel="stylesheet" />

Be aware that Featherlight uses very unspecific CSS selectors to help you overwrite every aspect. This means in turn, that if you're not following a modularized approach to write CSS (which you should! It's terrific!) and have many global and specific definitions (read ID's and such – which you shouldn't), these definitions can break the Featherlight styling.

Featherlight requires jQuery version 1.7.0 or higher (regular version, not the slim one). It's recommended to include the javascript at the bottom of the page before the closing `</body>` tag.

<script src="//code.jquery.com/jquery-latest.js"></script>
<script src="//cdn.rawgit.com/noelboss/featherlight/1.7.13/release/featherlight.min.js" type="text/javascript" charset="utf-8"></script>


# Usage

By default, featherlight acts on all elements using the 'data-featherlight' attribute. An element with this attribute triggers the lightbox. The value of the attribute acts as selector for an element that's opened as lightbox.

<a href="#" data-featherlight="#mylightbox">Open element in lightbox</a>
<div id="mylightbox">This div will be opened in a lightbox</div>

Featherlight is smart. 'data-featherlight' can also contain a link to an image, an ajax-url or even DOM code:

<a href="#" data-featherlight="myimage.png">Open image in lightbox</a>
<a href="#" data-featherlight="myhtml.html .selector">Open ajax content in lightbox</a>
<a href="#" data-featherlight="<p>Fancy DOM Lightbox!</p>">Open some DOM in lightbox</a>

it also works with links using href and the "image" and "ajax" keywords (this can also be manually set with the configuration options like `{image: 'photo.jpg'}` or `{type: 'image'}`):

<a href="myimage.png" data-featherlight="image">Open image in lightbox</a>
<a href="myhtml.html .selector" data-featherlight="ajax">Open ajax content in lightbox</a>
<a href="#" data-featherlight-ajax="myhtml.html .selector">Open ajax content in lightbox</a>
<a href="#" data-featherlight="myhtml.html .selector" data-featherlight-type="ajax">Open ajax content in lightbox</a>

By default, Featherlight initializes all elements matching `$.featherlight.autoBind` on document ready. If you want to prevent this, set `$.featherlight.autoBind` to `false` before the DOM is ready.

## Bind Featherlight
You can bind the Featherlight events on any element using the following code:

$('.myElement').featherlight($content, configuration);

It will then look for the `targetAttr` (by default "data-featherlight") on this element and use its value to find the content that will be opened as lightbox when you click on the element.

***$content*** – jQuery Object or String: You can manually pass a jQuery object or a string (see [content filters](#content-filters)) to be opened in the lightbox. Optional

***configuration*** – Object: Object to configure certain aspects of the plugin. See [Configuration](#configuration). Optional

## Manual calling of Featherlight
In cases where you don't want an Element to act as Trigger you can call Featherlight manually. You can use this for example in an ajax callback to display the response data.

$.featherlight($content, configuration);

***$content*** – jQuery Object or String: You can manually pass a jQuery object or a string (see [content filters](#content-filters)) to be opened in the lightbox. Optional

***configuration*** – Object: Object to configure certain aspects of the plugin. See [Configuration](#configuration). Optional

# Configuration

Featherlight comes with a bunch of configuration-options which make it very flexible.
These options can be passed when calling `featherlight`.
Alternatively, they can specified as attribute on the elements triggering the lightbox;
for example, `<a data-featherlight-close-on-esc="false" ...>` has the same effect as
passing `{closeOnEsc: false}`.
You can also modify the `$.featherlight.defaults` directly which holds all the defaults:

```javascript
/* you can access and overwrite all defaults using $.featherlight.defaults */
defaults: {
namespace: 'featherlight', /* Name of the events and css class prefix */
targetAttr: 'data-featherlight', /* Attribute of the triggered element that contains the selector to the lightbox content */
variant: null, /* Class that will be added to change look of the lightbox */
resetCss: false, /* Reset all css */
background: null, /* Custom DOM for the background, wrapper and the closebutton */
openTrigger: 'click', /* Event that triggers the lightbox */
closeTrigger: 'click', /* Event that triggers the closing of the lightbox */
filter: null, /* Selector to filter events. Think $(...).on('click', filter, eventHandler) */
root: 'body', /* A selector specifying where to append featherlights */
openSpeed: 250, /* Duration of opening animation */
closeSpeed: 250, /* Duration of closing animation */
closeOnClick: 'background', /* Close lightbox on click ('background', 'anywhere', or false) */
closeOnEsc: true, /* Close lightbox when pressing esc */
closeIcon: '&#10005;', /* Close icon */
loading: '', /* Content to show while initial content is loading */
persist: false, /* If set, the content will persist and will be shown again when opened again. 'shared' is a special value when binding multiple elements for them to share the same content */
otherClose: null, /* Selector for alternate close buttons (e.g. "a.close") */
beforeOpen: $.noop, /* Called before open. can return false to prevent opening of lightbox. Gets event as parameter, this contains all data */
beforeContent: $.noop, /* Called when content is about to be presented. `this` is the featherlight instance. Gets event as parameter */
beforeClose: $.noop, /* Called before close. can return false to prevent opening of lightbox. `this` is the featherlight instance. Gets event as parameter */
afterOpen: $.noop, /* Called after open. `this` is the featherlight instance. Gets event as parameter */
afterContent: $.noop, /* Called after content is ready and has been set. Gets event as parameter, this contains all data */
afterClose: $.noop, /* Called after close. `this` is the featherlight instance. Gets event as parameter */
onKeyUp: $.noop, /* Called on key up for the frontmost featherlight */
onResize: $.noop, /* Called after new content and when a window is resized */
type: null, /* Specify content type. If unset, it will check for the targetAttrs value. */
contentFilters: ['jquery', 'image', 'html', 'ajax', 'text'] /* List of content filters to use to determine the content */
jquery/image/html/ajax/text: undefined /* Specify content type and data */
}
```

================================================

namespace – String: 'featherlight'
All functions bound to elements are namespaced. This is also used to prefix all CSS classes for the background, the content-wrapper and the close button.


================================================

targetAttr – String: 'data-featherlight'
Attribute on the triggering element pointing to the target element or content that will be opened in the lightbox.

================================================

variant – String: null
Pass your own CSS class to adjust the styling of the lightbox according to your need.


================================================

resetCss – Boolean: false
Set this to true to remove all default css and start from designing scratch.

================================================


openTrigger & closeTrigger – String: 'click'
Events that are used to open or close the lightbox. The close event is bound to the close button and to the lightbox background (if enabled).
Has no effect if $.featherlight is called directly.

================================================

filter - String: null
A selector to filter events, when calling `featherlight` on a jQuery set, in a similar fashion to `$(...).on('click', filter, eventHandler)`.

Attributes both the selector and the filtered element are taken into account.

In the following example, the first link will make an ajax request while the second will display the text "second".

<div data-featherlight data-featherlight-filter="a"
data-featherlight-type="ajax">
<a href="first">Hello</a>
<a href="second" data-featherlight-type="text">World</a>
</div>

================================================

root - String: 'body'
This selector specified where the featherlight should be appended.

================================================

openSpeed & closeSpeed – Integer or String: 250
Defines the speed for the opening and close animations. Values allowed are [jQuery animation durations](http://api.jquery.com/animate/#duration). Avoid the usage of 0 since this would cause a reversal of time and the end of the world! (Okay, kidding, just the closing callback would not be fired and unused ghost-DOM would linger around, but still.)


================================================

closeOnClick – 'background', 'anywhere' or false
If set, the close event is also bound to the either the background only or anywhere

================================================

closeOnEsc – Boolean: true
If true, the lightbox is closed when pressing the ESC key

================================================

closeIcon – String: '&#10005;';
Oh the naming...

================================================

loading – String: '';
Shown initially while content loads. The lightbox also has a class '.featherlight-loading' while content is loading. This makes it easy to specify a "Loading..." message or a spinner.

================================================

persist - Boolean or 'shared': false;
If set, the content will persist and will be shown again when opened again.
In case where multiple buttons need to persist the same content, use the special value 'shared'.
The content filter `jquery` (used for links like `.some-class` or `#some-id`) will clone the given content if and only if it is not persisted. Otherwise it will be moved into the lightbox.

================================================

otherClose - String: null
While the close icon generated by featherlight will have class 'featherlight-close', you may specify alternate selector for other buttons having the same effect in your dialog, for example "a:contains('Cancel')".

================================================

background – DOM String: null
You can provide the wrapping DOM. This is a bit tricky and just for the advanced users. It's recommended to study the plugin code. But you need to provide an element with a "{namespace}-inner" class: the content of the lightbox will replace this element. It is recommended that instead of providing this option you modify the lightbox on `beforeOpen` or `afterOpen`.


================================================

beforeOpen, beforeClose – Function: null
Called before the open or close method is executed. This function can return false to prevent open or
close method from execution. `this` is an object and contains the triggering DOM element (if existing) and the related Featherlight objects.

// example
beforeOpen: function(event){
console.log(this); // this contains all related elements
return false; // prevent lightbox from opening
}


================================================

beforeContent, afterContent – Function: null
Called before and after the loading of the content. For ajax calls or images, there can be a significant delay, for inline content the two calls will occur one right after the other. It receives the event object. `this` is an object and contains the triggering DOM element (if existing) and the related Featherlight objects.

================================================

afterOpen, afterClose – Function: null
Called after the open or close method is executed – it is not called, if the `before-` or `open` function returns `false`! It receives the event object. `this` is an object and contains the triggering DOM element (if existing) and the related Featherlight objects.

// example
afterOpen: function(event){
console.log(this); // this contains all related elements
alert(this.$content.hasClass('true')); // alert class of content
}

================================================

onKeyUp, onResize – Function: null
The function receives the event object. `this` is an object and contains the triggering DOM element (if existing) and the related Featherlight objects.

================================================

type – String: null

The type object allows you to manually set what type the lightbox is. Set the value to 'image', 'ajax' or any of the content filters. Otherwise, the value from targetAttr will used be to determine the type of the lightbox. Example:

$('.image-lightbox').featherlight({type: 'image'});


# Methods

`$.featherlight` is actually a constructor of new featherlight objects. Modify `$.featherlight.prototype` to change the default properties, or use the `configuration` object passed to the constructor to override the properties of that specific new instance.

It's possible to use or change these methods, but the API isn't guaranteed to remain constant; enquire if you have particular needs.

var current = $.featherlight.current();
current.close();
// do something else
current.open(); // reopen it

Check the source code for more details.

# Globals

`$.featherlight` has the following globals:

autoBind: '[data-featherlight]' /* Will automatically bind elements matching this selector. Clear or set before onReady */
current: function() /* returns the currently opened featherlight, or undefined */
close: function() /* closes the currently opened featherlight (if any) */

# Content Filters

There are many ways to specify content to featherlight. Featherlight uses a set of heuristics to determine the type, for example data ending with `.gif` will be assumed to be an image. The following are equivalent:

<a href="#" data-featherlight="photo.gif">See in a lightbox</a>

<a href="photo.gif" data-featherlight>See in a lightbox</a>

<a id="#example" href="#">See in a lightbox</a>
<script>$('#example').featherlight('photo.gif');</script>

In case the heuristic wouldn't work, you can specify which contentFilter to use:

<a href="photo_without_extension" data-featherlight="image">See in a lightbox</a>

<a id="force_as_image" href="photo_without_extension">See in a lightbox</a>
<script>
$('#force_as_image').featherlight('image');
// Equivalent:
$('#force_as_image').featherlight({type: {image: true}});
</script>

<a id="force_as_image2" href="#">See in a lightbox</a>
<script>$('#force_as_image2').featherlight('photo_without_extension', {type:{image: true}});</script>

You can add your own heuristics, for example:

$.featherlight.contentFilters.feed = {
regex: /^feed:/,
process: function(url) { /* deal with url */ return $('Loading...'); }
};
$.featherlight.defaults.contentFilters.unshift('feed');

This way the following would be possible:

<a href="feed://some_url" data-featherlight>See the feed in a lightbox</a>

The content filter 'text' needs to be specified explicitly, it has no heuristic attached to it:

<a href="Hello, world" data-featherlight="text">Example</a>

# Examples

## Use link-hashtags to open lightbox
<a href="#targetElement" class="fl">Open</a>
<div id="targetElement">Lightbox</div>

$('a.fl').featherlight({
targetAttr: 'href'
});

## Open images with Featherlight
Use a link and point the data-featherlight attribute to the desired attribute which contains the link...

<a href="myimage.jpg" data-featherlight="image">Open Image</a>

...or directly provide the link as the data-featherlight attribute:

<a href="#" data-featherlight="myimage.jpg">Open Image</a>

## Open lightbox with ajax content
Use Featherlight with ajax using 'ajax' keyword or providing a url. It even supports selecting elements inside the response document.

<a href="url.html .jQuery-Selector" data-featherlight="ajax">Open Ajax Content</a>

or you can provide the link directly as the featherlight-attribute:

<a href="#" data-featherlight="url.html .jQuery-Selector">Open Ajax Content</a>

## Open lightbox with iframe
Featherlight generates an iframe with the 'iframe' keyword and a given URL.
The default size of the iframe is very small (300 x 150).

<a href="http://www.example.com" data-featherlight="iframe">Open example.com in an iframe</a>

Options:
You can use the following iframe attributes:
`
allow, allowfullscreen, frameborder, height, longdesc, marginheight, marginwidth, mozallowfullscreen, name, referrerpolicy, sandbox, scrolling, src, srcdoc, style, webkitallowfullscreen, width
`

For example, to set the height and width, you would use

data-featherlight-iframe-height="640" data-featherlight-iframe-width="480"

or to set some css style:

data-featherlight-iframe-style="border:none"

You can also set the iframe attributes `iframeWidth`, `iframeMinWidth` etc. using JavaScript:

$.featherlight({iframe: 'editor.html', iframeMaxWidth: '80%', iframeWidth: 500,
iframeHeight: 300});

## Open YouTube video with Featherlight
Featherlight generates an iframe that contains the embedded video.

Display a clickable thumbnail image that opens a video with a fixed size of 640 x 480 and automatically start playback:
`
<a href="http://www.youtube.com/embed/f0BzD1zCye0?rel=0&amp;autoplay=1" data-featherlight="iframe" data-featherlight-iframe-width="640" data-featherlight-iframe-height="480" data-featherlight-iframe-frameborder="0" data-featherlight-iframe-allow="autoplay; encrypted-media" data-featherlight-iframe-allowfullscreen="true">
<img src="http://img.youtube.com/vi/f0BzD1zCye0/0.jpg" alt="" />
</a>
`

A text link that opens a video in a lightbox that is stretched to 85% height and width of the viewport:
`
<a href="http://www.youtube.com/embed/f0BzD1zCye0?rel=0&amp;autoplay=1" data-featherlight="iframe" data-featherlight-iframe-frameborder="0" data-featherlight-iframe-allow="autoplay; encrypted-media" data-featherlight-iframe-allowfullscreen="true" data-featherlight-iframe-style="display:block;border:none;height:85vh;width:85vw;">My video</a>
`

A link that opens a video in a lightbox that fills 100% of the window:
Note: the "close" icon is not visible so this example is not user-friendly.
`
<a href="http://www.youtube.com/embed/f0BzD1zCye0?rel=0&amp;autoplay=1" data-featherlight="iframe" data-featherlight-iframe-frameborder="0" data-featherlight-iframe-allow="autoplay; encrypted-media" data-featherlight-iframe-allowfullscreen="true"
data-featherlight-iframe-style="position:fixed;background:#000;border:none;top:0;right:0;bottom:0;left:0;width:100%;height:100%;">My video</a>
`

# IE8 background transparency
If you want the background in IE8 to be translucent, use data:image before the rgba background:

background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAKQWlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUU9kWh8+9N73QEiIgJfQaegkg0jtIFQRRiUmAUAKGhCZ2RAVGFBEpVmRUwAFHhyJjRRQLg4Ji1wnyEFDGwVFEReXdjGsJ7601896a/cdZ39nnt9fZZ+9917oAUPyCBMJ0WAGANKFYFO7rwVwSE8vE9wIYEAEOWAHA4WZmBEf4RALU/L09mZmoSMaz9u4ugGS72yy/UCZz1v9/kSI3QyQGAApF1TY8fiYX5QKUU7PFGTL/BMr0lSkyhjEyFqEJoqwi48SvbPan5iu7yZiXJuShGlnOGbw0noy7UN6aJeGjjAShXJgl4GejfAdlvVRJmgDl9yjT0/icTAAwFJlfzOcmoWyJMkUUGe6J8gIACJTEObxyDov5OWieAHimZ+SKBIlJYqYR15hp5ejIZvrxs1P5YjErlMNN4Yh4TM/0tAyOMBeAr2+WRQElWW2ZaJHtrRzt7VnW5mj5v9nfHn5T/T3IevtV8Sbsz55BjJ5Z32zsrC+9FgD2JFqbHbO+lVUAtG0GQOXhrE/vIADyBQC03pzzHoZsXpLE4gwnC4vs7GxzAZ9rLivoN/ufgm/Kv4Y595nL7vtWO6YXP4EjSRUzZUXlpqemS0TMzAwOl89k/fcQ/+PAOWnNycMsnJ/AF/GF6FVR6JQJhIlou4U8gViQLmQKhH/V4X8YNicHGX6daxRodV8AfYU5ULhJB8hvPQBDIwMkbj96An3rWxAxCsi+vGitka9zjzJ6/uf6Hwtcim7hTEEiU+b2DI9kciWiLBmj34RswQISkAd0oAo0gS4wAixgDRyAM3AD3iAAhIBIEAOWAy5IAmlABLJBPtgACkEx2AF2g2pwANSBetAEToI2cAZcBFfADXALDIBHQAqGwUswAd6BaQiC8BAVokGqkBakD5lC1hAbWgh5Q0FQOBQDxUOJkBCSQPnQJqgYKoOqoUNQPfQjdBq6CF2D+qAH0CA0Bv0BfYQRmALTYQ3YALaA2bA7HAhHwsvgRHgVnAcXwNvhSrgWPg63whfhG/AALIVfwpMIQMgIA9FGWAgb8URCkFgkAREha5EipAKpRZqQDqQbuY1IkXHkAwaHoWGYGBbGGeOHWYzhYlZh1mJKMNWYY5hWTBfmNmYQM4H5gqVi1bGmWCesP3YJNhGbjS3EVmCPYFuwl7ED2GHsOxwOx8AZ4hxwfrgYXDJuNa4Etw/XjLuA68MN4SbxeLwq3hTvgg/Bc/BifCG+Cn8cfx7fjx/GvyeQCVoEa4IPIZYgJGwkVBAaCOcI/YQRwjRRgahPdCKGEHnEXGIpsY7YQbxJHCZOkxRJhiQXUiQpmbSBVElqIl0mPSa9IZPJOmRHchhZQF5PriSfIF8lD5I/UJQoJhRPShxFQtlOOUq5QHlAeUOlUg2obtRYqpi6nVpPvUR9Sn0vR5Mzl/OX48mtk6uRa5Xrl3slT5TXl3eXXy6fJ18hf0r+pvy4AlHBQMFTgaOwVqFG4bTCPYVJRZqilWKIYppiiWKD4jXFUSW8koGStxJPqUDpsNIlpSEaQtOledK4tE20Otpl2jAdRzek+9OT6cX0H+i99AllJWVb5SjlHOUa5bPKUgbCMGD4M1IZpYyTjLuMj/M05rnP48/bNq9pXv+8KZX5Km4qfJUilWaVAZWPqkxVb9UU1Z2qbapP1DBqJmphatlq+9Uuq43Pp893ns+dXzT/5PyH6rC6iXq4+mr1w+o96pMamhq+GhkaVRqXNMY1GZpumsma5ZrnNMe0aFoLtQRa5VrntV4wlZnuzFRmJbOLOaGtru2nLdE+pN2rPa1jqLNYZ6NOs84TXZIuWzdBt1y3U3dCT0svWC9fr1HvoT5Rn62fpL9Hv1t/ysDQINpgi0GbwaihiqG/YZ5ho+FjI6qRq9Eqo1qjO8Y4Y7ZxivE+41smsImdSZJJjclNU9jU3lRgus+0zwxr5mgmNKs1u8eisNxZWaxG1qA5wzzIfKN5m/krCz2LWIudFt0WXyztLFMt6ywfWSlZBVhttOqw+sPaxJprXWN9x4Zq42Ozzqbd5rWtqS3fdr/tfTuaXbDdFrtOu8/2DvYi+yb7MQc9h3iHvQ732HR2KLuEfdUR6+jhuM7xjOMHJ3snsdNJp9+dWc4pzg3OowsMF/AX1C0YctFx4bgccpEuZC6MX3hwodRV25XjWuv6zE3Xjed2xG3E3dg92f24+ysPSw+RR4vHlKeT5xrPC16Il69XkVevt5L3Yu9q76c+Oj6JPo0+E752vqt9L/hh/QL9dvrd89fw5/rX+08EOASsCegKpARGBFYHPgsyCRIFdQTDwQHBu4IfL9JfJFzUFgJC/EN2hTwJNQxdFfpzGC4sNKwm7Hm4VXh+eHcELWJFREPEu0iPyNLIR4uNFksWd0bJR8VF1UdNRXtFl0VLl1gsWbPkRoxajCCmPRYfGxV7JHZyqffS3UuH4+ziCuPuLjNclrPs2nK15anLz66QX8FZcSoeGx8d3xD/iRPCqeVMrvRfuXflBNeTu4f7kufGK+eN8V34ZfyRBJeEsoTRRJfEXYljSa5JFUnjAk9BteB1sl/ygeSplJCUoykzqdGpzWmEtPi000IlYYqwK10zPSe9L8M0ozBDuspp1e5VE6JA0ZFMKHNZZruYjv5M9UiMJJslg1kLs2qy3mdHZZ/KUcwR5vTkmuRuyx3J88n7fjVmNXd1Z752/ob8wTXuaw6thdauXNu5Tnddwbrh9b7rj20gbUjZ8MtGy41lG99uit7UUaBRsL5gaLPv5sZCuUJR4b0tzlsObMVsFWzt3WazrWrblyJe0fViy+KK4k8l3JLr31l9V/ndzPaE7b2l9qX7d+B2CHfc3em681iZYlle2dCu4F2t5czyovK3u1fsvlZhW3FgD2mPZI+0MqiyvUqvakfVp+qk6oEaj5rmvep7t+2d2sfb17/fbX/TAY0DxQc+HhQcvH/I91BrrUFtxWHc4azDz+ui6rq/Z39ff0TtSPGRz0eFR6XHwo911TvU1zeoN5Q2wo2SxrHjccdv/eD1Q3sTq+lQM6O5+AQ4ITnx4sf4H++eDDzZeYp9qukn/Z/2ttBailqh1tzWibakNml7THvf6YDTnR3OHS0/m/989Iz2mZqzymdLz5HOFZybOZ93fvJCxoXxi4kXhzpXdD66tOTSna6wrt7LgZevXvG5cqnbvfv8VZerZ645XTt9nX297Yb9jdYeu56WX+x+aem172296XCz/ZbjrY6+BX3n+l37L972un3ljv+dGwOLBvruLr57/17cPel93v3RB6kPXj/Mejj9aP1j7OOiJwpPKp6qP6391fjXZqm99Oyg12DPs4hnj4a4Qy//lfmvT8MFz6nPK0a0RupHrUfPjPmM3Xqx9MXwy4yX0+OFvyn+tveV0auffnf7vWdiycTwa9HrmT9K3qi+OfrW9m3nZOjk03dp76anit6rvj/2gf2h+2P0x5Hp7E/4T5WfjT93fAn88ngmbWbm3/eE8/syOll+AAAACXBIWXMAAAsTAAALEwEAmpwYAAADpmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDEzLTExLTA2VDIyOjExOjgxPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5QaXhlbG1hdG9yIDMuMDwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICAgICA8dGlmZjpDb21wcmVzc2lvbj41PC90aWZmOkNvbXByZXNzaW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4xPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj43MjwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+NzI8L3RpZmY6WFJlc29sdXRpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4xMDwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOkNvbG9yU3BhY2U+MTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+MTA8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KbQS91AAAAB1JREFUGBljZGBgOA7EBAETQRVQBaMK8YYU0cEDAE1HANt9zybzAAAAAElFTkSuQmCC);
background: rgba(0, 0, 0, 0.8);

---

# Featherlight Gallery
You will need to use an extension (featherlight.gallery.js). Since Featherlight was created to be as small and simple as possible, it has selected functionality, and allows you to add additional functionality by using extensions. featherlight.gallery.js is a small extension that turns your set of links into a [gallery](http://noelboss.github.io/featherlight/gallery.html).

## Gallery installation

Simply include the extension CSS and JavaScript Files after the regular featherlight files like this:

<link href="//cdn.rawgit.com/noelboss/featherlight/1.7.13/release/featherlight.min.css" type="text/css" rel="stylesheet" />
<link href="//cdn.rawgit.com/noelboss/featherlight/1.7.13/release/featherlight.gallery.min.css" type="text/css" rel="stylesheet" />

Add the JavaScript at the bottom of the body:

```html
<script src="//code.jquery.com/jquery-latest.js"></script>
<script src="//cdn.rawgit.com/noelboss/featherlight/1.7.13/release/featherlight.min.js" type="text/javascript" charset="utf-8"></script>
<script src="//cdn.rawgit.com/noelboss/featherlight/1.7.13/release/featherlight.gallery.min.js" type="text/javascript" charset="utf-8"></script>
```

Check out the example here: [Gallery with Featherlight](gallery.html)


## Gallery configuration

The gallery also has a range of configuration options and the following defaults:


```javascript
$('a.gallery').featherlightGallery({
previousIcon: '&#9664;', /* Code that is used as previous icon */
nextIcon: '&#9654;', /* Code that is used as next icon */
galleryFadeIn: 100, /* fadeIn speed when slide is loaded */
galleryFadeOut: 300 /* fadeOut speed before slide is loaded */
});
```


It also overrides its `autoBind` global option:

```javascript
autoBind: '[data-featherlight-gallery]' /* Will automatically bind elements matching this selector. Clear or set before onReady */
```

Example in pure HTML:

```html
<section
data-featherlight-gallery
data-featherlight-filter="a"
>
<h>This is a gallery</h>
<a href="photo_large.jpg"><img src="photo_thumbnail.jpg"></a>
<a href="other_photo_large.jpg"><img src="other_photo_thumbnail.jpg"></a>
</section>
```

Example in JavaScript (assuming there are `a` tags of class `gallery` in the page):

```javascript
$('a.gallery').featherlightGallery({
previousIcon: '«',
nextIcon: '»',
galleryFadeIn: 300,

openSpeed: 300
});
```

The gallery responds to custom events `previous` and `next` to navigate to the previous and next images.

Instead of navigation buttons it will use swipe events on touch devices, assuming that one of the [supported swipe libraries](https://github.com/noelboss/featherlight/wiki/Gallery:-swipe-on-touch-devices) is also installed.

It sets the classes `'featherlight-first-slide'` and `'featherlight-last-slide'` if the current slide is the first and/or last one.

## Gallery installation

Simply include the extension CSS and JavaScript Files after the regular featherlight files like this:

<link href="//cdn.rawgit.com/noelboss/featherlight/1.7.13/release/featherlight.min.css" type="text/css" rel="stylesheet" />
<link href="//cdn.rawgit.com/noelboss/featherlight/1.7.13/release/featherlight.gallery.min.css" type="text/css" rel="stylesheet" />
<script src="//code.jquery.com/jquery-latest.js"></script>
<script src="//cdn.rawgit.com/noelboss/featherlight/1.7.13/release/featherlight.min.js" type="text/javascript" charset="utf-8"></script>
<script src="//cdn.rawgit.com/noelboss/featherlight/1.7.13/release/featherlight.gallery.min.js" type="text/javascript" charset="utf-8"></script>

## Gallery on Mobile Devices
To support mobile/tablet and all touch devices, you will need to include one of the [supported swipe libraries](https://github.com/noelboss/featherlight/wiki/Gallery:-swipe-on-touch-devices). For example, to use `swipe_detect` library, include it in the header:

```html
<script src="//cdnjs.cloudflare.com/ajax/libs/detect_swipe/2.1.1/jquery.detect_swipe.min.js"></script>
```


# Support

## Questions

For questions, please use [Stack Overflow](http://stackoverflow.com/questions/ask) and be sure to use the `featherlight.js` tag. Please **provide an example**, starting for example from [this jsfiddle](http://jsfiddle.net/JNsu6/15/)

## Reporting bugs and issues

If you believe you've found a bug, please open an issue on [Github](https://github.com/noelboss/featherlight/issues/new) and **provide an example** starting from [this jsfiddle](http://jsfiddle.net/JNsu6/15/).

## Pull requests

Pull requests are welcome (good tips can be found on [Stack Overflow](http://stackoverflow.com/questions/14680711/how-to-do-a-github-pull-request))

To run the tests, you can open `test/featherlight.html` or `test/featherlight_gallery.html` in your browser.
Alternatively, run them from the console with `grunt test`; you will need to run `npm install` the first time.

BIN
rsc/img/back01.JPG 파일 보기

Before After
Width: 4608  |  Height: 3456  |  Size: 3.2MB

BIN
rsc/img/default.png 파일 보기

Before After
Width: 170  |  Height: 164  |  Size: 26KB

+ 2
- 0
rsc/jquery-3.3.1.min.js
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
파일 보기


+ 6
- 0
rsc/sass/_admin.scss 파일 보기

@@ -0,0 +1,6 @@
#admin-panel {
position: fixed;
bottom: 1em; right: 1em;
width:400px;height:300px;
background: #fa0;
}

+ 18
- 0
rsc/sass/_colours.scss 파일 보기

@@ -0,0 +1,18 @@
$white:#fff;
$black:#000;
$gray:#777;

//$OFS-green-dark: #67635b;
//$OFS-green-lighter: #989186;

$spkt01:#0ca4bf;
$spkt02:#bf920c;
$spkt03: $gray;
$spkt04:#bf3b0c;
$spkt04:#f7b96c;

html {
color:$gray;
background:$white;
}

+ 7
- 0
rsc/sass/_design.scss 파일 보기

@@ -0,0 +1,7 @@
a {
color: hsl(0,0,0);
text-decoration:none;
&:hover {
text-decoration:underline;
}
}

+ 13
- 0
rsc/sass/_dev.scss 파일 보기

@@ -0,0 +1,13 @@
#page-wrap {
background:#eee;

#main-content { background:#ffe; }
#sidebar { background:#fef;}
#content { background:#efe;}
#main-nav { background:#eff;}
#main-footer { background:#fee;}
}

// Temporary items


+ 77
- 0
rsc/sass/_extras.scss 파일 보기

@@ -0,0 +1,77 @@
/////////////////////////////////////////////////////////////////////
// Link design
// Copied 2019-01-30 from https://codepen.io/jimmynotjim/pen/EabQjV
// and edited to suit by dSP
@mixin text-underline-crop($background) {
text-shadow: .03em 0 $background,
-.03em 0 $background,
0 .03em $background,
0 -.03em $background,
.06em 0 $background,
-.06em 0 $background,
.09em 0 $background,
-.09em 0 $background,
.12em 0 $background,
-.12em 0 $background,
.15em 0 $background,
-.15em 0 $background;
}

@mixin text-background($color-bg, $color-text) {
position: relative;
z-index:0;
&::after {
content: "";
top: 0;
left: 0;
bottom: 0;
right: 0;
position: absolute;
z-index: -1;
opacity:1;
transition: opacity 1s ease;
background-image: linear-gradient($color-text, $color-text);
background-size: 1px 0.5px;
background-repeat: repeat-x;
background-position: 0% 95%;
}
&:hover {
&::after {
opacity: 0;
}
}
}

@mixin text-selection($selection) {
&::selection {
@include text-underline-crop($selection);
background: $selection;
}

&::-moz-selection {
@include text-underline-crop($selection);
background: $selection;
}
}

@mixin link-underline($background, $text, $selection){
@include text-underline-crop($background);
@include text-background($background, $text);
@include text-selection($selection);

color: $text;
text-decoration: none;

*,
*:after,
&:after,
*:before,
&:before {
text-shadow: none;
}
&:visited {
color: $text;
}

}

+ 18
- 0
rsc/sass/_fonts.scss 파일 보기

@@ -0,0 +1,18 @@
// fonts
//@import url('https://fonts.googleapis.com/css?family=Poiret+One');
$poiret1:'Poiret One', cursive;
$georgia:georgia, serif;
$verdana:verdana, sans;

$cursive: cursive;//$poiret1;
$serif: $georgia;
$sans: $verdana;


.card, .item {
// font-family: $sans;
h2 {
font-family: $serif;
}
}

+ 130
- 0
rsc/sass/_structure.scss 파일 보기

@@ -0,0 +1,130 @@
$below-banner:2em;

// reset
body,h1,h2,h3,h4,h5,h6,ul {
margin:0;
padding:0;
}

#main-nav {
//dev:
// display: none;
@media #{$view-mobile} {
display: none;
}
}

//////////////////////////////////////////////
// basic site layout

h1 { margin: .5em 0 0 1em; }

.card {
max-width:600px;
img { width: 100%; }
&::after {
content:"";
clear: both;
display:table;
}
}

#page-wrap {
}
///////////////////////////////////////////////


.featherlight .featherlight-content {
background: none;
overflow: visible;
.featherlight-close-icon {
background: none;
top: -10px;
right: -10px;
color: $white;
font-size: 1.5em;
}
@media #{$view_mobile} {
iframe { width:100%; height:100%;}
}
}

/////////////////////////////////////////////////////////////////////
#mobile-nav {
display: none;
@media #{$view-mobile} {
display: block;
// dev
display: none;
a.menu-toggle {
display: block;
}
}
font-family: $sans;
font-size:1.2em;
.the_list {
.home-link,
>ul:last-child {
font-family:$cursive;
font-size: 1.4em;
margin-top: -.7em;
color: $spkt02;
a {
color: $spkt02;
display: inline-block;
margin-top: 8px;
}
}
.home-link {
display: inline-block;
text-transform: lowercase;
margin-bottom: 8px;
}
display:none;
padding: 2em;
ul { margin-bottom: 1.7em; }
li {
margin:0.5em;
list-style:none;
ul { margin-bottom: 1em; }
}
a {
color:$black;
text-decoration:none;
font-weight: normal;
}
.active>a {
color:$spkt02;
font-weight: bold;
}
}
&.open .the_list {
display:block;
position:fixed;
top:0;
left:0;
z-index:99;
background: rgba(255,255,255,0.9);
height:100%;
width: 100%;
overflow:scroll;
}
a.menu-toggle {
position:fixed;
top:0;
right:0;
width:50px;
height:50px;
z-index:100;
cursor:pointer;
background:$white;
background: rgba(255,255,255,0.9);
background-image: url('img/menu-icon.png');
background-position: top;
}
&.open a.menu-toggle {
background-position:bottom;
}
}


+ 13
- 0
rsc/sass/_susy-prefix.scss 파일 보기

@@ -0,0 +1,13 @@
// Susy (Prefixed)
// ===============


@import 'version';
@import 'susy/utilities';
@import 'susy/su-validate';
@import 'susy/su-math';
@import 'susy/settings';
@import 'susy/normalize';
@import 'susy/parse';
@import 'susy/syntax-helpers';
@import 'susy/api';

+ 5
- 0
rsc/sass/_susy.scss 파일 보기

@@ -0,0 +1,5 @@
// Susy (Un-Prefixed)
// ==================

@import 'susy-prefix';
@import 'susy/unprefix';

+ 53
- 0
rsc/sass/_version.scss 파일 보기

@@ -0,0 +1,53 @@
// Release Management in Susy
// ==========================


// Susy Version [variable]
// -----------------------
/// The current version of Susy being used.
/// - We will release a major version for any BREAKING changes.
/// - We will release a minor version for any significant NEW features.
/// - We will release a patch for any BUGFIX changes.
///
/// @group _version
/// @access private
/// @since 3.0.1
///
/// @prop {integer} 'major' - the major release number
/// @prop {integer} 'minor' - the minor release number
/// @prop {integer} 'patch' - the patch number
$_susy-version: (
'major': 3,
'minor': 0,
'patch': 1,
);


// Susy Version [function]
// -----------------------
/// Returns the current version of Susy
/// as a string in the common `major.minor.patch` format –
/// or returns one part (major | minor | patch) as a number
/// for version comparisons.
/// Since version numbers aren't actual decimals,
/// there is no simple way to return the full version
/// as a comparable number in Sass.
///
/// @group _version
///
/// @param {'major' | 'minor' | 'patch'} $part [null] -
/// The part (major | minor | patch) to return as a number.
/// Any other value will return the full version as a string.
/// @example scss - Current Susy Version
/// /* Full Version: #{susy-version()} */
/// /* Major Release: #{susy-version('major')} */
@function susy-version(
$part: null
) {
$major: map-get($_susy-version, 'major');
$minor: map-get($_susy-version, 'minor');
$patch: map-get($_susy-version, 'patch');
$full: '#{$major}.#{$minor}.#{$patch}';

@return map-get($_susy-version, $part) or $full;
}

+ 40
- 0
rsc/sass/main.scss 파일 보기

@@ -0,0 +1,40 @@
//@import 'colors';

@import "susy";
$susy: (
'columns': susy-repeat(8, 202px),
'gutters': 1em,
'spread': 'narrow',
'container-spread': 'narrow',
);

* {
box-sizing:border-box;
}
$SiteMaxWidth: 1000px;
$BannerMinWidth:750px;




$view_biggest: "only screen and (min-width : 1000px)";
$view_compact: "only screen and (max-width : 950px)";
$view_mobile: "only screen and (max-width : 750px)";
$view_not_mobile: "only screen and (min-width : 751px)";
$view_smallest_banner: "only screen and (max-width : 750px)";


@import "colours";
@import "fonts";
@import "structure";
@import "design";
@import "admin";

//@import "dev";


// mobile query:
$mobile-device: "only screen and (max-width : 800px)";
$not-mobile: "only screen and (min-width : 801px)";


+ 318
- 0
rsc/sass/susy/_api.scss 파일 보기

@@ -0,0 +1,318 @@
/// Susy3 API Functions
/// ===================
/// These three functions form the core of Susy's
/// layout-building grid API.
///
/// - Use `span()` and `gutter()` to return any grid-width,
/// and apply the results wherever you need them:
/// CSS `width`, `margin`, `padding`, `flex-basis`, `transform`, etc.
/// - For asymmetrical-fluid grids,
/// `slice()` can help manage your nesting context.
///
/// All three functions come with an unprefixed alias by default,
/// using the `susy` import.
/// Import the `susy-prefix` partial instead,
/// if you only only want prefixed versions of the API.
///
/// This is a thin syntax-sugar shell around
/// the "Su" core-math functions: `su-span`, `su-gutter`, and `su-slice`.
/// If you prefer the more constrained syntax of the math engine,
/// you are welcome to use those functions instead.
///
/// @group api
/// @see susy-span
/// @see susy-gutter
/// @see susy-slice
/// @see su-span
/// @see su-gutter
/// @see su-slice



/// ## Shorthand
///
/// All functions draw on the same shorthand syntax in two parts,
/// seperated by the word `of`.
///
/// ### Span Syntax: `<width>` [`<location>` `<spread>`]
/// The first part describes the
/// **span** width, location, and spread in any order.
/// Only the width is required:
///
/// - `span(2)` will return the width of 2 columns.
/// - `span(3 wide)` will return 3-columns, with an additional gutter.
/// - location is only needed with asymmetrical grids,
/// where `span(3 at 2)` will return the width of
/// specific columns on the grid.
/// Since these are functions, they will not handle placement for you.
///
/// ### Context Syntax: `[of <columns> <container-spread> <gutters>]`
/// The second half of Susy's shorthand
/// describes the grid-**context** –
/// available columns, container-spread, and optional gutter override –
/// in any order.
/// All of these settings have globally-defined defaults:
///
/// - `span(2 of 6)` will set the context to
/// a slice of 6 columns from the global grid.
/// More details below.
/// - `span(2 of 12 wide)` changes the container-spread
/// as well as the column-context.
/// - `span(2 of 12 set-gutters 0.5em)`
/// will override the global gutters setting
/// for this one calculation.
///
/// A single unitless number for `columns`
/// will be treated as a slice of the parent grid.
/// On a grid with `columns: susy-repeat(12, 120px)`,
/// the shorthand `of 4` will use the parent `120px` column-width.
/// You can also be more explicit,
/// and say `of susy-repeat(4, 100px)`.
/// If you are using asymmetrical grids,
/// like `columns: (1 1 2 3 5 8)`,
/// Susy can't slice it for you without knowing which columns you want.
/// The `slice` function accepts exactly the same syntax as `span`,
/// but returns a list of columns rather than a width.
/// Use it in your context like `of slice(first 3)`.
///
/// @group api



// Susy Span
// ---------
/// This is the primary function in Susy —
/// used to return the width of a span across one or more columns,
/// and any relevant gutters along the way.
/// With the default settings,
/// `span(3)` will return the width of 3 columns,
/// and the 2 intermediate gutters.
/// This can be used to set the `width` property of grid elements,
/// or `margin` and `padding`
/// to push, pull, and pad your elements.
///
/// - This is a thin syntax-sugar shell around
/// the core-math `su-span()` function.
/// - The un-prefixed alias `span()` is available by default.
///
/// @group api
/// @see su-span
/// @see $susy
///
/// @param {list} $span -
/// Shorthand expression to define the width of the span,
/// optionally containing:
/// - a count, length, or column-list span.
/// - `at $n`, `first`, or `last` location on asymmetrical grids,
/// where `at 1 == first`,
/// and `last` will calculate the proper location
/// based on columns and span.
/// - `narrow`, `wide`, or `wider` for optionally spreading
/// across adjacent gutters.
/// - `of $n <spread>` for available grid columns
/// and spread of the container.
/// Span counts like `of 6` are valid
/// in the context of symmetrical grids,
/// where Susy can safely infer a slice of the parent columns.
/// - and `set-gutters $n` to override global gutter settings.
///
/// @param {map} $config [()] -
/// Optional map of Susy grid configuration settings.
/// See `$susy` documentation for details.
///
/// @return {length} -
/// Calculated length value, using the units given,
/// or converting to `%` for fraction-based grids,
/// or a full `calc` function when units/fractions
/// are not comparable outside the browser.
///
/// @example scss - span half the grid
/// .foo {
/// // the result is a bit under 50% to account for gutters
/// width: susy-span(6 of 12);
/// }
///
/// @example scss - span a specific segment of asymmetrical grid
/// .foo {
/// width: susy-span(3 at 3 of (1 2 3 5 8));
/// }
@function susy-span(
$span,
$config: ()
) {
$output: susy-compile($span, $config);

@if map-get($output, 'span') {
@return su-call('su-span', $output);
}

$actual: '[#{type-of($span)}] `#{inspect($span)}`';
@return _susy-error(
'Unable to determine span value from #{$actual}.',
'susy-span');
}



// Susy Gutter
// -----------
/// The gutter function returns
/// the width of a single gutter on your grid,
/// to be applied where you see fit –
/// on `margins`, `padding`, `transform`, or element `width`.
///
/// - This is a thin syntax-sugar shell around
/// the core-math `su-gutter()` function.
/// - The un-prefixed alias `gutter()` is available by default.
///
/// @group api
/// @see su-gutter
/// @see $susy
///
/// @param {list | number} $context [null] -
/// Optional context for nested gutters,
/// including shorthand for
/// `columns`, `gutters`, and `container-spread`
/// (additional shorthand will be ignored)
///
/// @param {map} $config [()] -
/// Optional map of Susy grid configuration settings.
/// See `$susy` documentation for details.
///
/// @return {length} -
/// Width of a gutter as `%` of current context,
/// or in the units defined by `column-width` when available
///
/// @example scss - add gutters before or after an element
/// .floats {
/// float: left;
/// width: span(3 of 6);
/// margin-left: gutter(of 6);
/// }
///
/// @example scss - add gutters to padding
/// .flexbox {
/// flex: 1 1 span(3 wide of 6 wide);
/// padding: gutter(of 6) / 2;
/// }
///
@function susy-gutter(
$context: susy-get('columns'),
$config: ()
) {
$context: susy-compile($context, $config, 'context-only');

@return su-call('su-gutter', $context);
}



// Susy Slice
// ----------
/// Working with asymmetrical grids (un-equal column widths)
/// can be challenging – 
/// expecially when they involve fluid/fractional elements.
/// Describing a context `of (15em 6em 6em 6em 15em)` is a lot
/// to put inside the span or gutter function shorthand.
/// This slice function returns a sub-slice of asymmetrical columns to use
/// for a nested context.
/// `slice(3 at 2)` will give you a subset of the global grid,
/// spanning 3 columns, starting with the second.
///
/// - This is a thin syntax-sugar shell around
/// the core-math `su-slice()` function.
/// - The un-prefixed alias `slice()` is available by default.
///
/// @group api
/// @see su-slice
/// @see $susy
///
/// @param {list} $span -
/// Shorthand expression to define the subset span, optionally containing:
/// - `at $n`, `first`, or `last` location on asymmetrical grids;
/// - `of $n <spread>` for available grid columns
/// and spread of the container
/// - Span-counts like `of 6` are only valid
/// in the context of symmetrical grids
/// - Valid spreads include `narrow`, `wide`, or `wider`
///
/// @param {map} $config [()] -
/// Optional map of Susy grid configuration settings.
/// See `$susy` documentation for details.
///
/// @return {list} -
/// Subset list of columns for use for a nested context
///
/// @example scss - Return a nested segment of asymmetrical grid
/// $context: susy-slice(3 at 3 of (1 2 3 5 8));
/// /* $context: #{$context}; */
@function susy-slice(
$span,
$config: ()
) {
$span: susy-compile($span, $config);

@return su-call('su-slice', $span);
}



/// ## Building Grids
/// The web has come a long way
/// since the days of double-margin-hacks
/// and inconsistent subpixel rounding.
/// In addition to floats and tables,
/// we can now use much more powerful tools,
/// like flexbox and CSS grid,
/// to build more interesting and responsive layouts.
///
/// With Susy3, we hope you'll start moving in that direction.
/// You can still build classic 12-column Grid Systems,
/// and we'll help you get there,
/// but Susy3 is primarily designed for a grid-math-on-demand
/// approach to layout:
/// applying our functions only where you really need grid math.
/// Read the [intro article by OddBird][welcome] for more details.
///
/// [welcome]: http://oddbird.net/2017/06/28/susy3/
///
/// @group api
/// @link http://oddbird.net/2017/06/28/susy3/ Article: Welcome to Susy3
///
/// @example scss - floats
/// .float {
/// width: span(3);
/// margin-right: gutter();
/// }
///
/// @example scss - flexbox
/// .flexbox {
/// flex: 1 1 span(3);
/// // half a gutter on either side…
/// padding: 0 gutter() / 2;
/// }
///
/// @example scss - pushing and pulling
/// .push-3 {
/// margin-left: span(3 wide);
/// }
///
/// .pull-3 {
/// margin-left: 0 - span(3 wide);
/// }
///
/// @example scss - building an attribute system
/// // markup example: <div data-span="last 3"></div>
/// [data-span] {
/// float: left;
///
/// &:not([data-span*='last']) {
/// margin-right: gutter();
/// }
/// }
///
/// @for $span from 1 through length(susy-get('columns')) {
/// [data-span*='#{$span}'] {
/// width: span($span);
/// }
/// }

+ 261
- 0
rsc/sass/susy/_normalize.scss 파일 보기

@@ -0,0 +1,261 @@
/// Syntax Normalization
/// ====================
/// Susy is divided into two layers:
/// "Su" provides the core math functions with a stripped-down syntax,
/// while "Susy" adds global settings, shorthand syntax,
/// and other helpers.
/// Each setting (e.g. span, location, columns, spread, etc.)
/// has a single canonical syntax in Su.
///
/// This normalization module helps translate between those layers,
/// transforming parsed Susy input into
/// values that Su will understand.
///
/// @group _normal
///
/// @see susy-normalize
/// @see susy-normalize-span
/// @see susy-normalize-columns
/// @see susy-normalize-spread
/// @see susy-normalize-location



// Susy Normalize
// --------------
/// Normalize the values in a configuration map.
/// In addition to the global `$susy` properties,
/// this map can include local span-related imformation,
/// like `span` and `location`.
///
/// Normalization does not check that values are valid,
/// which will happen in the Su math layer.
/// These functions merely look for known Susy syntax –
/// returning a map with those shorthand values
/// converted into low-level data for Su.
/// For example `span: all` and `location: first`
/// will be converted into specific numbers.
///
/// @group _normal
/// @see $susy
/// @see susy-parse
///
/// @param {map} $config -
/// Map of Susy configuration settings to normalize.
/// See `$susy` and `susy-parse()` documentation for details.
/// @param {map | null} $context [null] -
/// Map of Susy configuration settings to use as global reference,
/// or `null` to use global settings.
///
/// @return {map} -
/// Map of Susy configuration settings,
/// with all values normalized for Su math functions.
@function susy-normalize(
$config,
$context: null
) {
// Spread
@each $setting in ('spread', 'container-spread') {
$value: map-get($config, $setting);

@if $value {
$value: susy-normalize-spread($value);
$config: map-merge($config, ($setting: $value));
}
}

// Columns
$columns: map-get($config, 'columns');

@if $columns {
$columns: susy-normalize-columns($columns, $context);
$config: map-merge($config, ('columns': $columns));
}

@if not $columns {
$map: type-of($context) == 'map';
$columns: if($map, map-get($context, 'columns'), null);
$columns: $columns or susy-get('columns');
}

// Span
$span: map-get($config, 'span');

@if $span {
$span: susy-normalize-span($span, $columns);
$config: map-merge($config, ('span': $span));
}

// Location
$location: map-get($config, 'location');

@if $location {
$location: susy-normalize-location($span, $location, $columns);
$config: map-merge($config, ('location': $location));
}

@return $config;
}



// Normalize Span
// --------------
/// Normalize `span` shorthand for Su.
/// Su span syntax allows an explicit length (e.g. `3em`),
/// unitless column-span number (e.g. `3` columns),
/// or an explicit list of columns (e.g. `(3 5 8)`).
///
/// Susy span syntax also allows the `all` keyword,
/// which will be converted to a slice of the context
/// in normalization.
///
/// @group _normal
///
/// @param {number | list | 'all'} $span -
/// Span value to normalize.
/// @param {list} $columns -
/// Normalized list of columns in the grid
///
/// @return {number | list} -
/// Number or list value for `$span`
@function susy-normalize-span(
$span,
$columns: susy-get('columns')
) {
@if ($span == 'all') {
@return length($columns);
}

@return $span;
}



// Normalize Columns
// -----------------
/// Normalize `column` shorthand for Su.
/// Su column syntax only allows column lists (e.g. `120px 1 1 1 120px`).
///
/// Susy span syntax also allows a unitless `slice` number (e.g `of 5`),
/// which will be converted to a slice of the context
/// in normalization.
///
/// @group _normal
///
/// @param {list | integer} $columns -
/// List of available columns,
/// or unitless integer representing a slice of
/// the available context.
/// @param {map | null} $context [null] -
/// Map of Susy configuration settings to use as global reference,
/// or `null` to access global settings.
///
/// @return {list} -
/// Columns list value, normalized for Su input.
///
/// @throws
/// when attempting to access a slice of asymmetrical context
@function susy-normalize-columns(
$columns,
$context: null
) {
$context: $context or susy-settings();

@if type-of($columns) == 'list' {
@return _susy-flatten($columns);
}

@if (type-of($columns) == 'number') and (unitless($columns)) {
$span: $columns;
$context: map-get($context, 'columns');
$symmetrical: susy-repeat(length($context), nth($context, 1));

@if ($context == $symmetrical) {
@return susy-repeat($span, nth($context, 1));
} @else {
$actual: 'of `#{$span}`';
$columns: 'grid-columns `#{$context}`';
@return _susy-error(
'context-slice #{$actual} can not be determined based on #{$columns}.',
'susy-normalize-columns');
}
}

@return $columns;
}



// Normalize Spread
// ----------------
/// Normalize `spread` shorthand for Su.
/// Su spread syntax only allows the numbers `-1`, `0`, or `1` –
/// representing the number of gutters covered
/// in relation to columns spanned.
///
/// Susy spread syntax also allows keywords for each value –
/// `narrow` for `-1`, `wide` for `0`, or `wider` for `1` –
/// which will be converted to their respective integers
/// in normalization.
///
/// @group _normal
///
/// @param {0 | 1 | -1 | 'narrow' | 'wide' | 'wider'} $spread -
/// Spread across adjacent gutters, relative to a column-count —
/// either `narrow` (-1), `wide` (0), or `wider` (1)
///
/// @return {number} -
/// Numeric value for `$spread`
@function susy-normalize-spread(
$spread
) {
$normal-spread: (
'narrow': -1,
'wide': 0,
'wider': 1,
);

@return map-get($normal-spread, $spread) or $spread;
}



// Normalize Location
// ------------------
/// Normalize `location` shorthand for Su.
/// Su location syntax requires the (1-indexed) number for a column.
///
/// Susy also allows the `first` and `last` keywords,
/// where `first` is always `1`,
/// and `last` is calculated based on span and column values.
/// Both keywords are normalized into an integer index
/// in normalization.
///
/// @group _normal
///
/// @param {number} $span -
/// Number of grid-columns to be spanned
/// @param {integer | 'first' | 'last'} $location -
/// Starting (1-indexed) column position of a span,
/// or a named location keyword.
/// @param {list} $columns -
/// Already-normalized list of columns in the grid.
///
/// @return {integer} -
/// Numeric value for `$location`
@function susy-normalize-location(
$span,
$location,
$columns
) {
$count: length($columns);
$normal-locations: (
'first': 1,
'alpha': 1,
'last': $count - $span + 1,
'omega': $count - $span + 1,
);

@return map-get($normal-locations, $location) or $location;
}

+ 163
- 0
rsc/sass/susy/_parse.scss 파일 보기

@@ -0,0 +1,163 @@
/// Shorthand Syntax Parser
/// =======================
/// The syntax parser converts [shorthand syntax][short]
/// into a map of settings that can be compared/merged with
/// other config maps and global setting.
///
/// [short]: api.html
///
/// @group _parser



// Parse
// -----
/// The `parse` function provides all the syntax-sugar in Susy,
/// converting user shorthand
/// into a usable map of keys and values
/// that can be normalized and passed to Su.
///
/// @group _parser
/// @see $susy
///
/// @param {list} $shorthand -
/// Shorthand expression to define the width of the span,
/// optionally containing:
/// - a count, length, or column-list span;
/// - `at $n`, `first`, or `last` location on asymmetrical grids;
/// - `narrow`, `wide`, or `wider` for optionally spreading
/// across adjacent gutters;
/// - `of $n <spread>` for available grid columns
/// and spread of the container
/// (span counts like `of 6` are only valid
/// in the context of symmetrical grids);
/// - and `set-gutters $n` to override global gutter settings
/// @param {bool} $context-only [false] -
/// Allow the parser to ignore span and span-spread values,
/// only parsing context and container-spread.
/// This makes it possible to accept spanless values,
/// like the `gutters()` syntax.
/// When parsing context-only,
/// the `of` indicator is optional.
///
/// @return {map} -
/// Map of span and grid settings
/// parsed from shorthand input –
/// including all the properties available globally –
/// `columns`, `gutters`, `spread`, `container-spread` –
/// along with the span-specific properties
/// `span`, and `location`.
///
/// @throw
/// when a shorthand value is not recognized
@function susy-parse(
$shorthand,
$context-only: false
) {
$parse-error: 'Unknown shorthand property:';
$options: (
'first': 'location',
'last': 'location',
'alpha': 'location',
'omega': 'location',
'narrow': 'spread',
'wide': 'spread',
'wider': 'spread',
);

$return: ();
$span: null;
$columns: null;

$of: null;
$next: false;

// Allow context-only shorthand, without span
@if ($context-only) and (not index($shorthand, 'of')) {
@if su-valid-columns($shorthand, 'fail-silent') {
$shorthand: 'of' $shorthand;
} @else {
$shorthand: join('of', $shorthand);
}
}

// loop through the shorthand list
@for $i from 1 through length($shorthand) {
$item: nth($shorthand, $i);
$type: type-of($item);
$error: false;
$details: '[#{$type}] `#{$item}`';

// if we know what's supposed to be coming next…
@if $next {

// Add to the return map
$return: map-merge($return, ($next: $item));

// Reset next to `false`
$next: false;

} @else { // If we don't know what's supposed to be coming…

// Keywords…
@if ($type == 'string') {
// Check the map for keywords…
@if map-has-key($options, $item) {
$setting: map-get($options, $item);

// Spread could be on the span or the container…
@if ($setting == 'spread') and ($of) {
$return: map-merge($return, ('container-spread': $item));
} @else {
$return: map-merge($return, ($setting: $item));
}

} @else if ($item == 'all') {
// `All` is a span shortcut
$span: 'all';
} @else if ($item == 'at') {
// Some keywords setup what's next…
$next: 'location';
} @else if ($item == 'set-gutters') {
$next: 'gutters';
} @else if ($item == 'of') {
$of: true;
} @else {
$error: true;
}

} @else if ($type == 'number') or ($type == 'list') { // Numbers & lists…

@if not ($span or $of) {
// We don't have a span, and we're not expecting context…
$span: $item;
} @else if ($of) and (not $columns) {
// We are expecting context…
$columns: $item;
} @else {
$error: true;
}

} @else {
$error: true;
}
}

@if $error {
@return _susy-error('#{$parse-error} #{$details}', 'susy-parse');
}
}

// If we have span, merge it in
@if $span {
$return: map-merge($return, ('span': $span));
}

// If we have columns, merge them in
@if $columns {
$return: map-merge($return, ('columns': $columns));
}

// Return the map of settings…
@return $return;
}

+ 329
- 0
rsc/sass/susy/_settings.scss 파일 보기

@@ -0,0 +1,329 @@
/// Susy3 Configuration
/// ===================
/// Susy3 has 4 core settings, in a single settings map.
/// You'll notice a few differences from Susy2:
///
/// **Columns** no longer accept a single number, like `12`,
/// but use a syntax more similar to the new
/// CSS [grid-template-columns][columns] –
/// a list of relative sizes for each column on the grid.
/// Unitless numbers in Susy act very similar to `fr` units in CSS,
/// and the `susy-repeat()` function (similar to the css `repeat()`)
/// helps quickly establish equal-width columns.
///
/// [columns]: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns
///
/// - `susy-repeat(12)` will create 12 fluid, equal-width columns
/// - `susy-repeat(6, 120px)` will create 6 equal `120px`-wide columns
/// - `120px susy-repeat(4) 120px` will create 6 columns,
/// the first and last are `120px`,
/// while the middle 4 are equal fractions of the remainder.
/// Susy will output `calc()` values in order to achieve this.
///
/// **Gutters** haven't changed –
/// a single fraction or explicit width –
/// but the `calc()` output feature
/// means you can now use any combination of units and fractions
/// to create static-gutters on a fluid grid, etc.
///
/// **Spread** existed in the Susy2 API as a span option,
/// and was otherwise handled behind the scenes.
/// Now we're giving you full control over all spread issues.
/// You can find a more [detailed explanation of spread on the blog][spread].
///
/// [spread]: http://oddbird.net/2017/06/13/susy-spread/
///
/// You can access your global settings at any time
/// with the `susy-settings()` function,
/// or grab a single setting from the global scope
/// with `susy-get('columns')`, `susy-get('gutters')` etc.
///
/// @group config
/// @link http://oddbird.net/2017/06/13/susy-spread/
/// Article: Understanding Spread in Susy3
///
/// @see $susy
/// @see susy-settings
/// @see susy-get



// Susy
// ----
/// The grid is defined in a single map variable,
/// with four initial properties:
/// `columns`, `gutters`, `spread` and `container-spread`.
/// Anything you put in the root `$susy` variable map
/// will be treated as a global project default.
/// You can create similar configuration maps
/// under different variable names,
/// to override the defaults as-needed.
///
/// @group config
/// @type Map
///
/// @see $_susy-defaults
/// @see {function} susy-repeat
/// @link
/// https://codepen.io/mirisuzanne/pen/EgmJJp?editors=1100
/// Spread examples on CodePen
///
/// @prop {list} columns -
/// Columns are described by a list of numbers,
/// representing the relative width of each column.
/// The syntax is a simplified version of CSS native
/// `grid-template-columns`,
/// expecting a list of grid-column widths.
/// Unitless numbers create fractional fluid columns
/// (similar to the CSS-native `fr` unit),
/// while length values (united numbers)
/// are used to define static columns.
/// You can mix-and match units and fractions,
/// to create a mixed grid.
/// Susy will generate `calc()` values when necessary,
/// to make all your units work together.
///
/// Use the `susy-repeat($count, $value)` function
/// to more easily repetative columns,
/// similar to the CSS-native `repeat()`.
///
/// - `susy-repeat(8)`:
/// an 8-column, symmetrical, fluid grid.
/// <br />Identical to `(1 1 1 1 1 1 1 1)`.
/// - `susy-repeat(6, 8em)`:
/// a 6-column, symmetrical, em-based grid.
/// <br />Identical to `(8em 8em 8em 8em 8em 8em)`.
/// - `(300px susy-repeat(4) 300px)`:
/// a 6-column, asymmetrical, mixed fluid/static grid
/// using `calc()` output.
/// <br />Identical to `(300px 1 1 1 1 300px)`.
///
/// **NOTE** that `12` is no longer a valid 12-column grid definition,
/// and you must list all the columns individually
/// (or by using the `susy-repeat()` function).
///
/// @prop {number} gutters -
/// Gutters are defined as a single width,
/// or fluid ratio, similar to the native-CSS
/// `grid-column-gap` syntax.
/// Similar to columns,
/// gutters can use any valid CSS length unit,
/// or unitless numbers to define a relative fraction.
///
/// - `0.5`:
/// a fluid gutter, half the size of a single-fraction column.
/// - `1em`:
/// a static gutter, `1em` wide.
///
/// Mix static gutters with fluid columns, or vice versa,
/// and Susy will generate the required `calc()` to make it work.
///
/// @prop {string} spread [narrow] -
/// Spread of an element across adjacent gutters:
/// either `narrow` (none), `wide` (one), or `wider` (two)
///
/// - Both spread settings default to `narrow`,
/// the most common use-case.
/// A `narrow` spread only has gutters *between* columns
/// (one less gutter than columns).
/// This is how all css-native grids work,
/// and most margin-based grid systems.
/// - A `wide` spread includes the same number of gutters as columns,
/// spanning across a single side-gutter.
/// This is how most padding-based grid systems often work,
/// and is also useful for pushing and pulling elements into place.
/// - The rare `wider` spread includes gutters
/// on both sides of the column-span
/// (one more gutters than columns).
///
/// @prop {string} container-spread [narrow] -
/// Spread of a container around adjacent gutters:
/// either `narrow` (none), `wide` (one), or `wider` (two).
/// See `spread` property for details.
///
/// @since 3.0.0-beta.1 -
/// `columns` setting no longer accepts numbers
/// (e.g. `12`) for symmetrical fluid grids,
/// or the initial `12 x 120px` syntax for
/// symmetrical fixed-unit grids.
/// Use `susy-repeat(12)` or `susy-repeat(12, 120px)` instead.
///
/// @example scss - default values
/// // 4 symmetrical, fluid columns
/// // gutters are 1/4 the size of a column
/// // elements span 1 less gutter than columns
/// // containers span 1 less gutter as well
/// $susy: (
/// 'columns': susy-repeat(4),
/// 'gutters': 0.25,
/// 'spread': 'narrow',
/// 'container-spread': 'narrow',
/// );
///
/// @example scss - inside-static gutters
/// // 6 symmetrical, fluid columns…
/// // gutters are static, triggering calc()…
/// // elements span equal columns & gutters…
/// // containers span equal columns & gutters…
/// $susy: (
/// 'columns': susy-repeat(6),
/// 'gutters': 0.5em,
/// 'spread': 'wide',
/// 'container-spread': 'wide',
/// );
$susy: () !default;



// Susy Repeat
// -----------
/// Similar to the `repeat(<count>, <value>)` function
/// that is available in native CSS Grid templates,
/// the `susy-repeat()` function helps generate repetative layouts
/// by repeating any value a given number of times.
/// Where Susy previously allowed `8` as a column definition
/// for 8 equal columns, you should now use `susy-repeat(8)`.
///
/// @group config
///
/// @param {integer} $count -
/// The number of repetitions, e.g. `12` for a 12-column grid.
/// @param {*} $value [1] -
/// The value to be repeated.
/// Technically any value can be repeated here,
/// but the function exists to repeat column-width descriptions:
/// e.g. the default `1` for single-fraction fluid columns,
/// `5em` for a static column,
/// or even `5em 120px` if you are alternating column widths.
///
/// @return {list} -
/// List of repeated values
///
/// @example scss
/// // 12 column grid, with 5em columns
/// $susy: (
/// columns: susy-repeat(12, 5em),
/// );
///
/// @example scss
/// // asymmetrical 5-column grid
/// $susy: (
/// columns: 20px susy-repeat(3, 100px) 20px,
/// );
///
/// /* result: #{susy-get('columns')} */
@function susy-repeat(
$count,
$value: 1
) {
$return: ();

@for $i from 1 through $count {
$return: join($return, $value);
}

@return $return;
}



// Susy Defaults
// -------------
/// Configuration map of Susy factory defaults.
/// Do not override this map directly –
/// use `$susy` for user and project setting overrides.
///
/// @access private
/// @type Map
///
/// @see $susy
///
/// @prop {number | list} columns [susy-repeat(4)]
/// @prop {number} gutters [0.25]
/// @prop {string} spread ['narrow']
/// @prop {string} container-spread ['narrow']
$_susy-defaults: (
'columns': susy-repeat(4),
'gutters': 0.25,
'spread': 'narrow',
'container-spread': 'narrow',
);



// Susy Settings
// -------------
/// Return a combined map of Susy settings,
/// based on the factory defaults (`$_susy-defaults`),
/// user-defined project configuration (`$susy`),
/// and any local overrides required –
/// such as a configuration map passed into a function.
///
/// @group config
///
/// @param {maps} $overrides… -
/// Optional map override of global configuration settings.
/// See `$susy` above for properties.
///
/// @return {map} -
/// Combined map of Susy configuration settings,
/// in order of specificity:
/// any `$overrides...`,
/// then `$susy` project settings,
/// and finally the `$_susy-defaults`
///
/// @example scss - global settings
/// @each $key, $value in susy-settings() {
/// /* #{$key}: #{$value} */
/// }
///
/// @example scss - local settings
/// $local: ('columns': 1 2 3 5 8);
///
/// @each $key, $value in susy-settings($local) {
/// /* #{$key}: #{$value} */
/// }
@function susy-settings(
$overrides...
) {
$settings: map-merge($_susy-defaults, $susy);

@each $config in $overrides {
$settings: map-merge($settings, $config);
}

@return $settings;
}



// Susy Get
// --------
/// Return the current global value of any Susy setting
///
/// @group config
///
/// @param {string} $key -
/// Setting to retrieve from the configuration.
///
/// @return {*} -
/// Value mapped to `$key` in the configuration maps,
/// in order of specificity:
/// `$susy`, then `$_susy-defaults`
///
/// @example scss -
/// /* columns: #{susy-get('columns')} */
/// /* gutters: #{susy-get('gutters')} */
@function susy-get(
$key
) {
$settings: susy-settings();

@if not map-has-key($settings, $key) {
@return _susy-error(
'There is no Susy setting called `#{$key}`',
'susy-get');
}

@return map-get($settings, $key);
}

+ 441
- 0
rsc/sass/susy/_su-math.scss 파일 보기

@@ -0,0 +1,441 @@
/// Grid Math Engine
/// ================
/// The `su` functions give you direct access to the math layer,
/// without any syntax-sugar like shorthand parsing, and normalization.
/// If you prefer named arguments, and stripped-down syntax,
/// you can use these functions directly in your code –
/// replacing `span`, `gutter`, and `slice`.
///
/// These functions are also useful
/// for building mixins or other extensions to Susy.
/// Apply the Susy syntax to new mixins and functions,
/// using our "Plugin Helpers",
/// or write your own syntax and pass the normalized results along
/// to `su` for compilation.
///
/// @group su-math
///
/// @see su-span
/// @see su-gutter
/// @see su-slice
/// @ignore _su-sum
/// @ignore _su-calc-span
/// @ignore _su-calc-sum
/// @ignore _su-needs-calc-output



// Su Span
// -------
/// Calculates and returns a CSS-ready span width,
/// based on normalized span and context data –
/// a low-level version of `susy-span`,
/// with all of the logic and none of the syntax sugar.
///
/// - Grids defined with unitless numbers will return `%` values.
/// - Grids defined with comparable units
/// will return a value in the units provided.
/// - Grids defined with a mix of units,
/// or a combination of untiless numbers and unit-lengths,
/// will return a `calc()` string.
///
/// @group su-math
/// @see susy-span
///
/// @param {number | list} $span -
/// Number or list of grid columns to span
/// @param {list} $columns -
/// List of columns available
/// @param {number} $gutters -
/// Width of a gutter in column-comparable units
/// @param {0 | 1 | -1} $spread -
/// Number of gutters spanned,
/// relative to `span` count
/// @param {0 | 1 | -1} $container-spread [$spread] -
/// Number of gutters spanned,
/// relative to `columns` count
/// @param {integer} $location [1] -
/// Optional position of sub-span among full set of columns
///
/// @return {length} -
/// Relative or static length of a span on the grid
@function su-span(
$span,
$columns,
$gutters,
$spread,
$container-spread: $spread,
$location: 1
) {
$span: su-valid-span($span);
$columns: su-valid-columns($columns);
$gutters: su-valid-gutters($gutters);
$spread: su-valid-spread($spread);

@if (type-of($span) == 'number') {
@if (not unitless($span)) {
@return $span;
}

$location: su-valid-location($span, $location, $columns);
$span: su-slice($span, $columns, $location, $validate: false);
}

@if _su-needs-calc-output($span, $columns, $gutters, $spread, not 'validate') {
@return _su-calc-span($span, $columns, $gutters, $spread, $container-spread, not 'validate');
}

$span-width: _su-sum($span, $gutters, $spread, $validate: false);

@if unitless($span-width) {
$container-spread: su-valid-spread($container-spread);
$container: _su-sum($columns, $gutters, $container-spread, $validate: false);
@return percentage($span-width / $container);
}

@return $span-width;
}



// Su Gutter
// ---------
/// Calculates and returns a CSS-ready gutter width,
/// based on normalized grid data –
/// a low-level version of `susy-gutter`,
/// with all of the logic and none of the syntax sugar.
///
/// - Grids defined with unitless numbers will return `%` values.
/// - Grids defined with comparable units
/// will return a value in the units provided.
/// - Grids defined with a mix of units,
/// or a combination of untiless numbers and unit-lengths,
/// will return a `calc()` string.
///
/// @group su-math
/// @see susy-gutter
///
/// @param {list} $columns -
/// List of columns in the grid
/// @param {number} $gutters -
/// Width of a gutter in column-comparable units
/// @param {0 | 1 | -1} $container-spread -
/// Number of gutters spanned,
/// relative to `columns` count
///
/// @return {length} -
/// Relative or static length of one gutter in a grid
@function su-gutter(
$columns,
$gutters,
$container-spread
) {
@if (type-of($gutters) == 'number') {
@if ($gutters == 0) or (not unitless($gutters)) {
@return $gutters;
}
}

@if _su-needs-calc-output($gutters, $columns, $gutters, -1, not 'validate') {
@return _su-calc-span($gutters, $columns, $gutters, -1, $container-spread, not 'validate');
}

$container: _su-sum($columns, $gutters, $container-spread);
@return percentage($gutters / $container);
}



// Su Slice
// --------
/// Returns a list of columns
/// based on a given span/location slice of the grid –
/// a low-level version of `susy-slice`,
/// with all of the logic and none of the syntax sugar.
///
/// @group su-math
/// @see susy-slice
///
/// @param {number} $span -
/// Number of grid columns to span
/// @param {list} $columns -
/// List of columns in the grid
/// @param {number} $location [1] -
/// Starting index of a span in the list of columns
/// @param {bool} $validate [true] -
/// Check that arguments are valid before proceeding
///
/// @return {list} -
/// Subset list of grid columns, based on span and location
@function su-slice(
$span,
$columns,
$location: 1,
$validate: true
) {
@if $validate {
$columns: su-valid-columns($columns);
$location: su-valid-location($span, $location, $columns);
}

$floor: floor($span);
$sub-columns: ();

@for $i from $location to ($location + $floor) {
$sub-columns: append($sub-columns, nth($columns, $i));
}

@if $floor != $span {
$remainder: $span - $floor;
$column: $location + $floor;
$sub-columns: append($sub-columns, nth($columns, $column) * $remainder);
}

@return $sub-columns;
}



// Su Sum
// ------
/// Get the total sum of column-units in a layout.
///
/// @group su-math
/// @access private
///
/// @param {list} $columns -
/// List of columns in the grid
/// @param {number} $gutters -
/// Width of a gutter in column-comparable units
/// @param {0 | 1 | -1} $spread -
/// Number of gutters spanned,
/// relative to `columns` count
/// @param {bool} $validate [true] -
/// Check that arguments are valid before proceeding
///
/// @return {number} -
/// Total sum of column-units in a grid
@function _su-sum(
$columns,
$gutters,
$spread,
$validate: true
) {
@if $validate {
$columns: su-valid-span($columns);
$gutters: su-valid-gutters($gutters);
$spread: su-valid-spread($spread);
}

// Calculate column-sum
$column-sum: 0;
@each $column in $columns {
$column-sum: $column-sum + $column;
}

$gutter-sum: (ceil(length($columns)) + $spread) * $gutters;
$total: if(($gutter-sum > 0), $column-sum + $gutter-sum, $column-sum);

@return $total;
}



// Su Calc
// -------
/// Return a usable span width as a `calc()` function,
/// in order to create mixed-unit grids.
///
/// @group su-math
/// @access private
///
/// @param {number | list} $span -
/// Pre-sliced list of grid columns to span
/// @param {list} $columns -
/// List of columns available
/// @param {number} $gutters -
/// Width of a gutter in column-comparable units
/// @param {0 | 1 | -1} $spread -
/// Number of gutters spanned,
/// relative to `span` count
/// @param {0 | 1 | -1} $container-spread [$spread] -
/// Number of gutters spanned,
/// relative to `columns` count
/// @param {bool} $validate [true] -
/// Check that arguments are valid before proceeding
///
/// @return {length} -
/// Relative or static length of a span on the grid
@function _su-calc-span(
$span,
$columns,
$gutters,
$spread,
$container-spread: $spread,
$validate: true
) {
@if $validate {
$span: su-valid-span($span);
$columns: su-valid-columns($columns);
$gutters: su-valid-gutters($gutters);
$spread: su-valid-spread($spread);
$container-spread: su-valid-spread($container-spread);
}

// Span and context
$span: _su-calc-sum($span, $gutters, $spread, not 'validate');
$context: _su-calc-sum($columns, $gutters, $container-spread, not 'validate');

// Fixed and fluid
$fixed-span: map-get($span, 'fixed');
$fluid-span: map-get($span, 'fluid');
$fixed-context: map-get($context, 'fixed');
$fluid-context: map-get($context, 'fluid');

$calc: '#{$fixed-span}';
$fluid-calc: '(100% - #{$fixed-context})';

// Fluid-values
@if (not $fluid-span) {
$fluid-calc: null;
} @else if ($fluid-span != $fluid-context) {
$fluid-span: '* #{$fluid-span}';
$fluid-context: if($fluid-context, '/ #{$fluid-context}', '');
$fluid-calc: '(#{$fluid-calc $fluid-context $fluid-span})';
}

@if $fluid-calc {
$calc: if(($calc != ''), '#{$calc} + ', '');
$calc: '#{$calc + $fluid-calc}';
}

@return calc(#{unquote($calc)});
}



// Su Calc-Sum
// -----------
/// Get the total sum of fixed and fluid column-units
/// for creating a mixed-unit layout with `calc()` values.
///
/// @group su-math
/// @access private
///
/// @param {list} $columns -
/// List of columns available
/// @param {number} $gutters -
/// Width of a gutter in column-comparable units
/// @param {0 | 1 | -1} $spread -
/// Number of gutters spanned,
/// relative to `span` count
/// @param {bool} $validate [true] -
/// Check that arguments are valid before proceeding
///
/// @return {map} -
/// Map with `fixed` and `fluid` keys
/// containing the proper math as strings
@function _su-calc-sum(
$columns,
$gutters,
$spread,
$validate: true
) {
@if $validate {
$columns: su-valid-span($columns);
$gutters: su-valid-gutters($gutters);
$spread: su-valid-spread($spread);
}

$fluid: 0;
$fixed: ();
$calc: null;

// Gutters
$gutters: $gutters * (length($columns) + $spread);

// Columns
@each $col in append($columns, $gutters) {
@if unitless($col) {
$fluid: $fluid + $col;
} @else {
$fixed: _su-map-add-units($fixed, $col);
}
}

// Compile Fixed Units
@each $unit, $total in $fixed {
@if ($total != (0 * $total)) {
$calc: if($calc, '#{$calc} + #{$total}', '#{$total}');
}
}

// Calc null or string
@if $calc {
$calc: if(str-index($calc, '+'), '(#{$calc})', '#{$calc}');
}

// Fluid 0 => null
$fluid: if(($fluid == 0), null, $fluid);


// Return map
$return: (
'fixed': $calc,
'fluid': $fluid,
);

@return $return;
}



// Needs Calc
// ----------
/// Check if `calc()` will be needed in defining a span,
/// if the necessary units in a grid are not comparable.
///
/// @group su-math
/// @access private
///
/// @param {list} $span -
/// Slice of columns to span
/// @param {list} $columns -
/// List of available columns in the grid
/// @param {number} $gutters -
/// Width of a gutter
/// @param {0 | 1 | -1} $spread -
/// Number of gutters spanned,
/// relative to `span` count
/// @param {bool} $validate [true] -
/// Check that arguments are valid before proceeding
///
/// @return {bool} -
/// `True` when units do not match, and `calc()` will be required
@function _su-needs-calc-output(
$span,
$columns,
$gutters,
$spread,
$validate: true
) {
@if $validate {
$span: su-valid-span($span);
$columns: su-valid-columns($columns);
$gutters: su-valid-gutters($gutters);
}

$has-gutter: if((length($span) > 1) or ($spread >= 0), true, false);
$check: if($has-gutter, append($span, $gutters), $span);
$safe-span: _su-is-comparable($check...);

@if ($safe-span == 'static') {
@return false;
} @else if (not $safe-span) {
@return true;
}

$safe-fluid: _su-is-comparable($gutters, $columns...);

@return not $safe-fluid;
}

+ 213
- 0
rsc/sass/susy/_su-validate.scss 파일 보기

@@ -0,0 +1,213 @@
/// Validation
/// ==========
/// Each argument to Su has a single canonical syntax.
/// These validation functions check to ensure
/// that each argument is valid,
/// in order to provide useful errors
/// before attempting to calculate the results/
///
/// @group _validation
///
/// @see su-valid-columns
/// @see su-valid-gutters
/// @see su-valid-spread
/// @see su-valid-location



// Valid Span
// ----------
/// Check that the `span` argument
/// is a number, length, or column-list
///
/// @group _validation
///
/// @param {number | list} $span -
/// Number of columns, or length of span
///
/// @return {number | list} -
/// Validated `$span` number, length, or columns list
///
/// @throw
/// when span value is not a number, or valid column list
@function su-valid-span(
$span
) {
$type: type-of($span);
@if ($type == 'number') {
@return $span;
} @else if ($type == 'list') and su-valid-columns($span, 'silent-failure') {
@return $span;
}

$actual: '[#{type-of($span)}] `#{inspect($span)}`';
@return _susy-error(
'#{$actual} is not a valid number, length, or column-list for $span.',
'su-valid-span');
}



// Valid Columns
// -------------
/// Check that the `columns` argument is a valid
/// list of column-lengths
///
/// @group _validation
///
/// @param {list} $columns -
/// List of column-lengths
/// @param {bool} $silent-failure [true] -
/// Set false to return null on failure
///
/// @return {list} -
/// Validated `$columns` list
///
/// @throw
/// when column value is not a valid list of numbers
@function su-valid-columns(
$columns,
$silent-failure: false
) {
@if (type-of($columns) == 'list') {
$fail: false;

@each $col in $columns {
@if (type-of($col) != 'number') {
$fail: true;
}
}

@if not $fail {
@return $columns;
}
}

// Silent Failure
@if $silent-failure {
@return null;
}

// Error Message
$actual: '[#{type-of($columns)}] `#{inspect($columns)}`';

@return _susy-error(
'#{$actual} is not a valid list of numbers for $columns.',
'su-valid-columns');
}



// Valid Gutters
// -------------
/// Check that the `gutters` argument is a valid number
///
/// @group _validation
///
/// @param {number} $gutters -
/// Width of a gutter
///
/// @return {number} -
/// Validated `$gutters` number
///
/// @throw
/// when gutter value is not a number
@function su-valid-gutters(
$gutters
) {
$type: type-of($gutters);

@if ($type == 'number') {
@return $gutters;
}

$actual: '[#{$type}] `#{inspect($gutters)}`';
@return _susy-error(
'#{$actual} is not a number or length for $gutters.',
'su-valid-gutters');
}



// Valid Spread
// ------------
/// Check that the `spread` argument is a valid
/// intiger between `-1` and `1`
///
/// @group _validation
///
/// @param {0 | 1 | -1} $spread -
/// Number of gutters to include in a span,
/// relative to the number columns
///
/// @return {0 | 1 | -1} -
/// Validated `$spread` number
///
/// @throw
/// when spread value is not a valid spread
@function su-valid-spread(
$spread
) {
@if index(0 1 -1, $spread) {
@return $spread;
}

$actual: '[#{type-of($spread)}] `#{inspect($spread)}`';
@return _susy-error(
'#{$actual} is not a normalized [0 | 1 | -1] value for `$spread`.',
'su-valid-spread');
}



// Valid Location
// --------------
/// Check that the `location` argument is a valid number,
/// within the scope of available columns
///
/// @group _validation
///
/// @param {number} $span -
/// Number of grid-columns to be spanned
/// @param {integer | string} $location -
/// Starting (1-indexed) column-position of that span
/// @param {list} $columns -
/// List of available columns in the grid
///
/// @return {integer} -
/// Validated `$location` intiger
///
/// @throw
/// when location value is not a valid index,
/// given the context and span.
@function su-valid-location(
$span,
$location,
$columns
) {
$count: length($columns);

@if $location {
@if (type-of($location) != 'number') or (not unitless($location)) {
$actual: '[#{type-of($location)}] `#{$location}`';
@return _susy-error(
'#{$actual} is not a unitless number for $location.',
'su-valid-location');
} @else if (round($location) != $location) {
@return _susy-error(
'Location (`#{$location}`) must be a 1-indexed intiger position.',
'su-valid-location');
} @else if ($location > $count) or ($location < 1) {
@return _susy-error(
'Position `#{$location}` does not exist in grid `#{$columns}`.',
'su-valid-location');
} @else if ($location + $span - 1 > $count) {
$details: 'grid `#{$columns}` for span `#{$span}` at `#{$location}`';
@return _susy-error(
'There are not enough columns in #{$details}.',
'su-valid-location');
}
}

@return $location;
}

+ 191
- 0
rsc/sass/susy/_syntax-helpers.scss 파일 보기

@@ -0,0 +1,191 @@
/// Syntax Utilities for Extending Susy
/// ===================================
/// There are many steps involved
/// when translating between the Susy syntax layer,
/// and the Su core math.
/// That entire process can be condensed with these two functions.
/// For anyone that wants to access the full power of Susy,
/// and build their own plugins, functions, or mixins –
/// this is the primary API for compiling user input,
/// and accessing the core math.
///
/// This is the same technique we use internally,
/// to keep our API layer simple and light-weight.
/// Every function accepts two arguments,
/// a "shorthand" description of the span or context,
/// and an optional settings-map to override global defaults.
///
/// - Use `susy-compile()` to parse, merge, and normalize
/// all the user settings into a single map.
/// - Then use `su-call()` to call one of the core math functions,
/// with whatever data is needed for that function.
///
/// @group plugin-utils
/// @see susy-compile
/// @see su-call
///
/// @example scss - Susy API `gutter` function
/// @function susy-gutter(
/// $context: susy-get('columns'),
/// $config: ()
/// ) {
/// // compile and normalize all user arguments and global settings
/// $context: susy-compile($context, $config, 'context-only');
/// // call `su-gutter` with the appropriate data
/// @return su-call('su-gutter', $context);
/// }
///
/// @example scss - Sample `span` mixin for floated grids
/// @mixin span(
/// $span,
/// $config: ()
/// ) {
/// $context: susy-compile($span, $config);
/// width: su-call('su-span', $context);
///
/// @if index($span, 'last') {
/// float: right;
/// } @else {
/// float: left;
/// margin-right: su-call('su-gutter', $context);
/// }
/// }



// Compile
// -------
/// Susy's syntax layer has various moving parts,
/// with syntax-parsing for the grid/span shorthand,
/// and normalization for each of the resulting values.
/// The compile function rolls this all together
/// in a single call –
/// for quick access from our internal API functions,
/// or any additional functions and mixins you add to your project.
/// Pass user input and configuration maps to the compiler,
/// and it will hand back a map of values ready for Su.
/// Combine this with the `su-call` function
/// to quickly parse, normalize, and process grid calculations.
///
/// @group plugin-utils
/// @see su-call
///
/// @param {list | map} $shorthand -
/// Shorthand expression to define the width of the span,
/// optionally containing:
/// - a count, length, or column-list span;
/// - `at $n`, `first`, or `last` location on asymmetrical grids;
/// - `narrow`, `wide`, or `wider` for optionally spreading
/// across adjacent gutters;
/// - `of $n <spread>` for available grid columns
/// and spread of the container
/// (span counts like `of 6` are only valid
/// in the context of symmetrical grids);
/// - and `set-gutters $n` to override global gutter settings
/// @param {map} $config [null] -
/// Optional map of Susy grid configuration settings
/// @param {bool} $context-only [false] -
/// Allow the parser to ignore span and span-spread values,
/// only parsing context and container-spread
///
/// @return {map} -
/// Parsed and normalized map of settings,
/// based on global and local configuration,
/// alongwith shorthad adjustments.
///
/// @example scss -
/// $user-input: 3 wide of susy-repeat(6, 120px) set-gutters 10px;
/// $grid-data: susy-compile($user-input, $susy);
///
/// @each $key, $value in $grid-data {
/// /* #{$key}: #{$value}, */
/// }
@function susy-compile(
$short,
$config: null,
$context-only: false
) {
// Get and normalize config
$config: if($config, susy-settings($config), susy-settings());
$normal-config: susy-normalize($config);

// Parse and normalize shorthand
@if (type-of($short) != 'map') and (length($short) > 0) {
$short: susy-parse($short, $context-only);
}

$normal-short: susy-normalize($short, $normal-config);

// Merge and return
@return map-merge($normal-config, $normal-short);
}



// Call
// ----
/// The Susy parsing and normalization process
/// results in a map of configuration settings,
/// much like the global `$susy` settings map.
/// In order to pass that information along to Su math functions,
/// the proper values have to be picked out,
/// and converted to arguments.
///
/// The `su-call` function streamlines that process,
/// weeding out the unnecessary data,
/// and passing the rest along to Su in the proper format.
/// Combine this with `susy-compile` to quickly parse,
/// normalize, and process grid calculations.
///
/// @group plugin-utils
///
/// @require su-span
/// @require su-gutter
/// @require su-slice
/// @see susy-compile
///
/// @param {'su-span' | 'su-gutter' | 'su-slice'} $name -
/// Name of the Su math function to call.
/// @param {map} $config -
/// Parsed and normalized map of Susy configuration settings
/// to use for math-function arguments.
///
/// @return {*} -
/// Results of the function being called.
///
/// @example scss -
/// $user-input: 3 wide of susy-repeat(6, 120px) set-gutters 10px;
/// $grid-data: susy-compile($user-input, $susy);
///
/// .su-span {
/// width: su-call('su-span', $grid-data);
/// }
@function su-call(
$name,
$config
) {
$grid-function-args: (
'su-span': ('span', 'columns', 'gutters', 'spread', 'container-spread', 'location'),
'su-gutter': ('columns', 'gutters', 'container-spread'),
'su-slice': ('span', 'columns', 'location'),
);

$args: map-get($grid-function-args, $name);

@if not $args {
$options: 'Try one of these: #{map-keys($grid-function-args)}';
@return _susy-error(
'#{$name} is not a public Su function. #{$options}',
'su-call');
}

$call: if(function-exists('get-function'), get-function($name), $name);
$output: ();

@each $arg in $args {
$value: map-get($config, $arg);
$output: if($value, map-merge($output, ($arg: $value)), $output);
}

@return call($call, $output...);
}

+ 56
- 0
rsc/sass/susy/_unprefix.scss 파일 보기

@@ -0,0 +1,56 @@
// Unprefix Susy
// =============


// Span
// ----
/// Un-prefixed alias for `susy-span`
/// (available by default)
///
/// @group api
/// @alias susy-span
///
/// @param {list} $span
/// @param {map} $config [()]
@function span(
$span,
$config: ()
) {
@return susy-span($span, $config);
}


// Gutter
// ------
/// Un-prefixed alias for `susy-gutter`
/// (available by default)
///
/// @group api
/// @alias susy-gutter
///
/// @param {integer | list} $context [null] -
/// @param {map} $config [()]
@function gutter(
$context: susy-get('columns'),
$config: ()
) {
@return susy-gutter($context, $config);
}


// Slice
// -----
/// Un-prefixed alias for `susy-slice`
/// (available by default)
///
/// @group api
/// @alias susy-slice
///
/// @param {list} $span
/// @param {map} $config [()]
@function slice(
$span,
$config: ()
) {
@return susy-slice($span, $config);
}

+ 167
- 0
rsc/sass/susy/_utilities.scss 파일 보기

@@ -0,0 +1,167 @@
// Sass Utilities
// ==============
// - Susy Error Output Override [variable]
// - Susy Error [function]



// Susy Error Output Override
// --------------------------
/// Turn off error output for testing
/// @group x-utility
/// @access private
$_susy-error-output-override: false !default;



// Susy Error
// ----------
/// Optionally return error messages without failing,
/// as a way to test error cases
///
/// @group x-utility
/// @access private
///
/// @param {string} $message -
/// A useful error message, explaining the problem
/// @param {string} $source -
/// The original source of the error for debugging
/// @param {bool} $override [$_susy-error-output-override] -
/// Optionally return the error rather than failing
/// @return {string} -
/// Combined error with source and message
/// @throws When `$override == true`
@function _susy-error(
$message,
$source,
$override: $_susy-error-output-override
) {
@if $override {
@return 'ERROR [#{$source}] #{$message}';
}

@error '[#{$source}] #{$message}';
}


// Su Is Comparable
// ----------------
/// Check that the units in a grid are comparable
///
/// @group _validation
/// @access private
///
/// @param {numbers} $lengths… -
/// Arglist of all the number values to compare
/// (columns, gutters, span, etc)
///
/// @return {'fluid' | 'static' | false} -
/// The type of span (fluid or static) when units match,
/// or `false` for mismatched units
@function _su-is-comparable(
$lengths...
) {
$first: nth($lengths, 1);

@if (length($lengths) == 1) {
@return if(unitless($first), 'fluid', 'static');
}

@for $i from 2 through length($lengths) {
$comp: nth($lengths, $i);

$fail: not comparable($first, $comp);
$fail: $fail or (unitless($first) and not unitless($comp));
$fail: $fail or (unitless($comp) and not unitless($first));

@if $fail {
@return false;
}
}

@return if(unitless($first), 'fluid', 'static');
}


// Su Map Add Units
// ----------------
/// The calc features use a map of units and values
/// to compile the proper algorythm.
/// This function adds a new value to any comparable existing unit/value,
/// or adds a new unit/value pair to the map
///
/// @group x-utility
/// @access private
///
/// @param {map} $map -
/// A map of unit/value pairs, e.g. ('px': 120px)
/// @param {length} $value -
/// A new length to be added to the map
/// @return {map} -
/// The updated map, with new value added
///
/// @example scss -
/// $map: (0px: 120px);
/// $map: _su-map-add-units($map, 1in); // add a comparable unit
/// $map: _su-map-add-units($map, 3vw); // add a new unit
///
/// @each $units, $value in $map {
/// /* #{$units}: #{$value} */
/// }
@function _su-map-add-units(
$map,
$value
) {
$unit: $value * 0;
$has: map-get($map, $unit) or 0;

@if ($has == 0) {
@each $try, $could in $map {
$match: comparable($try, $value);
$unit: if($match, $try, $unit);
$has: if($match, $could, $has);
}
}

@return map-merge($map, ($unit: $has + $value));
}


// Susy Flatten
// ------------
/// Flatten a multidimensional list
///
/// @group x-utility
/// @access private
///
/// @param {list} $list -
/// The list to be flattened
/// @return {list} -
/// The flattened list
///
/// @example scss -
/// $list: 120px (30em 30em) 120px;
/// /* #{_susy-flatten($list)} */
@function _susy-flatten(
$list
) {
$flat: ();

// Don't iterate over maps
@if (type-of($list) == 'map') {
@return $list;
}

// Iterate over lists (or single items)
@each $item in $list {
@if (type-of($item) == 'list') {
$item: _susy-flatten($item);
$flat: join($flat, $item);
} @else {
$flat: append($flat, $item);
}
}

// Return flattened list
@return $flat;
}

+ 31
- 0
rsc/script.js 파일 보기

@@ -0,0 +1,31 @@
function toggle_menu(){
var menu = document.querySelector('#mobile-nav');
menu.classList.toggle('open');
}

$( document ).ready(function() {
var mainnav = $("#main-nav");
var platzhalter = $("#platzhalter");
var test = 2;

$(window).on("scroll", function(e) {
var pos = $(document).scrollTop();
var width = $(window).width();
var airAbove = 250;
if (width < 1000) {
if (width > 750) {
airAbove = width/4;
} else {
airAbove = 187;
}
}

if (pos > airAbove) {
mainnav.addClass("fixed");
platzhalter.addClass("fixed");
} else {
mainnav.removeClass("fixed");
platzhalter.removeClass("fixed");
}
});
});

+ 126
- 0
rsc/style.css 파일 보기

@@ -0,0 +1,126 @@
* {
box-sizing: border-box; }

html {
color: #777;
background: #fff; }

.card h2, .item h2 {
font-family: georgia, serif; }

body, h1, h2, h3, h4, h5, h6, ul {
margin: 0;
padding: 0; }

@media only screen and (max-width: 750px) {
#main-nav {
display: none; } }

h1 {
margin: .5em 0 0 1em; }

.card {
max-width: 600px; }
.card img {
width: 100%; }
.card::after {
content: "";
clear: both;
display: table; }

.featherlight .featherlight-content {
background: none;
overflow: visible; }
.featherlight .featherlight-content .featherlight-close-icon {
background: none;
top: -10px;
right: -10px;
color: #fff;
font-size: 1.5em; }
@media only screen and (max-width: 750px) {
.featherlight .featherlight-content iframe {
width: 100%;
height: 100%; } }

#mobile-nav {
display: none;
font-family: verdana, sans;
font-size: 1.2em; }
@media only screen and (max-width: 750px) {
#mobile-nav {
display: block;
display: none; }
#mobile-nav a.menu-toggle {
display: block; } }
#mobile-nav .the_list {
display: none;
padding: 2em; }
#mobile-nav .the_list .home-link,
#mobile-nav .the_list > ul:last-child {
font-family: cursive;
font-size: 1.4em;
margin-top: -.7em;
color: #bf920c; }
#mobile-nav .the_list .home-link a,
#mobile-nav .the_list > ul:last-child a {
color: #bf920c;
display: inline-block;
margin-top: 8px; }
#mobile-nav .the_list .home-link {
display: inline-block;
text-transform: lowercase;
margin-bottom: 8px; }
#mobile-nav .the_list ul {
margin-bottom: 1.7em; }
#mobile-nav .the_list li {
margin: 0.5em;
list-style: none; }
#mobile-nav .the_list li ul {
margin-bottom: 1em; }
#mobile-nav .the_list a {
color: #000;
text-decoration: none;
font-weight: normal; }
#mobile-nav .the_list .active > a {
color: #bf920c;
font-weight: bold; }
#mobile-nav.open .the_list {
display: block;
position: fixed;
top: 0;
left: 0;
z-index: 99;
background: rgba(255, 255, 255, 0.9);
height: 100%;
width: 100%;
overflow: scroll; }
#mobile-nav a.menu-toggle {
position: fixed;
top: 0;
right: 0;
width: 50px;
height: 50px;
z-index: 100;
cursor: pointer;
background: #fff;
background: rgba(255, 255, 255, 0.9);
background-image: url("img/menu-icon.png");
background-position: top; }
#mobile-nav.open a.menu-toggle {
background-position: bottom; }

a {
color: black;
text-decoration: none; }
a:hover {
text-decoration: underline; }

#admin-panel {
position: fixed;
bottom: 1em;
right: 1em;
width: 400px;
height: 300px;
background: #fa0; }

/*# sourceMappingURL=style.css.map */

+ 7
- 0
rsc/style.css.map 파일 보기

@@ -0,0 +1,7 @@
{
"version": 3,
"mappings": "AAUA,CAAE;EACE,UAAU,EAAC,UAAU;;ACGzB,IAAK;EACD,KAAK,EALA,IAAK;EAMV,UAAU,EAfP,IAAI;;ACaP,kBAAG;EACC,WAAW,EAPX,cAAQ;;ACLhB,gCAA0B;EACtB,MAAM,EAAC,CAAC;EACR,OAAO,EAAC,CAAC;;AAMT,yCAAuB;EAH3B,SAAU;IAIF,OAAO,EAAE,IAAI;;AAOrB,EAAG;EAAE,MAAM,EAAE,YAAY;;AAEzB,KAAM;EACF,SAAS,EAAC,KAAK;EACf,SAAI;IAAE,KAAK,EAAE,IAAI;EACjB,YAAS;IACL,OAAO,EAAC,EAAE;IACV,KAAK,EAAE,IAAI;IACX,OAAO,EAAC,KAAK;;AASrB,mCAAoC;EAChC,UAAU,EAAE,IAAI;EAChB,QAAQ,EAAE,OAAO;EACjB,4DAAyB;IACrB,UAAU,EAAE,IAAI;IAChB,GAAG,EAAE,KAAK;IACV,KAAK,EAAE,KAAK;IACZ,KAAK,EF1CN,IAAI;IE2CH,SAAS,EAAE,KAAK;EAEpB,yCAAuB;IACnB,0CAAO;MAAE,KAAK,EAAC,IAAI;MAAE,MAAM,EAAC,IAAI;;AAKxC,WAAY;EACR,OAAO,EAAE,IAAI;EASb,WAAW,EDrDR,aAAQ;ECsDX,SAAS,EAAC,KAAK;EATf,yCAAuB;IAF3B,WAAY;MAGJ,OAAO,EAAE,KAAK;MAEd,OAAO,EAAE,IAAI;MACb,yBAAc;QACV,OAAO,EAAE,KAAK;EAMtB,qBAAU;IAkBN,OAAO,EAAC,IAAI;IACZ,OAAO,EAAE,GAAG;IAlBZ;yCACe;MACX,WAAW,ED7Db,OAAO;MC8DL,SAAS,EAAE,KAAK;MAChB,UAAU,EAAE,KAAK;MACjB,KAAK,EF9DT,OAAO;ME+DH;6CAAE;QACE,KAAK,EFhEb,OAAO;QEiEC,OAAO,EAAE,YAAY;QACrB,UAAU,EAAE,GAAG;IAGvB,gCAAW;MACP,OAAO,EAAE,YAAY;MACrB,cAAc,EAAE,SAAS;MACzB,aAAa,EAAE,GAAG;IAItB,wBAAG;MAAE,aAAa,EAAE,KAAK;IACzB,wBAAG;MACC,MAAM,EAAC,KAAK;MACZ,UAAU,EAAC,IAAI;MACf,2BAAG;QAAE,aAAa,EAAE,GAAG;IAE3B,uBAAE;MACE,KAAK,EF1FV,IAAI;ME2FC,eAAe,EAAC,IAAI;MACpB,WAAW,EAAE,MAAM;IAEvB,iCAAU;MACN,KAAK,EFxFT,OAAO;MEyFH,WAAW,EAAE,IAAI;EAGzB,0BAAiB;IACb,OAAO,EAAC,KAAK;IACb,QAAQ,EAAC,KAAK;IACd,GAAG,EAAC,CAAC;IACL,IAAI,EAAC,CAAC;IACN,OAAO,EAAC,EAAE;IACV,UAAU,EAAE,wBAAqB;IACjC,MAAM,EAAC,IAAI;IACX,KAAK,EAAE,IAAI;IACX,QAAQ,EAAC,MAAM;EAEnB,yBAAc;IACV,QAAQ,EAAC,KAAK;IACd,GAAG,EAAC,CAAC;IACL,KAAK,EAAC,CAAC;IACP,KAAK,EAAC,IAAI;IACV,MAAM,EAAC,IAAI;IACX,OAAO,EAAC,GAAG;IACX,MAAM,EAAC,OAAO;IACd,UAAU,EFvHX,IAAI;IEwHH,UAAU,EAAE,wBAAqB;IACjC,gBAAgB,EAAE,wBAAwB;IAC1C,mBAAmB,EAAE,GAAG;EAE5B,8BAAqB;IACjB,mBAAmB,EAAC,MAAM;;AC9HlC,CAAE;EACE,KAAK,EAAE,KAAU;EACjB,eAAe,EAAC,IAAI;EACpB,OAAQ;IACJ,eAAe,EAAC,SAAS;;ACJjC,YAAa;EACT,QAAQ,EAAE,KAAK;EACf,MAAM,EAAE,GAAG;EAAE,KAAK,EAAE,GAAG;EACvB,KAAK,EAAC,KAAK;EAAC,MAAM,EAAC,KAAK;EACxB,UAAU,EAAE,IAAI",
"sources": ["sass/main.scss","sass/_colours.scss","sass/_fonts.scss","sass/_structure.scss","sass/_design.scss","sass/_admin.scss"],
"names": [],
"file": "style.css"
}

Loading…
취소
저장