Commits: 6
Implement single feature pages
Now features link to pages at /features/xyz. Those pages contain a list of solutions with a given feature, and optionally custom text. The text comes from special documents at content/solutions/features. Currently there is only one as a sample - free plan. Those feature documents are not rendered (they don't produce pages themselves), and only serve as extra data for the taxonomy pages. This is based on a tip from the Zola forum:
https://zola.discourse.group/t/can-we-add-extra-data-to-taxonomyterm/770/2?u=tad-lispy
In the future I want to use those documents to provide more data, e.g. an icons for each feature.
new file mode 100644
index 0000000..7f2bf71
--- /dev/null
+++ b/content/solutions/features/_index.md
@@ -0,0 +1,8 @@
+---
+title: Features definitions
+page_template: page.html
+render: false
+---
+
+These are features of digital solutions we review.
+new file mode 100644
index 0000000..834b612
--- /dev/null
+++ b/content/solutions/features/free-plan.md
@@ -0,0 +1,7 @@
+---
+render: false
+---
+
+A free plan means that you can use a service in your business (usually in a limited capability) without any financial obligations.
+
+We only mark services with this feature if the free plan is unlimited in time (so not only a time trial) and actually useful for businesses.new file mode 100644
index 0000000..b37b27f
--- /dev/null
+++ b/templates/features/single.html
@@ -0,0 +1,53 @@
+{# This is a template for a single feature #}
+
+{\% extends "base.html" %}
+
+{\% block main_content %}
+
+{\% set feature_slug = term.name | slugify %}
+{\% set feature_path = "solutions/features/" ~ feature_slug ~ ".md" %}
+{# TODO: Consider if a feature page should be required
+
+ As it is now, they can be skipped. But maybe we want to
+ ensure that every feature is defined? At the very least it
+ would prevent typos.
+#}
+{\% set feature_data = load_data (path=feature_path, required=false) %}
+{\% if feature_data %}
+ {\% set feature_page = get_page(path=feature_path) %}
+ {\{ feature_page.content | safe \}}
+{\% endif %}
+
+<p>Solutions with the <em>{\{ term.name \}}</em> feature:</p>
+
+{\% set categories = get_taxonomy(kind="solution_categories") %}
+{\% for category in categories.items %}
+ {\% set_global solutions = [] %}
+ {\% for solution in category.pages %}
+ {\% if solution.taxonomies.features is containing (term.name) %}
+ {\% set_global solutions = solutions | concat (with=solution) %}
+ {\% endif %}
+ {\% endfor %}
+ {\% if solutions %}
+ <h2>{\{ category.name | title \}}</h2>
+ <section class="card-grid">
+ {\% for solution in solutions %}
+ {# TODO: Dry with solution.html - a macro? #}
+ <article>
+ <h3>{\{ solution.title \}}</h3>
+ <p>{\{ solution.summary | safe | striptags \}}</p>
+ <ul>
+ {\% for feature in solution.taxonomies.features %}
+ {\% set feature_slug = feature | slugify %}
+ <li>
+ <a href="{\{ get_url(path="/features/" ~ feature_slug) \}}">{\{ feature \}}</a>
+ </li>
+ {\% endfor %}
+ </ul>
+ <p style="margin-top: auto"><a href="{\{ get_url(path=solution.path) \}}">Learn more</a></p>
+ </article>
+ {\% endfor %}
+ </section>
+ {\% endif %}
+{\% endfor %}
+{\% endblock main_content %}index c0c552c..6482e75 100644
--- a/templates/solution.html
+++ b/templates/solution.html
@@ -11,7 +11,7 @@ Solution
=
=Do you use {\{ page.title \}} in your business? We can help you switch to one of the viable alternatives.
=
-
+{# TODO: Do not print the heading if there are no alternatives. Print something else. #}
={\% for category in page.taxonomies.solution_categories %}
= <h2>Alternatives in the {\{ category \}} category:</h2>
=
@@ -32,7 +32,10 @@ Do you use {\{ page.title \}} in your business? We can help you switch to one of t
= <p>{\{ alternative.summary | safe | striptags \}}</p>
= <ul>
= {\% for feature in alternative.taxonomies.features %}
- <li>{\{ feature \}}</li>
+ {\% set feature_slug = feature | slugify %}
+ <li>
+ <a href="{\{ get_url(path="/features/" ~ feature_slug) \}}">{\{ feature \}}</a>
+ </li>
= {\% endfor %}
= </ul>
= <p style="margin-top: auto"><a href="{\{ get_url(path=alternative.path) \}}">Learn more</a></p>index 3994c55..1f6a8c8 100644
--- a/templates/solutions.html
+++ b/templates/solutions.html
@@ -1,7 +1,5 @@
={\% extends "base.html" %}
=
-Solution
-
={\% block main_content %}
= {\{ section.content | safe \}}
=
@@ -16,9 +14,12 @@ Solution
= <h3>{\{ solution.title \}}</h3>
= <p>{\{ solution.summary | safe | striptags \}}</p>
= <ul>
- {\% for feature in solution.taxonomies.features %}
- <li>{\{ feature \}}</li>
- {\% endfor %}
+ {\% for feature in solution.taxonomies.features %}
+ {\% set feature_slug = feature | slugify %}
+ <li>
+ <a href="{\{ get_url(path="/features/" ~ feature_slug) \}}">{\{ feature \}}</a>
+ </li>
+ {\% endfor %}
= </ul>
= <p style="margin-top: auto"><a href="{\{ get_url(path=solution.path) \}}">Learn more</a></p>
= </article>Use a component (aka macro) for features bullets
The bullets are rendered in three templates as of now:
- the solutions index page
- a single solution page
- a single feature page
There will be more in the future, and the bullets will evolve (icons, tooltips, who knows what else). The bullets themselves are part of bigger blocks: solution card within a solutions card grid. Those two also should be re-implemented as components.
new file mode 100644
index 0000000..e34a337
--- /dev/null
+++ b/templates/components.html
@@ -0,0 +1,8 @@
+{# These are reusable components (macros) #}
+
+{\% macro feature_bullet(feature) %}
+{\% set feature_slug = feature | slugify %}
+<li>
+ <a href="{\{ get_url(path="/features/" ~ feature_slug) \}}">{\{ feature \}}</a>
+</li>
+{\% endmacro feature_bullet %}index b37b27f..731b828 100644
--- a/templates/features/single.html
+++ b/templates/features/single.html
@@ -2,6 +2,8 @@
=
={\% extends "base.html" %}
=
+{\% import "components.html" as components %}
+
={\% block main_content %}
=
={\% set feature_slug = term.name | slugify %}
@@ -38,10 +40,7 @@
= <p>{\{ solution.summary | safe | striptags \}}</p>
= <ul>
= {\% for feature in solution.taxonomies.features %}
- {\% set feature_slug = feature | slugify %}
- <li>
- <a href="{\{ get_url(path="/features/" ~ feature_slug) \}}">{\{ feature \}}</a>
- </li>
+ {\{ components::feature_bullet(feature=feature) \}}
= {\% endfor %}
= </ul>
= <p style="margin-top: auto"><a href="{\{ get_url(path=solution.path) \}}">Learn more</a></p>index 6482e75..c164f1d 100644
--- a/templates/solution.html
+++ b/templates/solution.html
@@ -1,6 +1,8 @@
+{# This is a template for a single solution page #}
+
={\% extends "base.html" %}
=
-Solution
+{\% import "components.html" as components %}
=
={\% block main_content %}
={\{ page.content | safe \}}
@@ -32,10 +34,7 @@ Do you use {\{ page.title \}} in your business? We can help you switch to one of t
= <p>{\{ alternative.summary | safe | striptags \}}</p>
= <ul>
= {\% for feature in alternative.taxonomies.features %}
- {\% set feature_slug = feature | slugify %}
- <li>
- <a href="{\{ get_url(path="/features/" ~ feature_slug) \}}">{\{ feature \}}</a>
- </li>
+ {\{ components::feature_bullet(feature=feature) \}}
= {\% endfor %}
= </ul>
= <p style="margin-top: auto"><a href="{\{ get_url(path=alternative.path) \}}">Learn more</a></p>index 1f6a8c8..ed2d441 100644
--- a/templates/solutions.html
+++ b/templates/solutions.html
@@ -1,5 +1,9 @@
+{# This is a template for the /solutions/ index page #}
+
={\% extends "base.html" %}
=
+{\% import "components.html" as components %}
+
={\% block main_content %}
= {\{ section.content | safe \}}
=
@@ -15,10 +19,7 @@
= <p>{\{ solution.summary | safe | striptags \}}</p>
= <ul>
= {\% for feature in solution.taxonomies.features %}
- {\% set feature_slug = feature | slugify %}
- <li>
- <a href="{\{ get_url(path="/features/" ~ feature_slug) \}}">{\{ feature \}}</a>
- </li>
+ {\{ components::feature_bullet(feature=feature) \}}
= {\% endfor %}
= </ul>
= <p style="margin-top: auto"><a href="{\{ get_url(path=solution.path) \}}">Learn more</a></p>DRY on the solution card component
Don't Repeat Yourself.
index e34a337..3bc698d 100644
--- a/templates/components.html
+++ b/templates/components.html
@@ -2,7 +2,20 @@
=
={\% macro feature_bullet(feature) %}
={\% set feature_slug = feature | slugify %}
-<li>
+ <li>
= <a href="{\{ get_url(path="/features/" ~ feature_slug) \}}">{\{ feature \}}</a>
-</li>
+ </li>
={\% endmacro feature_bullet %}
+
+{\% macro solution_card(solution) %}
+ <article>
+ <h3>{\{ solution.title \}}</h3>
+ <p>{\{ solution.summary | safe | striptags \}}</p>
+ <ul>
+ {\% for feature in solution.taxonomies.features %}
+ {\{ self::feature_bullet(feature=feature) \}}
+ {\% endfor %}
+ </ul>
+ <p style="margin-top: auto"><a href="{\{ get_url(path=solution.path) \}}">Learn more</a></p>
+ </article>
+{\% endmacro solution_card %}index 731b828..3cd5390 100644
--- a/templates/features/single.html
+++ b/templates/features/single.html
@@ -34,17 +34,7 @@
= <h2>{\{ category.name | title \}}</h2>
= <section class="card-grid">
= {\% for solution in solutions %}
- {# TODO: Dry with solution.html - a macro? #}
- <article>
- <h3>{\{ solution.title \}}</h3>
- <p>{\{ solution.summary | safe | striptags \}}</p>
- <ul>
- {\% for feature in solution.taxonomies.features %}
- {\{ components::feature_bullet(feature=feature) \}}
- {\% endfor %}
- </ul>
- <p style="margin-top: auto"><a href="{\{ get_url(path=solution.path) \}}">Learn more</a></p>
- </article>
+ {\{ components::solution_card(solution=solution) \}}
= {\% endfor %}
= </section>
= {\% endif %}index c164f1d..5042efe 100644
--- a/templates/solution.html
+++ b/templates/solution.html
@@ -29,16 +29,7 @@ Do you use {\{ page.title \}} in your business? We can help you switch to one of t
=
= <section class="card-grid">
= {\% for alternative in alternatives %}
- <article>
- <h3>{\{ alternative.title \}}</h3>
- <p>{\{ alternative.summary | safe | striptags \}}</p>
- <ul>
- {\% for feature in alternative.taxonomies.features %}
- {\{ components::feature_bullet(feature=feature) \}}
- {\% endfor %}
- </ul>
- <p style="margin-top: auto"><a href="{\{ get_url(path=alternative.path) \}}">Learn more</a></p>
- </article>
+ {\{ components::solution_card(solution=alternative) \}}
= {\% endfor %}
= </section>
= {\% endfor %}index ed2d441..1deb8ac 100644
--- a/templates/solutions.html
+++ b/templates/solutions.html
@@ -14,16 +14,7 @@
= <section class="card-grid">
= {\% for solution in category.pages %}
= {# TODO: Dry with solution.html - a macro? #}
- <article>
- <h3>{\{ solution.title \}}</h3>
- <p>{\{ solution.summary | safe | striptags \}}</p>
- <ul>
- {\% for feature in solution.taxonomies.features %}
- {\{ components::feature_bullet(feature=feature) \}}
- {\% endfor %}
- </ul>
- <p style="margin-top: auto"><a href="{\{ get_url(path=solution.path) \}}">Learn more</a></p>
- </article>
+ {\{ components::solution_card(solution=solution) \}}
= {\% endfor %}
= </section>
= {\% endfor %}add microsoft 365 mail as an email provider
new file mode 100644
index 0000000..6dd0278
--- /dev/null
+++ b/content/solutions/m365-mail/index.md
@@ -0,0 +1,25 @@
+---
+title: M365 Mail
+
+taxonomies:
+ solution_providers: [ "Microsoft" ]
+ solution_categories: [ "email provider" ]
+ features:
+ - "part of M365 Office suite"
+ - "enterprise focus"
+ - "US jurisdiction"
+---
+
+Microsoft offers email for business as part of its Microsoft 365 line of services.
+
+<!-- more -->
+
+Esc Collective recommends against using Microsoft 365 for email in your business, for multiple reasons:
+- Microsoft is based in the US, and is obliged to follow any court orders or presidential executive orders. Also if that involves violating international law, exposing your personal data or company secrets, or stopping business critical services without prior notice. This was painfully demonstrated when [Microsoft cut its email services to a staff member of the International Criminal Court](https://apnews.com/article/icc-trump-sanctions-karim-khan-court-a4b4c02751ab84c09718b1b95cbd5db3) after the US president sanctioned the ICC due to an ongoing procedure against Israeli politicians.
+- Big tech companies such as Microsoft do not respect EU regulations such as those for privacy - the General Data Protection Regulation (GDPR). While European politicians still pretend the [EU-US data privacy framework (DPF)](https://ec.europa.eu/commission/presscorner/detail/en/qanda_23_3752) is in place, anyone reading the news about US politics concludes the DPF is defunct. This means any services provided by US companies, even when hosted in Europe, are no longer compliant with GDPR.
+- Microsoft has built its business on vendor lock-in, and does not hesitate to use its monopolist position to raise prices or discontinue functionalities critical for some businesses.
+- Microsoft has [strong ties to the increasingly hostile regime of Donald Trump](https://www.cnbc.com/2025/01/09/microsoft-contributes-1-million-to-trumps-inauguration-fund.html)
+
+While Microsoft 365 offers a very complete set of services and sports "enterprise grade security", properly managing a M365 tenant and keeping it secure is a hard task for small and medium sized companies. Microsoft products are built with enterprises in mind, their architecture and administrative tools are not made for companies with limited IT staff and budgets. Also the listed threats on privacy, business continuity and business ethics far outweigh the advantages the Microsoft solutions might offer.
+
+Home page: <https://www.office.com/>Do not push alternatives to supported solutions
Use different text for supported vs not supported solutions.
index 3bc698d..c4ba2c3 100644
--- a/templates/components.html
+++ b/templates/components.html
@@ -19,3 +19,11 @@
= <p style="margin-top: auto"><a href="{\{ get_url(path=solution.path) \}}">Learn more</a></p>
= </article>
={\% endmacro solution_card %}
+
+{\% macro natural_list (items) %}
+{# Produce a natural sounding list, like "foo, bar and baz" #}
+ {\%- for item in items -%}
+ {\%- if loop.first %} {\% elif loop.last %} and {\% else %}, {\% endif %}
+ {\{ item | safe \}}
+ {\%- endfor -%}
+{\% endmacro natural_list %}index 5042efe..6c9c832 100644
--- a/templates/solution.html
+++ b/templates/solution.html
@@ -11,11 +11,19 @@
= set alternatives = []
=%}
=
-Do you use {\{ page.title \}} in your business? We can help you switch to one of the viable alternatives.
+<p>
+ Do you use {\{ page.title \}} in your business?
+ {\% if page.taxonomies.esc_support %}
+ Esc Collective offers support in form of {\{ components::natural_list (items=page.taxonomies.esc_support) \}}.
+ {\% else %}
+ We can help you switch to one of the viable alternatives.
+ {\% endif %}
+</p>
+
=
={# TODO: Do not print the heading if there are no alternatives. Print something else. #}
={\% for category in page.taxonomies.solution_categories %}
- <h2>Alternatives in the {\{ category \}} category:</h2>
+ <h2>Alternatives to {\{ page.title \}} in the {\{ category \}} category:</h2>
=
= {\% set term = get_taxonomy_term(kind="solution_categories", term=category) %}
= {\% set_global alternatives = [] %}Do not print a heading when no alternatives
index 6c9c832..9913136 100644
--- a/templates/solution.html
+++ b/templates/solution.html
@@ -23,8 +23,6 @@
=
={# TODO: Do not print the heading if there are no alternatives. Print something else. #}
={\% for category in page.taxonomies.solution_categories %}
- <h2>Alternatives to {\{ page.title \}} in the {\{ category \}} category:</h2>
-
= {\% set term = get_taxonomy_term(kind="solution_categories", term=category) %}
= {\% set_global alternatives = [] %}
= {\% for solution in term.pages %}
@@ -34,6 +32,8 @@
= {\% endif %}
= {\% endfor %}
=
+ {\% if not alternatives %}{\% continue %}{\% endif %}
+ <h2>Alternatives to {\{ page.title \}} in the {\{ category \}} category:</h2>
=
= <section class="card-grid">
= {\% for alternative in alternatives %}