View source for Timeline

%%(timeline)
l | 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

r | 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

l | 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))

{{toc numerate=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.

====Formatter====
""%%(timeline)
JSON?

or

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

%%(php)
<?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 ... ' ]
%%



===Source===
%%(hl html)
<!-- 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>
%%

%%(hl css)
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;
	}

}
%%