Timeline

  • R6.1 2021 - present
    Implements new PHP 8 functionality and responsive GUI with improved usability, uses IntlDateFormatter for setting timezone offset and DST, PHP 8 compatibility
  • R6.0 2020 - 2022
    Unicode support, UTF-8 migration tools, implements full support for relative path, accent and case-sensitive page tag, new chat, details and info formatter along with new wrapper options, PHP 8.0 compatibility
  • R5.5 2018 - 2020
    Major refactoring of init system, new session handler, new URI router, new template engine, HTML5 support, new php-diff rendering methods, implements auth and form token, adds Bad Behaviour as extension, file link tracking, media link support, uses password_hash() and password_verify() API, PHP 7.0 – 7.4 compatibility

see also /Dev/PatchesHacks/Timeline



1. Evaluation

I tend more to do a formatter out of it, or an action.
We should parse the description text as Wiki syntax so the user can add links, images and additional formatting.


Simply put, there is the CSS and the HTML and the data input as an array we provide which is then processed via foreach.
The CSS should put in a separate CSS, the formatter can't add a CSS file dynamically to the header like an action – its static once parsed into body_r.


There is a way to add a action call in the syntax, so it would load the style sheet dynamically – guess it requires a addhtml action, calling the function with the same name, to do that. However this would allow every user to inject random items, so we should add a new resource call option to the PostWacko class, where certain resources can be loaded dynamically via addhtml() in a formatter, such as CSS or JavaScript files.


'<ignore><!--notypo--><!--resource:begin-->' .
	'addhtml ' .
		'location='header' .
		'text=' . '<link rel="stylesheet" media="screen" href="' . $this->db->base_path . Ut::join_path(THEME_DIR, '_common/timeline.css') . '">' .
'<!--resource:end--><!--/notypo--></ignore>'	

We need a format the user provide which gets then parsed into an array, JSON or a delimiter and line-break?

direction: 'l|r'
flag: 'Moscow|Amsterdam'
time: 'September 12-13, 2022|July 1 & 2, 2022'
description: 'PHP Russia 2022|Dutch PHP Conference 2022'	

The format should be carefully selected for consistent use and further use in template boxes.

1.1. Formatter

%%(timeline)
JSON?

or

l | Moscow | September 12-13, 2022 | PHP Russia 2022
r | Amsterdam | July 1 & 2, 2022 | Dutch PHP Conference 2022
%%



<?php

/*
    converts inline data into a timeline

    % %(timeline
        [order="asc|desc"]
        [delim="comma|semicolon"]
        )
    content
    % %

    [0] direction: 'l|r'    (left|right)
    [1] flag: 'Amsterdam'
    [2] time: 'July 1 & 2, 2022'
    [3] description: 'Dutch PHP Conference 2022'

    e.g.
    l | Moscow | September 12-13, 2022 | PHP Russia 2022
    r | Amsterdam | July 1 & 2, 2022 | Dutch PHP Conference 2022

    https://wackowiki.org/doc/Dev/PatchesHacks/Timeline
*/

// set defaults
$options['delim']    ??= 'pipe';
$options['order']    ??= 'asc';

$delim    = match($options['delim']) {
    
'semicolon'    => ';',
    default        => 
'|',
};

// set CSS styles once
if (!isset($this->timline))
{
    
$this->timline    true;
    
$tpl->style_n    true;
}

// get data
$lines    preg_split('/[\n]/u'$text);

if (
$options['order'] == 'desc')
{
    
$lines array_reverse($lines);
}

foreach (
$lines as $line)
{
    
// blank
    
if (preg_match('/^#|^\s*$/u'$line))
    {
        continue;
    }

    
$tpl->enter('n_');

    
$item explode('|'$line);
    {
        
// debug
        #Ut::debug_print_r($item);

        
$direction    = match(trim($item[0])) {
            
'r'            => 'r',
            default        => 
'l',
        };

        
// further string processing here (links, filter, ...)
        
$flag            trim($item[1] ?? '');
        
$time            trim($item[2] ?? '');
        
$description    trim($item[3] ?? '');

        if (
$flag)
        {
            
$tpl->direction        $direction;
            
$tpl->flag            $flag;
            
$tpl->time            $time;
            
$tpl->description    $this->format($description'wiki', ['post_wacko' => true]);
        }
    }

    
$tpl->leave(); // n_
}

[ === main === ]
<ignore>
	<!--notypo-->
	[ ' style ' ]
	<ul class="timeline">
		[= n _ =
		<li>
			<div class="direction-[ ' direction ' ]">
				<div class="flag-wrapper">
					<span class="flag">[ ' flag | e ' ]</span>
					<span class="time-wrapper">
						<span class="time">[ ' time | e ' ]</span>
					</span>
				</div>
				<div class="desc">[ ' description ' ]</div>
			</div>
		</li>
		=]
	</ul>	
	<!--/notypo-->
</ignore>

[ === style === ]
<style>[ ' n css ' ]</style>

[ === css === ]
[ ' nonstatic ' ]
/* ================ The Timeline ================ */

.timeline {
	position: relative;
	width: 660px;
	margin: 0 auto;
	margin-top: 20px;
	padding: 1em 0;
	list-style-type: none;
}

[ ' // and more CSS ... ' ]	

2. Source

<!-- The Timeline -->
 
<ul class="timeline">
 
  <!-- Item 1 -->
  <li>
    <div class="direction-r">
      <div class="flag-wrapper">
        <span class="flag">Freelancer</span>
        <span class="time-wrapper"><span class="time">2013 - present</span></span>
      </div>
      <div class="desc">My current employment. Way better than the position before!</div>
    </div>
  </li>
  
  <!-- Item 2 -->
  <li>
    <div class="direction-l">
      <div class="flag-wrapper">
        <span class="flag">Apple Inc.</span>
        <span class="time-wrapper"><span class="time">2011 - 2013</span></span>
      </div>
      <div class="desc">My first employer. All the stuff I've learned and projects I've been working on.</div>
    </div>
  </li>
 
  <!-- Item 3 -->
  <li>
    <div class="direction-r">
      <div class="flag-wrapper">
        <span class="flag">Harvard University</span>
        <span class="time-wrapper"><span class="time">2008 - 2011</span></span>
      </div>
      <div class="desc">A description of all the lectures and courses I have taken and my final degree?</div>
    </div>
  </li>
  
</ul>

body {  
  margin: 0;
  padding: 0;
  background: rgb(230,230,230);
  
  color: rgb(50,50,50);
  font-family: 'Open Sans', sans-serif;
  font-size: 112.5%;
  line-height: 1.6em;
}
 
/* ================ The Timeline ================ */
 
.timeline {
    position: relative;
    width: 660px;
    margin: 0 auto;
    margin-top: 20px;
    padding: 1em 0;
    list-style-type: none;
}
 
.timeline:before {
    position: absolute;
    left: 50%;
    top: 0;
    content: ' ';
    display: block;
    width: 6px;
    height: 100%;
    margin-left: -3px;
    background: rgb(80,80,80);
    background: linear-gradient(to bottom, rgba(80,80,80,0) 0%, rgb(80,80,80) 8%, rgb(80,80,80) 92%, rgba(80,80,80,0) 100%);
 
    z-index: 5;
}
 
.timeline li {
    padding: 1em 0;
}
 
.timeline li:after {
    content: '';
    display: block;
    height: 0;
    clear: both;
    visibility: hidden;
}
 
.direction-l {
    position: relative;
    width: 300px;
    float: left;
    text-align: right;
}
 
.direction-r {
    position: relative;
    width: 300px;
    float: right;
}
 
.flag-wrapper {
    position: relative;
    display: inline-block;
 
    text-align: center;
}
 
.flag {
    position: relative;
    display: inline;
    background: rgb(248,248,248);
    padding: 6px 10px;
    border-radius: 5px;
 
    font-weight: 600;
    text-align: left;
}
 
.direction-l .flag {
    box-shadow: -1px 1px 1px rgba(0,0,0,0.15), 0 0 1px rgba(0,0,0,0.15);
}
 
.direction-r .flag {
    box-shadow: 1px 1px 1px rgba(0,0,0,0.15), 0 0 1px rgba(0,0,0,0.15);
}
 
.direction-l .flag:before,
.direction-r .flag:before {
    position: absolute;
    top: 50%;
    right: -40px;
    content: ' ';
    display: block;
    width: 12px;
    height: 12px;
    margin-top: -10px;
    background: #fff;
    border-radius: 10px;
    border: 4px solid rgb(255,80,80);
    z-index: 10;
}
 
.direction-r .flag:before {
    left: -40px;
}
 
.direction-l .flag:after {
    content: '';
    position: absolute;
    left: 100%;
    top: 50%;
    height: 0;
    width: 0;
    margin-top: -8px;
    border: solid transparent;
    border-left-color: rgb(248,248,248);
    border-width: 8px;
    pointer-events: none;
}
 
.direction-r .flag:after {
    content: '';
    position: absolute;
    right: 100%;
    top: 50%;
    height: 0;
    width: 0;
    margin-top: -8px;
    border: solid transparent;
    border-right-color: rgb(248,248,248);
    border-width: 8px;
    pointer-events: none;
}
 
.time-wrapper {
    display: inline;
    
    line-height: 1rem;
    font-size: 0.66666rem;
    color: rgb(250,80,80);
    vertical-align: middle;
}
 
.direction-l .time-wrapper {
    float: left;
}
 
.direction-r .time-wrapper {
    float: right;
}
 
.time {
    display: inline-block;
    padding: 4px 6px;
    background: rgb(248,248,248);
}
 
.desc {
    margin: 1rem 0.75rem 0 0;
    
    font-size: 0.77777rem;
    font-style: italic;
    line-height: 1.5rem;
}
 
.direction-r .desc {
    margin: 1em 0 0 0.75rem;
}
 
/* ================ Timeline Media Queries ================ */
 
@media screen and (max-width: 660px) {
 
    .timeline {
        width: 100%;
        padding: 4rem 0 1rem 0;
    }
    
    .timeline li {
        padding: 2rem 0;
    }
    
    .direction-l,
    .direction-r {
        float: none;
        width: 100%;
    
        text-align: center;
    }
    
    .flag-wrapper {
        text-align: center;
    }
    
    .flag {
        background: rgb(255,255,255);
        z-index: 15;
    }
    
    .direction-l .flag:before,
    .direction-r .flag:before {
        position: absolute;
        top: -30px;
        left: 50%;
        content: ' ';
        display: block;
        width: 12px;
        height: 12px;
        margin-left: -9px;
        background: #fff;
        border-radius: 10px;
        border: 4px solid rgb(255,80,80);
        z-index: 10;
    }
    
    .direction-l .flag:after,
    .direction-r .flag:after {
        content: '';
        position: absolute;
        left: 50%;
        top: -8px;
        height: 0;
        width: 0;
        margin-left: -8px;
        border: solid transparent;
        border-bottom-color: rgb(255,255,255);
        border-width: 8px;
        pointer-events: none;
    }
    
    .time-wrapper {
        display: block;
        position: relative;
        margin: 4px 0 0 0;
        z-index: 14;
    }
    
    .direction-l .time-wrapper {
        float: none;
    }
    
    .direction-r .time-wrapper {
        float: none;
    }
    
    .desc {
        position: relative;
        margin: 1rem 0 0 0;
        padding: 1rem;
        background: rgb(245,245,245);
        box-shadow: 0 0 1px rgba(0,0,0,0.20);
    
        z-index: 15;
    }
    
    .direction-l .desc,
    .direction-r .desc {
        position: relative;
        margin: 1rem 1em 0 1rem;
        padding: 1rem;
    
        z-index: 15;
    }
 
}
 
@media screen and (min-width: 400px ?? max-width: 660px) {
 
    .direction-l .desc,
    .direction-r .desc {
        margin: 1em 4em 0 4em;
    }
 
}

Comments