From 80542825e849f4c983af3492de09bab143e7925a Mon Sep 17 00:00:00 2001 From: Zachary Klosko Date: Sat, 31 Oct 2020 23:23:29 -0400 Subject: [PATCH 01/12] Starting work on new design for docs pages --- docs/_config.yml | 5 + docs/_data/nav.yml | 57 +-- docs/{ => _docs}/analytics.md | 4 +- docs/{ => _docs}/api.md | 2 +- docs/{ => _docs}/backing-up-the-server.md | 3 +- docs/{ => _docs}/contribute.md | 2 +- docs/{ => _docs}/freeipa.md | 3 +- docs/{ => _docs}/hd-audio-modules.md | 3 +- docs/{ => _docs}/icecast-shoutcast.md | 3 +- docs/{ => _docs}/install.md | 2 +- docs/{ => _docs}/interface-customization.md | 3 +- docs/{ => _docs}/interface-localization.md | 2 +- docs/{ => _docs}/library.md | 3 +- docs/{ => _docs}/live-broadcast.md | 3 +- docs/{ => _docs}/microsite.md | 3 +- docs/{ => _docs}/preparing-media.md | 3 +- docs/{ => _docs}/rights-royalties.md | 2 +- docs/{ => _docs}/scheduling-shows.md | 2 +- docs/{ => _docs}/settings.md | 2 +- docs/{ => _docs}/troubleshooting.md | 2 +- docs/{ => _docs}/upgrading.md | 2 +- docs/{ => _docs}/users.md | 3 +- docs/{ => _docs}/vagrant.md | 2 +- docs/_includes/head.html | 8 +- docs/_includes/navbar.html | 19 +- docs/_layouts/404.html | 41 +- docs/_layouts/article.html | 37 ++ docs/_layouts/contacts.html | 307 ++++++++++++ docs/_layouts/default.html | 54 +-- docs/_layouts/docindex.html | 141 ++++++ docs/_layouts/faq.html | 504 ++++++++++++++++++++ docs/_layouts/guides.html | 137 ------ docs/css/creative.min.css | 3 +- docs/docs.md | 4 + docs/guides.md | 4 - 35 files changed, 1055 insertions(+), 320 deletions(-) rename docs/{ => _docs}/analytics.md (99%) rename docs/{ => _docs}/api.md (99%) rename docs/{ => _docs}/backing-up-the-server.md (99%) rename docs/{ => _docs}/contribute.md (98%) rename docs/{ => _docs}/freeipa.md (99%) rename docs/{ => _docs}/hd-audio-modules.md (99%) rename docs/{ => _docs}/icecast-shoutcast.md (99%) rename docs/{ => _docs}/install.md (99%) rename docs/{ => _docs}/interface-customization.md (98%) rename docs/{ => _docs}/interface-localization.md (99%) rename docs/{ => _docs}/library.md (99%) rename docs/{ => _docs}/live-broadcast.md (99%) rename docs/{ => _docs}/microsite.md (97%) rename docs/{ => _docs}/preparing-media.md (99%) rename docs/{ => _docs}/rights-royalties.md (99%) rename docs/{ => _docs}/scheduling-shows.md (99%) rename docs/{ => _docs}/settings.md (99%) rename docs/{ => _docs}/troubleshooting.md (99%) rename docs/{ => _docs}/upgrading.md (98%) rename docs/{ => _docs}/users.md (98%) rename docs/{ => _docs}/vagrant.md (99%) create mode 100644 docs/_layouts/article.html create mode 100644 docs/_layouts/contacts.html create mode 100644 docs/_layouts/docindex.html create mode 100644 docs/_layouts/faq.html delete mode 100644 docs/_layouts/guides.html create mode 100644 docs/docs.md delete mode 100644 docs/guides.md diff --git a/docs/_config.yml b/docs/_config.yml index 09770b95e..ee87b9234 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -18,6 +18,11 @@ headsubtext: Everything you need to get your terrestrial / on-line radio station headbuttonurl: index#get-started headbuttontext: Get LibreTime +# Collections Settings +collections: + docs: + output: true + # Build settings plugins: - kramdown diff --git a/docs/_data/nav.yml b/docs/_data/nav.yml index 6390c9483..9bc465453 100644 --- a/docs/_data/nav.yml +++ b/docs/_data/nav.yml @@ -1,50 +1,7 @@ -# Navigation for Docs page (guides.md) -djguides: -- page: Broadcasting Live - url: live-broadcast -- page: Using the Library (Playlists, Smartblocks, Webstreams, Podcasts) - url: library -- page: Preparing Media - url: preparing-media -- page: Scheduling Shows - url: scheduling-shows - -managerguides: -- page: Playout History & Auditing - url: analytics -- page: Rights and Royalties - url: rights-royalties -- page: User Management - url: users - -adminguides: -- page: API Usage - url: api -- page: Backing up LibreTime - url: backing-up-the-server -- page: Built-in Microsite - url: microsite -- page: Configuring Icecast/Shoutcast - url: icecast-shoutcast -- page: FreeIPA Configuration - url: freeipa -- page: HD Audio Modules Reference - url: hd-audio-modules -- page: Installing Libretime - url: install -- page: Interface Customization - url: interface-customization -- page: Settings - url: settings -- page: Troubleshooting/Uninstall - url: troubleshooting -- page: Upgrading LibreTime - url: upgrading - -devguides: -- page: Contributing to Libretime - url: contribute -- page: Developing with Vagrant - url: vagrant -- page: Translate Libretime - url: interface-localization +navbarlink: +- name: Install + url: /install +- name: Docs + url: /docs +- name: Github + url: https://github.com/LibreTime/libretime \ No newline at end of file diff --git a/docs/analytics.md b/docs/_docs/analytics.md similarity index 99% rename from docs/analytics.md rename to docs/_docs/analytics.md index e2c1363e9..828e9b6f6 100644 --- a/docs/analytics.md +++ b/docs/_docs/analytics.md @@ -1,8 +1,8 @@ --- -layout: default +layout: article title: Analytics git: analytics.md - +category: manager --- ## History {#history} diff --git a/docs/api.md b/docs/_docs/api.md similarity index 99% rename from docs/api.md rename to docs/_docs/api.md index e43062920..a72c5798b 100644 --- a/docs/api.md +++ b/docs/_docs/api.md @@ -1,5 +1,5 @@ --- -layout: default +layout: article title: LibreTime API Usage git: api.md --- diff --git a/docs/backing-up-the-server.md b/docs/_docs/backing-up-the-server.md similarity index 99% rename from docs/backing-up-the-server.md rename to docs/_docs/backing-up-the-server.md index 4ecc57540..9228d5eb8 100644 --- a/docs/backing-up-the-server.md +++ b/docs/_docs/backing-up-the-server.md @@ -1,6 +1,7 @@ --- -layout: default +layout: article title: Backing Up The Server +category: admin git: backing-up-the-server.md --- diff --git a/docs/contribute.md b/docs/_docs/contribute.md similarity index 98% rename from docs/contribute.md rename to docs/_docs/contribute.md index 068bd0d4e..179ddf2f2 100644 --- a/docs/contribute.md +++ b/docs/_docs/contribute.md @@ -1,5 +1,5 @@ --- -layout: default +layout: article title: Contributing to LibreTime git: contribute.md --- diff --git a/docs/freeipa.md b/docs/_docs/freeipa.md similarity index 99% rename from docs/freeipa.md rename to docs/_docs/freeipa.md index 1e33f3b7b..f9f962ced 100644 --- a/docs/freeipa.md +++ b/docs/_docs/freeipa.md @@ -1,6 +1,7 @@ --- -layout: default +layout: article title: FreeIPA Configuration +category: admin --- You can configure LibreTime to delegate all authentication to a FreeIPA server. diff --git a/docs/hd-audio-modules.md b/docs/_docs/hd-audio-modules.md similarity index 99% rename from docs/hd-audio-modules.md rename to docs/_docs/hd-audio-modules.md index f97025cd0..281d00485 100644 --- a/docs/hd-audio-modules.md +++ b/docs/_docs/hd-audio-modules.md @@ -1,7 +1,8 @@ --- -layout: default +layout: article title: HD Audio Modules git: hd-audio-modules.md +category: admin --- This listing is provided to help ensure that the correct model parameter is passed to the ALSA kernel module for an Intel HDA soundcard, if one is fitted to your LibreTime server. See the chapter *Preparing the server* in this book for more details. diff --git a/docs/icecast-shoutcast.md b/docs/_docs/icecast-shoutcast.md similarity index 99% rename from docs/icecast-shoutcast.md rename to docs/_docs/icecast-shoutcast.md index 45f7767aa..9ff2ee0d7 100644 --- a/docs/icecast-shoutcast.md +++ b/docs/_docs/icecast-shoutcast.md @@ -1,7 +1,8 @@ --- -layout: default +layout: article title: Icecast and Shoutcast Stream Configuration git: icecast-shoutcast.md +category: admin --- ## Background diff --git a/docs/install.md b/docs/_docs/install.md similarity index 99% rename from docs/install.md rename to docs/_docs/install.md index a29a569dc..2775593f9 100644 --- a/docs/install.md +++ b/docs/_docs/install.md @@ -1,6 +1,6 @@ --- title: Install -layout: default +layout: article git: install.md --- diff --git a/docs/interface-customization.md b/docs/_docs/interface-customization.md similarity index 98% rename from docs/interface-customization.md rename to docs/_docs/interface-customization.md index 1b282ef00..56d5adf72 100644 --- a/docs/interface-customization.md +++ b/docs/_docs/interface-customization.md @@ -1,7 +1,8 @@ --- -layout: default +layout: article title: Modifying the LibreTime interface git: interface-customization.md +category: admin --- diff --git a/docs/interface-localization.md b/docs/_docs/interface-localization.md similarity index 99% rename from docs/interface-localization.md rename to docs/_docs/interface-localization.md index a0aeb1833..b3c4764cc 100644 --- a/docs/interface-localization.md +++ b/docs/_docs/interface-localization.md @@ -1,5 +1,5 @@ --- -layout: default +layout: article title: Interface Localization git: interface-localization.md --- diff --git a/docs/library.md b/docs/_docs/library.md similarity index 99% rename from docs/library.md rename to docs/_docs/library.md index e2a4eda5f..221082243 100644 --- a/docs/library.md +++ b/docs/_docs/library.md @@ -1,7 +1,8 @@ --- -layout: default +layout: article title: Library git: library.md +category: dj --- ## Dashboard {#dashboard} diff --git a/docs/live-broadcast.md b/docs/_docs/live-broadcast.md similarity index 99% rename from docs/live-broadcast.md rename to docs/_docs/live-broadcast.md index eb69bbf5d..3012f8e66 100644 --- a/docs/live-broadcast.md +++ b/docs/_docs/live-broadcast.md @@ -1,7 +1,8 @@ --- -layout: default +layout: article title: Broadcasting live with MIXXX or B.U.T.T. git: live-broadcast.md +category: dj --- ## Live shows with MIXXX {#mixxx} diff --git a/docs/microsite.md b/docs/_docs/microsite.md similarity index 97% rename from docs/microsite.md rename to docs/_docs/microsite.md index 0705dd465..7c3badc62 100644 --- a/docs/microsite.md +++ b/docs/_docs/microsite.md @@ -1,7 +1,8 @@ --- -layout: default +layout: article title: Built-in Microsite git: microsite.md +category: admin --- ## Overview diff --git a/docs/preparing-media.md b/docs/_docs/preparing-media.md similarity index 99% rename from docs/preparing-media.md rename to docs/_docs/preparing-media.md index 25395a84e..0c8382d81 100644 --- a/docs/preparing-media.md +++ b/docs/_docs/preparing-media.md @@ -1,7 +1,8 @@ --- -layout: default +layout: article title: Preparing Media for LibreTime git: preparing-media.md +category: dj --- Before uploading media to an LibreTime server, there are a number of factors which should be considered. Getting your ingest workflow right will save you a lot of time later. diff --git a/docs/rights-royalties.md b/docs/_docs/rights-royalties.md similarity index 99% rename from docs/rights-royalties.md rename to docs/_docs/rights-royalties.md index f02adf87b..9d0b633bf 100644 --- a/docs/rights-royalties.md +++ b/docs/_docs/rights-royalties.md @@ -1,5 +1,5 @@ --- -layout: default +layout: article title: Rights and Royalties git: rights-royalties.md --- diff --git a/docs/scheduling-shows.md b/docs/_docs/scheduling-shows.md similarity index 99% rename from docs/scheduling-shows.md rename to docs/_docs/scheduling-shows.md index 257b13350..0799704a5 100644 --- a/docs/scheduling-shows.md +++ b/docs/_docs/scheduling-shows.md @@ -1,5 +1,5 @@ --- -layout: default +layout: article title: Scheduling Shows git: scheduling-shows.md --- diff --git a/docs/settings.md b/docs/_docs/settings.md similarity index 99% rename from docs/settings.md rename to docs/_docs/settings.md index c57b96007..5cfb28f0f 100644 --- a/docs/settings.md +++ b/docs/_docs/settings.md @@ -1,5 +1,5 @@ --- -layout: default +layout: article title: Settings git: settings.md --- diff --git a/docs/troubleshooting.md b/docs/_docs/troubleshooting.md similarity index 99% rename from docs/troubleshooting.md rename to docs/_docs/troubleshooting.md index a65373835..e5212c3a2 100644 --- a/docs/troubleshooting.md +++ b/docs/_docs/troubleshooting.md @@ -1,5 +1,5 @@ --- -layout: default +layout: article title: Troubleshooting git: troubleshooting.md --- diff --git a/docs/upgrading.md b/docs/_docs/upgrading.md similarity index 98% rename from docs/upgrading.md rename to docs/_docs/upgrading.md index 8423f1b6b..46772b4e7 100644 --- a/docs/upgrading.md +++ b/docs/_docs/upgrading.md @@ -1,5 +1,5 @@ --- -layout: default +layout: article title: Upgrading LibreTime git: upgrading.md --- diff --git a/docs/users.md b/docs/_docs/users.md similarity index 98% rename from docs/users.md rename to docs/_docs/users.md index d8f49599c..a46951070 100644 --- a/docs/users.md +++ b/docs/_docs/users.md @@ -1,7 +1,8 @@ --- -layout: default +layout: article title: Managing Users git: users.md +category: manager --- > Note: if your Airtime server is accessible from the public Internet (ex. being hosted in a cloud VM) diff --git a/docs/vagrant.md b/docs/_docs/vagrant.md similarity index 99% rename from docs/vagrant.md rename to docs/_docs/vagrant.md index 197136740..21700507e 100644 --- a/docs/vagrant.md +++ b/docs/_docs/vagrant.md @@ -1,5 +1,5 @@ --- -layout: default +layout: article title: Using Vagrant and Virtualbox for developing LibreTime git: vagrant.md --- diff --git a/docs/_includes/head.html b/docs/_includes/head.html index f1a49e89e..2a04fecbb 100644 --- a/docs/_includes/head.html +++ b/docs/_includes/head.html @@ -6,12 +6,14 @@ - + - + - \ No newline at end of file + + + {{ site.title }} - {{ page.title }} \ No newline at end of file diff --git a/docs/_includes/navbar.html b/docs/_includes/navbar.html index dd101e213..a79fd1787 100644 --- a/docs/_includes/navbar.html +++ b/docs/_includes/navbar.html @@ -7,24 +7,11 @@ diff --git a/docs/_layouts/404.html b/docs/_layouts/404.html index d57fe6438..85dad0a60 100644 --- a/docs/_layouts/404.html +++ b/docs/_layouts/404.html @@ -1,43 +1,16 @@ - - - - {% include head.html %} - - - - {{ site.title }} - {{ page.title }} - - +--- +layout: default +--- - - {% include navbar.html %} - -
-
+
+
404 Tape

Page not found :(

The requested page could not be found.

- + Back to Docs
- - {% include footer.html %} - {% include scripts.html %} - - + diff --git a/docs/_layouts/article.html b/docs/_layouts/article.html new file mode 100644 index 000000000..f518edaa3 --- /dev/null +++ b/docs/_layouts/article.html @@ -0,0 +1,37 @@ +--- +layout: default +--- + + +
+
+
+
+

{{ page.title }}

+ +
+
+
+
+ + +
+
+
+
+ {{ content }} +
+ +
+
+
+

Contents

+
+
+
{% include toc.html html=content class=toc %}
+
+
+
+
+
+
diff --git a/docs/_layouts/contacts.html b/docs/_layouts/contacts.html new file mode 100644 index 000000000..f3d0c92a7 --- /dev/null +++ b/docs/_layouts/contacts.html @@ -0,0 +1,307 @@ + + + + + Contacts | Docs - UI Kit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+
+
+
+

Let's Talk

+
You came to the right place
+
+
+
+ + + + + + + +
+ + + +
+
+
+
+
+ + +

Live Chat

+ Chat with our live support +
+
+
+
+ + +

Support Ticket

+ Submit a ticket +
+
+
+
+ + +

Phone Number

+ 1-800-643-4500 +
+
+
+
+
+ + +
+
+
+
+
+

Say Hello!

+

Usually, we reply within 2 business days

+
+ + +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ +
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index fbb148448..ae92c0934 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -3,10 +3,6 @@ {% include head.html %} - - - {{ site.title }} - {{ page.title }} @@ -21,55 +17,7 @@ Back to top -
-
-
-
- - - Back - -
-

-
- - -
-
-
-
-
-
-
- -
{% include toc.html html=content class=toc %}
-
-
- -
-
-
- -
{% include toc.html html=content class=toc %}
-
-
-
-
+ {{content}} {% include footer.html %} diff --git a/docs/_layouts/docindex.html b/docs/_layouts/docindex.html new file mode 100644 index 000000000..228fff4f4 --- /dev/null +++ b/docs/_layouts/docindex.html @@ -0,0 +1,141 @@ +--- +layout: default +--- + +
+
+
+
+

Knowledge Base

+

All of the guides you need to set up, maintain, and use Libretime, all in one place.

+
+ +
+
+
+
+
+

Install

+

Get Libretime up and running in just 10 minutes.

+
+
+
+ +
+
+
+
+

Configure

+

We strive to embrace and drive change in our industry.

+
+
+
+ +
+
+
+
+

Schedule Shows

+

We strive to embrace and drive change in our industry.

+
+
+
+ +
+
+
+
+

User Accounts

+

Learn how to add, change, and delete user accounts.

+
+
+
+ +
+
+
+
+

Libretime for FM Radio

+

We strive to embrace and drive change in our industry.

+
+
+
+ +
+
+
+
+

FAQs

+

Have questions?
We have answers.

+
+
+
+
+
+
+ + +
+
+
+
+
+

Can't find what you're looking for?

+

Let us help you!

+
+ + +
+
+
+
+ + +
+
+
+
+ +

For DJs

+ +
    + {% for doc in site.docs %} + {% if doc.category == "dj" %} +
  • {{doc.title}}
  • + {% endif %} + {% endfor %} +
+
+ +
+ +

For Program Managers

+ +
    + {% for doc in site.docs %} + {% if doc.category == "manager" %} +
  • {{doc.title}}
  • + {% endif %} + {% endfor %} +
+
+ +
+ +

For System Administrators

+ +
    + {% for doc in site.docs %} + {% if doc.category == "admin" %} +
  • {{doc.title}}
  • + {% endif %} + {% endfor %} +
+
+
+
+
+
+ diff --git a/docs/_layouts/faq.html b/docs/_layouts/faq.html new file mode 100644 index 000000000..a30181d17 --- /dev/null +++ b/docs/_layouts/faq.html @@ -0,0 +1,504 @@ + + + + + FAQ | Docs - UI Kit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+
+
+
+

Frequently Asked Questions

+ + +
+
+
+ + + + + + + +
+ + +
+
+
+ + +
+
+
+
+
+
+
What is Docs UI Kit?
+ +
+ +
+
Docs UI Kit is beautiful Open Source Bootstrap 4 UI Kit under MIT license. The UI Kit comes with more than 10 beautiful complete pages and includes a lot of reusable and customizable UI Blocks. Furthermore, Docs UI Kit includes 4 different Documentation Layouts.
+
+
+ +
+ +
+
Yes! It's just absolutely FREE and comes with a simple license! If you have any questions or doubts, please feel free to reach us here.
+
+
+ +
+ +
+
Yes! Docs UI Kit is absolutely Free and you can use in both personal and commercial projects without any attribution. However, we would much appreciate for any attribution.
+
+
+ +
+
+
Where can I download Docs UI Kit?
+ +
+ +
+
You can download Docs UI Kit on Htmlstream website or on the Github page.
+
+
+ +
+ +
+
Corrently, all our freebies comes with community support and you can open an issue on the product github page. We will do our best to answers your questions.
+
+
+ +
+ +
+
Sure! Please feel free to contribute - we appreciate any community help! You can pull request on github page.
+
+
+ +
+ +
+
You can check out our documenttion for detailed examples.
+
+
+ +
+ +
+
Docs UI Kit comes with 10+ example pages.
+
+
+
+
+ +
+
+
+
+
Installation Group Item #1
+ +
+ +
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
+
+
+ +
+ +
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
+
+
+ +
+ +
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
+
+
+
+
+ +
+
+
+
+
Update Group Item #1
+ +
+ +
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
+
+
+ +
+ +
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
+
+
+ +
+ +
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
+
+
+
+
+ +
+
+
+
+
Gitlab Group Item #1
+ +
+ +
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
+
+
+ +
+ +
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
+
+
+ +
+ +
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
+
+
+
+
+
+
+
+
+ +
+
+ +

Can't find what you're looking for?

+
Let us help you right now!
+ Submit a Request +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_layouts/guides.html b/docs/_layouts/guides.html deleted file mode 100644 index b583eb21a..000000000 --- a/docs/_layouts/guides.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - {% include head.html %} - - - - {{ site.title }} - {{ page.title }} - - - - - {% include navbar.html %} - - - - - - - Back to top - - - - - -
-
-
-
-

Docs for DJs

-
-

- Basic guides for broadcasting with Libretime can be found here, perfect for DJs. -

- {% for item in site.data.nav.djguides %} -
  • - {{ item.page }} -
  • - {% endfor %} -
    -
    -
    -
    - -
    -
    -
    -
    -

    Docs for Program Managers

    -
    -

    - Program Managers, sometimes known as Program Directors, manage DJs and are responsible for maintaining the station's on-air calendar. All the help Program Managers need is right here. (Note: guides for DJs will also be helpful for programming Libretime) -

    - {% for item in site.data.nav.managerguides %} -
  • - {{ item.page }} -
  • - {% endfor %} -
    -
    -
    -
    - -
    -
    -
    -
    -

    Docs for System Administrators

    -
    -

    - System administrators can find all of the information they need here to configure and maintain their LibreTime instance. -

    - {% for item in site.data.nav.adminguides %} -
  • - {{ item.page }} -
  • - {% endfor %} -
    -
    -
    -
    - -
    -
    -
    -
    -

    Docs for Developers

    -
    -

    - Calling all developers! Find the information you need to get started at the links below. -

    - {% for item in site.data.nav.devguides %} -
  • - {{ item.page }} -
  • - {% endfor %} -
    -
    -
    -
    - - {% include footer.html %} - - {% include scripts.html %} - - diff --git a/docs/css/creative.min.css b/docs/css/creative.min.css index 376e90bb0..8a44eb7a7 100755 --- a/docs/css/creative.min.css +++ b/docs/css/creative.min.css @@ -83,7 +83,8 @@ div.text-footer{ text-align: center; } -section{padding:8rem 0} +/* Changed padding */ +section{padding:2rem 0} .section-heading{margin-top:0} ::-moz-selection{color:#fff;background:#212529;text-shadow:none} ::selection{color:#fff;background:#212529;text-shadow:none} diff --git a/docs/docs.md b/docs/docs.md new file mode 100644 index 000000000..6d6191810 --- /dev/null +++ b/docs/docs.md @@ -0,0 +1,4 @@ +--- +title: Docs +layout: docindex +--- \ No newline at end of file diff --git a/docs/guides.md b/docs/guides.md deleted file mode 100644 index d0355b18d..000000000 --- a/docs/guides.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Docs -layout: guides ---- \ No newline at end of file From 7dab6edb836951e995adeae34c35d2745f04dd2f Mon Sep 17 00:00:00 2001 From: Zachary Klosko Date: Mon, 2 Nov 2020 13:20:34 -0500 Subject: [PATCH 02/12] Commiting to test quickstart.sh --- docs/_config.yml | 4 +- docs/_data/nav.yml | 7 - docs/_docs/calendar.md | 57 ++ docs/_docs/dashboard.md | 121 ++++ docs/_docs/freeipa.md | 2 +- docs/_docs/host-configuration.md | 280 +++++++++ docs/_docs/install.md | 550 +----------------- docs/_docs/library.md | 239 -------- docs/_docs/listener-stats.md | 15 + docs/_docs/microsite.md | 5 +- docs/_docs/playlists.md | 61 ++ .../{analytics.md => playout-history.md} | 25 +- docs/_docs/podcasts.md | 37 ++ docs/_docs/preparing-media.md | 37 +- docs/_docs/reverse-proxy.md | 118 ++++ docs/_docs/rights-royalties.md | 2 +- docs/_docs/scheduling-shows.md | 69 +-- docs/_docs/ssl.md | 140 +++++ docs/_docs/users.md | 3 +- docs/_docs/webstreams.md | 21 + docs/_includes/fans.html | 4 +- docs/_includes/footer.html | 59 +- docs/_includes/navbar.html | 12 +- docs/_includes/toc.html | 112 ---- docs/_layouts/404.html | 2 +- docs/_layouts/article.html | 26 +- docs/_layouts/docindex.html | 16 +- docs/_layouts/faq.html | 504 ---------------- docs/_layouts/splash.html | 12 +- docs/css/creative.min.css | 85 ++- quickstart.sh | 9 + 31 files changed, 1071 insertions(+), 1563 deletions(-) delete mode 100644 docs/_data/nav.yml create mode 100644 docs/_docs/calendar.md create mode 100644 docs/_docs/dashboard.md create mode 100644 docs/_docs/host-configuration.md delete mode 100644 docs/_docs/library.md create mode 100644 docs/_docs/listener-stats.md create mode 100644 docs/_docs/playlists.md rename docs/_docs/{analytics.md => playout-history.md} (92%) create mode 100644 docs/_docs/podcasts.md create mode 100644 docs/_docs/reverse-proxy.md create mode 100644 docs/_docs/ssl.md create mode 100644 docs/_docs/webstreams.md delete mode 100644 docs/_includes/toc.html delete mode 100644 docs/_layouts/faq.html create mode 100644 quickstart.sh diff --git a/docs/_config.yml b/docs/_config.yml index ee87b9234..56dc964f4 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -5,13 +5,12 @@ baseurl: "/" # The subpath of your site, e.g. /blog url: "https://libretime.org" # The base hostname & protocol for your site, e.g. http://example.com layouts_dir: _layouts -data_dir: _data includes_dir: _includes favicon: favicon.ico # For splash page layout only -headimage: img/header.jpg +headimage: /img/header.jpg headtext: Open Source Radio Automation headsubtext: Everything you need to get your terrestrial / on-line radio station up and broadcasting like a pro - without breaking the bank! # items below - only for first front page link @@ -22,6 +21,7 @@ headbuttontext: Get LibreTime collections: docs: output: true + permalink: /docs/:name # Build settings plugins: diff --git a/docs/_data/nav.yml b/docs/_data/nav.yml deleted file mode 100644 index 9bc465453..000000000 --- a/docs/_data/nav.yml +++ /dev/null @@ -1,7 +0,0 @@ -navbarlink: -- name: Install - url: /install -- name: Docs - url: /docs -- name: Github - url: https://github.com/LibreTime/libretime \ No newline at end of file diff --git a/docs/_docs/calendar.md b/docs/_docs/calendar.md new file mode 100644 index 000000000..56575c2dd --- /dev/null +++ b/docs/_docs/calendar.md @@ -0,0 +1,57 @@ +--- +layout: article +title: Show Calendar +category: interface +--- + +The Calendar page of the LibreTime administration interface has three views: **day**, **week** and **month**, which can be switched using the grey buttons in the top right corner. By default, the **month** view is shown, with today's date highlighted by a pale grey background. + +![](/img/Screenshot451-Calendar.png) + +In the top left corner of the page, you can go back or forward through the **Calendar** by clicking on the buttons which have a small grey triangle in a white circle. Click the **today** button to jump to today's date in the current view. (The **today** button will be greyed out if you are already viewing that date). In the **day** or **week** views, there is also a drop-down menu which allows you to set the resolution displayed for the calendar, ranging from one minute per row to sixty minutes per row. + +![](/img/Screenshot452-Calendar_resolution.png) + +### Editing a show + +Show configuration and metadata can be changed at any time, except for **Date/Time Start** and **Record from Line In?** options, which are fixed after broadcast of that show commences. Click the show in the Calendar, and select **Edit Show** from the pop-up context menu. This opens the **Update Show** box, which is almost exactly the same as the **Add this Show** box. Click the **+ Update show** button at the top or bottom of the box when you are done. + +![](/img/Screenshot459-Update_show.png) + +Episodes of repeating shows also have an **Instance Description** field in which you can add details for that particular episode. Click the episode in the Calendar, click **Edit** on the pop-up menu, then click **Edit this instance**. After entering an Instance Description, click the **+ Update show** button. + +![](/img/Screenshot583-Show_instance_description_vC9ooiT.png) + +Alternatively, individual shows can be clicked on and dragged to new days and times in the calendar. However, LibreTime will not allow you to drag a future show into the past, or drag and drop instances of a repeated show. In the **Day** and **Week** views, show length can be adjusted by clicking on the lower edge of the show box, and dragging the edge of the box upwards or downwards. The new show length is calculated automatically. + +### Adding content to a show + +To add content to a show, click the show in any view on the Calendar, and select **Schedule Tracks** from the pop-up menu. Shows that do not yet contain any scheduled content are marked with a red exclamation mark icon, to the right of the show start and end times in the top bar. Shows partially filled with content have a yellow exclamation mark icon. During playout of the show, a green play icon will also be shown in the top bar. + +![](/img/Screenshot488-Add_remove_content.png) + +The **Schedule Tracks** action opens a window with the name of the show. Like when using the **Now Playing** page, you can search for content items and add them to the show schedule on the right side of the page. Refer to the *Now Playing* chapter for details. + +When your show has all the required content, click the **OK** button in the bottom right corner to close the window. Back in the **Calendar**, click the show and select **View** from the pop-up menu to view a list of content now included in the show. + +![](/img/Screenshot489-Show_Content.png) + +The **Contents of Show** window is a read-only interface featuring an orange bar which indicates how much media has been added to the show. Click the **OK** button in the bottom right corner, or the white **x** icon in the top right corner, to close the window. + +![](/img/Screenshot353-Contents_of_show.png) + +### Removing content from a show + +To remove an individual item from a show, click on the show in the **Calendar**, and select **Schedule Tracks** from the pop-up menu. In the window which opens, click any item you wish to remove from the show, then click **Delete** on the pop-up menu, or check the box in the item's row then click the **Remove** icon at the top of the table. To remove all files and playlists from a show, click on the show in the **Calendar**, and select **Clear Show** from the pop-up menu.  + +### Deleting an upcoming show + +To delete an upcoming instance of a repeating show, click on the show in the **Calendar**, and select **Delete**, then **Delete Instance** from the pop-up menu. If you wish to delete all future instances of a repeating show, select **Delete Instance and All Following** from the pop-up menu. + +![](/img/Screenshot490-Delete_this_instance.png) + +You cannot delete or remove content from shows that have already played out. These shows have only one option on the pop-up menu, which is **View**. + +### Cancelling playout + +If you wish to cancel playout of a show while it is running, click on the show in the **Calendar** and select **Cancel Show** from the pop-up menu. This action cannot be undone. \ No newline at end of file diff --git a/docs/_docs/dashboard.md b/docs/_docs/dashboard.md new file mode 100644 index 000000000..acb2aa93d --- /dev/null +++ b/docs/_docs/dashboard.md @@ -0,0 +1,121 @@ +--- +layout: article +title: Dashboard +category: interface +--- + +The Dashboard is divided into two sections a Library section divided between +Tracks, Playlists, Smart Blocks, Webstreams, and Podcasts, with the **Scheduled +Shows** dialog on the right. This page provides an overview of the right-hand +interface. Check the links to see information about the other sections which +provide the content that can be scheduled. + +The **Scheduled Shows** page provides a view of the content your station is will +playout, or has already played out, which defaults to showing the 3 hours +ahead. This page also enables you to make last-minute changes to running shows. + +If you've only just installed LibreTime, there might not be any content shown +yet. Click the calendar and clock icons above the table to change the date and +time range, then click the **Find Shows** button (with the magnifying glass +icon) to the right. + +![](/img/now-playing.png) + +To display the content of a particular show, click **Filter by Show** and select +the name of the show from the drop-down menu which will appear. + +![](/img/filter.png) + +On the left side of the page, the **Start** and **End** times, **Duration** +and **Title** of each content item are shown. On the right, **Creator**, +**Album**, **Cue** or **Fade** times and **Mime** type (file format) can also be +shown. This information can help you to prepare voice tracks for insertion into +the show, including time checks if you wish. Putting current time information i +to voice tracks describing specific content can, of course, limit the +re-usability of those voice tracks, unless you always broadcast a particular +item at the same time of day. + +Click the **Show/hide columns** button on the right to configure the metadata +displayed, by checking the boxes in the pop-up window. + +![](/img/columns.png) + +The row for the currently playing item is displayed with a bright green +background. Any underbooked shows (shows with insufficient content to fill the +time allowed) are displayed with a row indicating the length of the underbooking +in minutes and seconds. These rows contain a red exclamation mark in the first +column, and have a pink background. + +![](/img/end-gap.png) + +### Removing content from a running show + +If a show is overbooked, which means the total playout duration is longer than +the time allowed for the show, a brown row indicates that the item will be faded +out when the show ends. A red row indicates that the item will not be played at +all. The length of the overbooking is shown in minutes and seconds in the last +row of the show. To remove the extra items from the end of an overbooked show, +click the **Scissors** button at the top left of the table. + +Alternatively, check the boxes for items which have not yet completed playout, +and click the **Trashcan** button, to the right of the Scissors, to remove them +from the schedule. If you remove the currently playing item, playout will skip +to the next item in the show automatically, so you should make sure you have +enough items remaining in the show to avoid dead air. + +![](/img/delete-extra.png) + +If you have a long schedule displayed, and you wish to skip to the currently +playing item, click the button with the end arrow icon, to the right of the +**Trashcan**. + +To cancel the current show completely, click the red button to the right again. +A pop-up window will ask you to confirm the cancellation, as this action cannot +be undone. + +Items which are no longer available have an exclamation mark icon in the second +column. This may happen for media files which were part of previous shows, and +were removed from LibreTime's library (main storage or watched folders) +subsequently. Items which are included in forthcoming shows cannot be removed +from the Library via the LibreTime interface. + +### Adding content to a running show + +After you have found the items that you want using the search tools, you can +then drag and drop them from the library table on the left side of the page into +the shows on the right side, including the current playing show. + +If the current show has nothing playing out at the time, the new item will begin +playing immediately. This manual triggering of playout can be used as a live +assist technique, in which the LibreTime server's soundcard output is mixed with +other sources such as microphones or telephone hybrids on its way to a +transmitter, or a separate stream encoder. For instance, a live show's host may +not wish to cut off a studio discussion in order to play music at a fixed time. + +![](/img/drag-and-drop.png) + +You can also select multiple items using the **Select** menu button, just +beneath the simple search field, which has the options to **Select this page** +of search results, **Deselect this page** and **Deselect all**. Alternatively, +use the checkboxes on the left side of the library table to select specific +items. Then drag one of the items into the show to add all of the selected +items, or click the **Add to selected show** button, which has a plus icon. If +you wish, you can also use the **Trashcan** button to permanently remove items +from LibreTime's library. Only *admin* users have permission to delete all +items. + +To insert checkbox selected items at a specific time in the show schedule, click +one of the songs in the schedule table. Then click the **Add to selected show** +button in the library table. This will insert the library songs after the +selected scheduled song. + +To add a single item at the insertion point, double-click on it in the library. +There is no need to select or drag the item first. + +Right-clicking on a song in the schedule table will show a pop-up. This pop-up +menu also enables you to audition the entire show in advance of playout, or +remove the item that was clicked on from the show. + +Multiple insertion points can be enabled, so that the same item is inserted into +the schedule at different times. For example, you may wish to play a news report +every hour, or a station ident after every five music files. diff --git a/docs/_docs/freeipa.md b/docs/_docs/freeipa.md index f9f962ced..39bb64bc9 100644 --- a/docs/_docs/freeipa.md +++ b/docs/_docs/freeipa.md @@ -1,7 +1,7 @@ --- layout: article title: FreeIPA Configuration -category: admin +category: install --- You can configure LibreTime to delegate all authentication to a FreeIPA server. diff --git a/docs/_docs/host-configuration.md b/docs/_docs/host-configuration.md new file mode 100644 index 000000000..18b6007e4 --- /dev/null +++ b/docs/_docs/host-configuration.md @@ -0,0 +1,280 @@ +--- +title: Host Configuration +layout: article +category: install +--- + +The streaming host configuration for LibreTime is shown in the file */etc/airtime/liquidsoap.cfg* which is automatically generated by the **Streams** page, found on the **System** menu of the LibreTime administration interface. For this reason, you would not normally edit the streaming configuration manually, as any changes are likely to be overwritten by the administration interface. + +### Database and RabbitMQ hosts {#database} + +Optionally, you may wish to edit the file */etc/airtime/airtime.conf* to set the PostgreSQL database host, and the username and password to connect to the database with: + + sudo nano /etc/airtime/airtime.conf + +You can also set options for RabbitMQ messaging, the LibreTime server and SoundCloud uploads in this file, although you should not normally need to adjust the defaults unless you are running a large LibreTime system distributed across multiple servers. To run the LibreTime server in demo mode, which changes the greeting on the login page and prevents user accounts from being created or modified, set the value of *demo* to 1. + + [database] + host = localhost + dbname = airtime + dbuser = airtime + dbpass = airtime + + [rabbitmq] + host = 127.0.0.1 + port = 5672 + user = airtime + password = XXXXXXXXXXXXXXXXXXXX + vhost = /airtime + + [general] + api_key = XXXXXXXXXXXXXXXXXXXXX + web_server_user = www-data + airtime_dir = /usr/share/airtime + base_url = libretime.example.com + base_port = 80 + base_dir = / + cache_ahead_hours = 1 + + [monit] + monit_user = guest + monit_password = airtime + + [soundcloud] + connection_retries = 3 + time_between_retries = 60 + + [demo] + demo = 0 + +Save and close the file with **Ctrl+O** and **Ctrl+X**. In order to update the configuration +used by the various components of LibreTime, run the following commands + + sudo systemctl restart libretime-liquidsoap + sudo systemctl restart libretime-playout + sudo systemctl restart libretime-celery + sudo systemctl restart libretime-analyzer + +### Changing the default PostgreSQL passwords {#postgre} + +Two of the most important passwords that should be changed *immediately* after installation +are the passwords used by the PostgreSQL database. +It is strongly recommended that you do this before exposing your server to the internet beyond your internal network. + +1. Login to PostgreSQL with `sudo -u postgres psql`. The PostgreSQL shell - `postgres=#` - means that you have logged in successfully. +2. Change the admin password with `ALTER USER postgres PASSWORD 'myPassword';`, where `myPassword` is the new password. +Make sure to include the semicolon at the end! A response of `ALTER ROLE` means that the command ran successfully. +3. Change the password for the *airtime* user with `ALTER USER airtime WITH PASSWORD 'new_password';` +A response of `ALTER ROLE` means that the command ran successfully. +4. If all is successful, logout of PostgreSQL with `\q`, go back to */etc/airtime/airtime.conf* to edit the password +in the config file, and restart all services mentioned in the previous section. + +### Changing the default Icecast passwords {#icecast} + +Random passwords are generated for Icecast during the installation. To look up and change the passwords, look in the file below. + +`/etc/icecast2/icecast.xml` + +Replace the admin and `changeme` field below with your own username and password. + +``` + + + changeme + + changeme + + admin + changeme + +``` + +Then, restart your icecast2 service. + +``` +service icecast2 restart +``` + +> Note: If you change the source password, you may need to manually configure Libretime to use the new password: go to **Settings** > **Streams**, set the streaming server to **Custom** and fill out the **Additional Options** below Stream 1. + +### API client configuration {#api} + +The LibreTime API enables many types of information about the broadcast schedule and configuration to be retrieved from the LibreTime server. Other than the live-info and week-info data fetched by website widgets (see the chapter *Exporting the schedule*), all API requests must be authenticated using the secret API key stored in the file */etc/airtime/api\_client.cfg* on the LibreTime server. This key is autogenerated during LibreTime installation and should be unique for each server. + +If you intend to use the LibreTime API across a public network, for security reasons it is highly recommended that all API requests are sent over encrypted https: and that the web server is configured to accept requests to the api/ directory from specific host names or IP addresses only. + +If you have changed the *base\_url*, *base\_port* or *base\_dir* setting in */etc/airtime/airtime.conf* from the defaults, you will probably also have to update the *Hostname* settings in the file */etc/airtime/api\_client.cfg* accordingly.** + + bin_dir = /usr/lib/airtime/api_clients + api_key = 'XXXXXXXXXXXXXXXXXXXX' + api_base = api + host = libretime.example.com + base_port = 80 + base_dir = / + +### Apache max file size configuration {#apache} + +By default, the maximum upload file size is 500 MB, which may not be large enough for some stations, especially if they are uploading prerecorded shows. The setting for this is located in */etc/apache2/sites-available/airtime.config*. Search for and update the following in megabytes: + +``` +; Maximum allowed size for uploaded files. +upload_max_filesize = 40M + +; Must be greater than or equal to upload_max_filesize +post_max_size = 40M +``` + +For quick reference, 1024 MB = 1 GB and 2048 MB = 2 GB, but most will be okay with rounding to the nearest thousand. After updating the config file, restart Apache by `sudo systemctl apache restart`. + + +### Playout settings {#playout} + +Settings for pypo, the playout engine used by LibreTime, are found in the file */etc/airtime/airtime.conf*. After making changes to this file, you will have to issue the command: + + sudo systemctl restart libretime-playout + +for the changes to take effect. + + ############################################ + # pypo - configuration # + ############################################ + # Set the type of client you are using. + # Currently supported types: + # 1) "obp" = Open Broadcast Platform + # 2) "airtime" + # + api_client = airtime + + ############################################ + # Cache Directories # + # *include* trailing slash !! # + ############################################ + cache_dir = /var/tmp/airtime/pypo/cache/ + file_dir = /var/tmp/airtime/pypo/files/ + tmp_dir = /var/tmp/airtime/pypo/tmp/ + + ############################################ + # Setup Directories # + # Do *not* include trailing slash !! # + ############################################ + cache_base_dir = /var/tmp/airtime/pypo + bin_dir = /usr/lib/airtime/pypo + log_base_dir = /var/log/airtime + pypo_log_dir = /var/log/airtime/pypo + liquidsoap_log_dir = /var/log/airtime/pypo-liquidsoap + + ############################################ + # Liquidsoap settings # + ############################################ + ls_host = 127.0.0.1 + ls_port = 1234 + + ############################################ + # RabbitMQ settings # + ############################################ + rabbitmq_host = localhost + rabbitmq_user = airtime + rabbitmq_password = XXXXXXXXXXXXXXXXXXXX + rabbitmq_vhost = /airtime + + ############################################ + # pypo preferences # + ############################################ + # Poll interval in seconds. + # + # This will rarely need to be changed because any schedule changes are + # automatically sent to pypo immediately. + # + # This is how often the poll script downloads new schedules and files from the + # server in the event that no changes are made to the schedule. + # + poll_interval = 3600# in seconds. + + # Push interval in seconds. + # + # This is how often the push script checks whether it has something new to + # push to liquidsoap. + # + # It's hard to imagine a situation where this should be more than 1 second. + # + push_interval = 1# in seconds + + # 'pre' or 'otf'. 'pre' cues while playlist preparation + # while 'otf' (on the fly) cues while loading into ls + # (needs the post_processor patch) + cue_style = pre + +--- + +## Setting the server time + +Accurate time keeping on your server is vital for LibreTime performance. You can confirm that the date and time of your server are set correctly with the **date** command: + + date + +The server should respond with the date, time, time zone and year in a format similar to the following example: + + Tue Jul 2 15:08:57 BST 2013 + +If the time on your server is wrong, it is recommended that you take LibreTime off-air until the problem is fixed. + +### Configuring NTP + +Although it is possible to set the date and time of the server manually, this is not recommended because the server clock can drift over time, compromising the accuracy of your broadcast schedule. If your LibreTime server is permanently connected to the Internet, you can synchronize your server to a time server with the **ntp** ** program. If **ntp** is not yet installed, you can enter the following command on Debian or Ubuntu: + + sudo apt-get install ntp + +Optionally, open the **ntp** configuration file in the **nano** editor to add further time server names: + + sudo nano /etc/ntp.conf + +On Ubuntu GNU/Linux, the default time server is *ntp.ubuntu.com*, but there are many other time servers available on the public Internet, including the group of servers listed at for each country. Using a variety of NTP servers located closely to your LibreTime server should produce the most accurate results. For example, for a server in the United Kingdom you could use the following list: + + # You do need to talk to an NTP server or two (or three). + server ntp.ubuntu.com + server 0.uk.pool.ntp.org + server 1.uk.pool.ntp.org + server 2.uk.pool.ntp.org + server 3.uk.pool.ntp.org + +Enter the server names you require, press **Ctrl+O** to write out the */etc/ntp.conf* file, then **Ctrl+X** to exit **nano**. Restart the **ntp** service with: + + sudo invoke-rc.d ntp restart + +The server should respond: + + * Stopping NTP server ntpd                                 [ OK ] + * Starting NTP server ntpd                                 [ OK ] + +Then use the **ntpq -p** command to confirm that **ntp** is working. This command should produce output similar to the following: + + ntpq -p + remote      refid    st t when poll reach delay offset jitter + ================================================================== + europium. 193.79.237.14  2 u   28   64    3 39.571  12.600  3.590 + norb.v4.c 46.227.200.72  3 u   28   64    3 47.856  -6.908 10.028 + 82.113.15 193.62.22.82   2 u   29   64    3 11.458  -0.513  2.629 + ntppub.le 158.43.192.66  2 u   91   64    2 122.781  44.864  0.001 + dns0.rmpl 195.66.241.3   2 u   27   64    3  22.171   1.464  4.242 + +### Adjusting the server time zone + +The data centre which hosts your LibreTime server could be located anywhere in the world. Some servers are set to *Coordinated Universal Time* or UTC (similar to *Greenwich Mean Time* or GMT), regardless of their location. LibreTime uses UTC time in its database for scheduling purposes, independent of the server time zone. + +If the server time zone is not appropriate for integration with your station's other systems, on a Debian or Ubuntu server you can reconfigure the **tzdata** (time zone data) package with the command: + + sudo dpkg-reconfigure tzdata + +This command opens a menu in which you can select the continent that you require, by pressing the Enter key. + +![](img/Screenshot15-Configuring_tzdata.png) + +The next step is to select your nearest city, again by pressing the Enter key. The appropriate time zone is selected according to the information that you have entered. + +![](img/Screenshot16-Configure_city.png) + +The console output from the **dpkg-reconfigure tzdata** command will confirm the new setting: + + Current default time zone: 'Europe/London' + Local time is now: Tue Jul 2 15:18:01 BST 2013. + Universal Time is now: Tue Jul 2 14:18:01 UTC 2013. diff --git a/docs/_docs/install.md b/docs/_docs/install.md index 2775593f9..67210482d 100644 --- a/docs/_docs/install.md +++ b/docs/_docs/install.md @@ -1,7 +1,7 @@ --- title: Install layout: article -git: install.md +category: install --- > Note: this guide is assuming you are using Ubuntu 18.04 LTS for installation, which comes with `ufw` and `netplan`, @@ -11,12 +11,10 @@ these are less tested. Firewall and static IP address configuration will need to ## Minimum System Requirements -| On-Premises Install | Cloud Install | -|---------------------|---------------| -| (FM + Internet Radio) | (Internet Radio Only) | -| 1 Ghz Processor| 1vCPU | -| 2 GB RAM | 2 GB RAM | -| Wired ethernet connection, static IP address | 2 TB of data transfer/month | +- Ubuntu 18.04 LTS, Debian 9 and 10, Raspbian 9 and 10 +- 1 Ghz Processor +- 4 GB RAM recommended (2 GB required) +- Wired internet connection and static IP address for on-prem install [DigitalOcean](https://www.digitalocean.com/pricing/#Compute) and [Linode](https://www.linode.com/pricing/#row--compute) have similar plans that meet Cloud Install requirements. Both plans cost $10/month. @@ -65,7 +63,7 @@ sudo ufw enable 8001/tcp sudo ufw enable 8002/tcp ``` -> If needed, instuctions for setting up a reverse proxy can be found [here](quickstart#reverse-proxy). +> If needed, instuctions for setting up a reverse proxy can be found [here](/docs/reverse-proxy). ### Installing LibreTime @@ -110,538 +108,4 @@ the `www-data` user needs to be added to the `audio` user group using the comman ``` sudo adduser www-data audio -``` - ---- - -## Reverse Proxy {#reverse-proxy} - -In some deployments, the LibreTime server is deployed behind a reverse proxy, -for example in containerization use-cases such as Docker and LXC. LibreTime -makes extensive use of its API for some site functionality, which causes -[Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) -to occur. By default, CORS requests are blocked by your browser and the origins -need to be added to the **Allowed CORS URLs** block in -[**General Settings**](settings). These origins should include any -domains that will be used externally to connect to your reverse proxy that you -want handled by LibreTime. These URLS can also be set during the first run configuration -that is displayed when you first install LibreTime - -### Reverse Proxy Basics - -A reverse proxy allows the LibreTime server to not be connected to the open internet. In -this configuration, it is rather behind another server that proxies traffic to it from -users. This provides some advantages in the containerization space, as this means that -the containers can be on their own internal network, protected from outside access. - -A reverse proxy also allows SSL to be terminated in a single location for multiple sites. -This means that all your traffic to the proxy from clients is encrypted, but the reverse -proxy's traffic to the containers on the internal network is not. All the SSL certificates -live on the reverse proxy and can be renewed there instead of on the individual -containers. - -### Setup - -There are known bugs when using LibreTime behind a reverse proxy ([#957](https://github.com/LibreTime/libretime/issues/957) -tracks the issue and contains a temporary workaround). For SSL redirection to work, you -need two domains: one for LibreTime and one for Icecast. Here, these will be -`libretime.example.com` and `icecast.example.com`. - -You will also require two VMs, servers or containers. Alternatively the reverse proxy can -be located on the server, proxying connections to containers also on the host. Setting up -a containerization environment is beyond the scope of this guide. It assumes that you have -Nginx set up on `proxy` and LibreTime will be installed on `libretime`. You will need root -access on both. `libretime` also needs to be able to be accessed from `proxy` -(`ping libretime` on `proxy`). - -On `libretime`, install LibreTime as described in the [install guide](quickstart). In short -this means run the following commands: - -``` -git clone https://github.com/LibreTime/libretime.git -cd libretime -sudo ./install -fiap -``` - -Once it has installed, replace `localhost` in -`/etc/icecast2/icecast.xml` with the following: - -``` -icecast.example.com -``` - -This is the hostname that people listening to your stream will connect to and what -LibreTime will use to stream out to them. You will then need to restart Icecast: - -``` -sudo systemctl restart icecast2 -``` - -On `proxy`, run the following: - -``` -cat << EOF | sudo tee /etc/nginx/sites-available/libretime.conf -server { - listen 80; - server_name libretime.example.com; - location / { - rewrite ^ https://$server_name$request_uri? permanent; - } -} -server { - listen 443 ssl; - server_name libretime.example.com; - ssl_certificate /etc/letsencrypt/live/libretime.example.com/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/libretime.example.com/privkey.pem; - add_header Strict-Transport-Security "max-age=15552000;"; - add_header X-Frame-Options "SAMEORIGIN"; - client_max_body_size 512M; - location / { - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_pass http://libretime/; - } -} -EOF -``` - -This Nginx configuration ensures that all traffic uses SSL to the reverse proxy, and -traffic is proxied to `libretime`. - -Next, the SSL certificate needs to be generated and the site activated. - -``` -sudo apt install certbot -sudo systemctl stop nginx -sudo certbot certonly -d libretime.example.com -a standalone -sudo systemctl start nginx -``` - -You can now go to [https://libretime.example.com](https://libretime.example.com) and go -through the installer. On `General Settings`, you need to change the Webserver Port to -`443` and add the following CORS URLs: - -``` -https://libretime.example.com -http://libretime.example.com -https://localhost -http://localhost -``` - ---- - -## Host Configuration - -The streaming host configuration for LibreTime is shown in the file */etc/airtime/liquidsoap.cfg* which is automatically generated by the **Streams** page, found on the **System** menu of the LibreTime administration interface. For this reason, you would not normally edit the streaming configuration manually, as any changes are likely to be overwritten by the administration interface. - -### Database and RabbitMQ hosts {#database} - -Optionally, you may wish to edit the file */etc/airtime/airtime.conf* to set the PostgreSQL database host, and the username and password to connect to the database with: - - sudo nano /etc/airtime/airtime.conf - -You can also set options for RabbitMQ messaging, the LibreTime server and SoundCloud uploads in this file, although you should not normally need to adjust the defaults unless you are running a large LibreTime system distributed across multiple servers. To run the LibreTime server in demo mode, which changes the greeting on the login page and prevents user accounts from being created or modified, set the value of *demo* to 1. - - [database] - host = localhost - dbname = airtime - dbuser = airtime - dbpass = airtime - - [rabbitmq] - host = 127.0.0.1 - port = 5672 - user = airtime - password = XXXXXXXXXXXXXXXXXXXX - vhost = /airtime - - [general] - api_key = XXXXXXXXXXXXXXXXXXXXX - web_server_user = www-data - airtime_dir = /usr/share/airtime - base_url = libretime.example.com - base_port = 80 - base_dir = / - cache_ahead_hours = 1 - - [monit] - monit_user = guest - monit_password = airtime - - [soundcloud] - connection_retries = 3 - time_between_retries = 60 - - [demo] - demo = 0 - -Save and close the file with **Ctrl+O** and **Ctrl+X**. In order to update the configuration -used by the various components of LibreTime, run the following commands - - sudo systemctl restart libretime-liquidsoap - sudo systemctl restart libretime-playout - sudo systemctl restart libretime-celery - sudo systemctl restart libretime-analyzer - -### Changing the default PostgreSQL passwords {#postgre} - -Two of the most important passwords that should be changed *immediately* after installation -are the passwords used by the PostgreSQL database. -It is strongly recommended that you do this before exposing your server to the internet beyond your internal network. - -1. Login to PostgreSQL with `sudo -u postgres psql`. The PostgreSQL shell - `postgres=#` - means that you have logged in successfully. -2. Change the admin password with `ALTER USER postgres PASSWORD 'myPassword';`, where `myPassword` is the new password. -Make sure to include the semicolon at the end! A response of `ALTER ROLE` means that the command ran successfully. -3. Change the password for the *airtime* user with `ALTER USER airtime WITH PASSWORD 'new_password';` -A response of `ALTER ROLE` means that the command ran successfully. -4. If all is successful, logout of PostgreSQL with `\q`, go back to */etc/airtime/airtime.conf* to edit the password -in the config file, and restart all services mentioned in the previous section. - -### Changing the default Icecast passwords {#icecast} - -Random passwords are generated for Icecast during the installation. To look up and change the passwords, look in the file below. - -`/etc/icecast2/icecast.xml` - -Replace the admin and `changeme` field below with your own username and password. - -``` - - - changeme - - changeme - - admin - changeme - -``` - -Then, restart your icecast2 service. - -``` -service icecast2 restart -``` - -> Note: If you change the source password, you may need to manually configure Libretime to use the new password: go to **Settings** > **Streams**, set the streaming server to **Custom** and fill out the **Additional Options** below Stream 1. - -### API client configuration {#api} - -The LibreTime API enables many types of information about the broadcast schedule and configuration to be retrieved from the LibreTime server. Other than the live-info and week-info data fetched by website widgets (see the chapter *Exporting the schedule*), all API requests must be authenticated using the secret API key stored in the file */etc/airtime/api\_client.cfg* on the LibreTime server. This key is autogenerated during LibreTime installation and should be unique for each server. - -If you intend to use the LibreTime API across a public network, for security reasons it is highly recommended that all API requests are sent over encrypted https: and that the web server is configured to accept requests to the api/ directory from specific host names or IP addresses only. - -If you have changed the *base\_url*, *base\_port* or *base\_dir* setting in */etc/airtime/airtime.conf* from the defaults, you will probably also have to update the *Hostname* settings in the file */etc/airtime/api\_client.cfg* accordingly.** - - bin_dir = /usr/lib/airtime/api_clients - api_key = 'XXXXXXXXXXXXXXXXXXXX' - api_base = api - host = libretime.example.com - base_port = 80 - base_dir = / - -### Apache max file size configuration {#apache} - -By default, the maximum upload file size is 500 MB, which may not be large enough for some stations, especially if they are uploading prerecorded shows. The setting for this is located in */etc/apache2/sites-available/airtime.config*. Search for and update the following in megabytes: - -``` -; Maximum allowed size for uploaded files. -upload_max_filesize = 40M - -; Must be greater than or equal to upload_max_filesize -post_max_size = 40M -``` - -For quick reference, 1024 MB = 1 GB and 2048 MB = 2 GB, but most will be okay with rounding to the nearest thousand. After updating the config file, restart Apache by `sudo systemctl apache restart`. - - -### Playout settings {#playout} - -Settings for pypo, the playout engine used by LibreTime, are found in the file */etc/airtime/airtime.conf*. After making changes to this file, you will have to issue the command: - - sudo systemctl restart libretime-playout - -for the changes to take effect. - - ############################################ - # pypo - configuration # - ############################################ - # Set the type of client you are using. - # Currently supported types: - # 1) "obp" = Open Broadcast Platform - # 2) "airtime" - # - api_client = airtime - - ############################################ - # Cache Directories # - # *include* trailing slash !! # - ############################################ - cache_dir = /var/tmp/airtime/pypo/cache/ - file_dir = /var/tmp/airtime/pypo/files/ - tmp_dir = /var/tmp/airtime/pypo/tmp/ - - ############################################ - # Setup Directories # - # Do *not* include trailing slash !! # - ############################################ - cache_base_dir = /var/tmp/airtime/pypo - bin_dir = /usr/lib/airtime/pypo - log_base_dir = /var/log/airtime - pypo_log_dir = /var/log/airtime/pypo - liquidsoap_log_dir = /var/log/airtime/pypo-liquidsoap - - ############################################ - # Liquidsoap settings # - ############################################ - ls_host = 127.0.0.1 - ls_port = 1234 - - ############################################ - # RabbitMQ settings # - ############################################ - rabbitmq_host = localhost - rabbitmq_user = airtime - rabbitmq_password = XXXXXXXXXXXXXXXXXXXX - rabbitmq_vhost = /airtime - - ############################################ - # pypo preferences # - ############################################ - # Poll interval in seconds. - # - # This will rarely need to be changed because any schedule changes are - # automatically sent to pypo immediately. - # - # This is how often the poll script downloads new schedules and files from the - # server in the event that no changes are made to the schedule. - # - poll_interval = 3600# in seconds. - - # Push interval in seconds. - # - # This is how often the push script checks whether it has something new to - # push to liquidsoap. - # - # It's hard to imagine a situation where this should be more than 1 second. - # - push_interval = 1# in seconds - - # 'pre' or 'otf'. 'pre' cues while playlist preparation - # while 'otf' (on the fly) cues while loading into ls - # (needs the post_processor patch) - cue_style = pre - ---- - -## Setting the server time - -Accurate time keeping on your server is vital for LibreTime performance. You can confirm that the date and time of your server are set correctly with the **date** command: - - date - -The server should respond with the date, time, time zone and year in a format similar to the following example: - - Tue Jul 2 15:08:57 BST 2013 - -If the time on your server is wrong, it is recommended that you take LibreTime off-air until the problem is fixed. - -### Configuring NTP - -Although it is possible to set the date and time of the server manually, this is not recommended because the server clock can drift over time, compromising the accuracy of your broadcast schedule. If your LibreTime server is permanently connected to the Internet, you can synchronize your server to a time server with the **ntp** ** program. If **ntp** is not yet installed, you can enter the following command on Debian or Ubuntu: - - sudo apt-get install ntp - -Optionally, open the **ntp** configuration file in the **nano** editor to add further time server names: - - sudo nano /etc/ntp.conf - -On Ubuntu GNU/Linux, the default time server is *ntp.ubuntu.com*, but there are many other time servers available on the public Internet, including the group of servers listed at for each country. Using a variety of NTP servers located closely to your LibreTime server should produce the most accurate results. For example, for a server in the United Kingdom you could use the following list: - - # You do need to talk to an NTP server or two (or three). - server ntp.ubuntu.com - server 0.uk.pool.ntp.org - server 1.uk.pool.ntp.org - server 2.uk.pool.ntp.org - server 3.uk.pool.ntp.org - -Enter the server names you require, press **Ctrl+O** to write out the */etc/ntp.conf* file, then **Ctrl+X** to exit **nano**. Restart the **ntp** service with: - - sudo invoke-rc.d ntp restart - -The server should respond: - - * Stopping NTP server ntpd                                 [ OK ] - * Starting NTP server ntpd                                 [ OK ] - -Then use the **ntpq -p** command to confirm that **ntp** is working. This command should produce output similar to the following: - - ntpq -p - remote      refid    st t when poll reach delay offset jitter - ================================================================== - europium. 193.79.237.14  2 u   28   64    3 39.571  12.600  3.590 - norb.v4.c 46.227.200.72  3 u   28   64    3 47.856  -6.908 10.028 - 82.113.15 193.62.22.82   2 u   29   64    3 11.458  -0.513  2.629 - ntppub.le 158.43.192.66  2 u   91   64    2 122.781  44.864  0.001 - dns0.rmpl 195.66.241.3   2 u   27   64    3  22.171   1.464  4.242 - -### Adjusting the server time zone - -The data centre which hosts your LibreTime server could be located anywhere in the world. Some servers are set to *Coordinated Universal Time* or UTC (similar to *Greenwich Mean Time* or GMT), regardless of their location. LibreTime uses UTC time in its database for scheduling purposes, independent of the server time zone. - -If the server time zone is not appropriate for integration with your station's other systems, on a Debian or Ubuntu server you can reconfigure the **tzdata** (time zone data) package with the command: - - sudo dpkg-reconfigure tzdata - -This command opens a menu in which you can select the continent that you require, by pressing the Enter key. - -![](img/Screenshot15-Configuring_tzdata.png) - -The next step is to select your nearest city, again by pressing the Enter key. The appropriate time zone is selected according to the information that you have entered. - -![](img/Screenshot16-Configure_city.png) - -The console output from the **dpkg-reconfigure tzdata** command will confirm the new setting: - - Current default time zone: 'Europe/London' - Local time is now: Tue Jul 2 15:18:01 BST 2013. - Universal Time is now: Tue Jul 2 14:18:01 UTC 2013. - ---- - -## SSL Configuration - -To increase the security of your server, you can enable encrypted access to the LibreTime administration interface, and direct your users towards this more secure login page. The main advantage of using this encryption is that your remote users' login names and passwords are not sent in plain text across the public Internet or untrusted local networks, such as shared Wi-Fi access points. - -### Deploying a certificate with Certbot - -One of the fastest, easiest, and cheapest ways to get an SSL certificate is through [Certbot](https://certbot.eff.org/), as created by the -[Electronic Frontier Foundation](https://www.eff.org/). There are some requirements for this process: -- you have an HTTP website (already installed and configured by default by the LibreTime installer) and -- this website is open to the public internet (likely via. port forwarding if your computer is behind a firewall) and -- the server is accessible to the public via. port 80 - -If you aren't able to verify all three requirements, you may want to try a self-signed certificate (see next section). - -These instructions come from Certbot's website and assume that you are using an Apache web server -running on Ubuntu 18.04 LTS (the Apache web server is installed with LibreTime by default). -Instructions for other Debian-based OSes are similar, but check with Certbot for clarification. - -Note: all instructions require you to have sudo priveledges - -First, add Certbot's PPA using: - -``` - sudo apt-get update - sudo apt-get install software-properties-common - sudo add-apt-repository universe - sudo add-apt-repository ppa:certbot/certbot - sudo apt-get update -``` - -Next, install Certbot and install the SSL certificate using the below commands: - -``` -sudo apt-get install certbot python3-certbot-apache -sudo certbot --apache # get and install the certificate -sudo certbot certonly --apache # to only get the certificate, not install it using Certbot -``` - -You can test certificate renewal by running `sudo certbot renew --dry-run`. -Head to your server's IP address to check to see that the installation worked. - -### Deploying a self-signed certificate - -The Debian/Ubuntu package *ssl-cert* creates a *snakeoil* certificate and key based on your server's hostname. This gratis certificate and key pair created under the */etc/ssl/certs*/ and */etc/ssl/private/* directories will not be recognised by users' browsers without manual intervention. You can install the *ssl-cert* package with the command: - - sudo apt-get install ssl-cert - -If the hostname of your server does not match the domain name you intend to use with the LibreTime virtual host, the user's browser will present an additional security warning. You can set the domain name of the certificate by editing the file */usr/share/ssl-cert/ssleay.cnf* to replace the *@HostName@* variable: - - commonName = @HostName@ - -with the domain name used by LibreTime: - - commonName = airtime.example.com - -Then save the file and regenerate the certificate with the command: - - sudo make-ssl-cert generate-default-snakeoil --force-overwrite - -You should enable additional Apache modules for page redirections, custom headers and secure access: - - sudo a2enmod alias headers ssl - -Next, edit the virtual host configuration for your LibreTime server to include a stanza for the https:// interface on port 443 and a redirect for logins from port 80: - - sudo nano /etc/apache2/sites-available/airtime-vhost.conf - -Using the following configuration for Apache 2.2 as a guide, replace *airtime.example.com* with the name of your server and *admin@example.com* with your email address. The older SSLv2 and SSLv3 protocols and SSL compression should be disabled, as they are generally believed to be insecure. You may wish to create a *ServerAlias* for users to access the administration interface over https:// if required. - -On port 80, Apache's *alias* module is used to set a *Redirect permanent* for the login page. Optionally, access could be denied to all sites except *localhost* and any other LibreTime servers on your network, so that unencrypted communication between LibreTime components can continue. - -``` - - SSLEngine on - SSLProtocol All -SSLv2 -SSLv3 - SSLCompression off - SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem - SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key - Header always set Strict-Transport-Security "max-age=31536000" - - ServerName airtime.example.com - #ServerAlias www.example.com - - ServerAdmin admin@example.com - - DocumentRoot /usr/share/airtime/php/airtime_mvc/public - DirectoryIndex index.php - - - Options -Indexes FollowSymLinks MultiViews - AllowOverride all - Order allow,deny - Allow from all - - - - - ServerName airtime.example.com - - ServerAdmin admin@example.com - - DocumentRoot /usr/share/airtime/php/airtime_mvc/public - Redirect permanent /login https://airtime.example.com/login - - SetEnv APPLICATION_ENV "production" - - - Options -Indexes FollowSymLinks MultiViews - AllowOverride All - Order allow,deny - Allow from all - - -``` - -Save the file with **Ctrl+O** and exit the **nano** editor with **Ctrl+X**. Then restart Apache with the command: - - sudo service apache restart - -When attempting to log into your server via http:// in future, you should be redirected to https:// automatically. - -### Importing a self-signed certificate into the browser - -The first time you access an LibreTime server with a self-signed certificate over https:// your browser will block the login page and display a security warning. In **Mozilla Firefox**, you can click **Technical Details** to confirm that the warning is due to the certificate being self-signed before clicking the **Add Exception** button. In **Google Chrome**, the button to click on the security warning page is **Proceed Anyway**. - -![](img/Screenshot547-connection_untrusted.png) - -On the next page in Firefox, click the **Get Certificate** button to inspect the details of the self-signed certificate. If all is well, click the **Confirm Security Exception** button. You should now be able to proceed to the https:// login page.   - -![](img/Screenshot548-confirm_exception.png) - -If the users of your LibreTime server wish to avoid going through these steps, or they do not trust the remote LibreTime server to be what it claims to be, it is also possible to import a trusted local copy of a certificate file into the browser. For example, in Firefox version 30 preferences, you can go into the **Advanced** section, click the **Certificates** tab, then click the **View Certificates** button. On the **Servers** tab of the **Certificate Manager**, there is an **Import** button which enables you to load a certificate file from the local computer. - -### Mixed encrypted and unencrypted content - -Whether your certificate is self-signed or not, you will see browser security warnings whenever a https:// page is delivering unencrypted content, such as the stream from an Icecast server. In Firefox, an exclamation mark icon is displayed in the address bar of the **Listen** pop-up. +``` \ No newline at end of file diff --git a/docs/_docs/library.md b/docs/_docs/library.md deleted file mode 100644 index 221082243..000000000 --- a/docs/_docs/library.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -layout: article -title: Library -git: library.md -category: dj ---- - -## Dashboard {#dashboard} - -The Dashboard is divided into two sections a Library section divided between -Tracks, Playlists, Smart Blocks, Webstreams, and Podcasts, with the **Scheduled -Shows** dialog on the right. This page provides an overview of the right-hand -interface. Check the links to see information about the other sections which -provide the content that can be scheduled. - -The **Scheduled Shows** page provides a view of the content your station is will -playout, or has already played out, which defaults to showing the 3 hours -ahead. This page also enables you to make last-minute changes to running shows. - -If you've only just installed LibreTime, there might not be any content shown -yet. Click the calendar and clock icons above the table to change the date and -time range, then click the **Find Shows** button (with the magnifying glass -icon) to the right. - -![](img/now-playing.png) - -To display the content of a particular show, click **Filter by Show** and select -the name of the show from the drop-down menu which will appear. - -![](img/filter.png) - -On the left side of the page, the **Start** and **End** times, **Duration** -and **Title** of each content item are shown. On the right, **Creator**, -**Album**, **Cue** or **Fade** times and **Mime** type (file format) can also be -shown. This information can help you to prepare voice tracks for insertion into -the show, including time checks if you wish. Putting current time information i -to voice tracks describing specific content can, of course, limit the -re-usability of those voice tracks, unless you always broadcast a particular -item at the same time of day. - -Click the **Show/hide columns** button on the right to configure the metadata -displayed, by checking the boxes in the pop-up window. - -![](img/columns.png) - -The row for the currently playing item is displayed with a bright green -background. Any underbooked shows (shows with insufficient content to fill the -time allowed) are displayed with a row indicating the length of the underbooking -in minutes and seconds. These rows contain a red exclamation mark in the first -column, and have a pink background. - -![](img/end-gap.png) - -### Removing content from a running show - -If a show is overbooked, which means the total playout duration is longer than -the time allowed for the show, a brown row indicates that the item will be faded -out when the show ends. A red row indicates that the item will not be played at -all. The length of the overbooking is shown in minutes and seconds in the last -row of the show. To remove the extra items from the end of an overbooked show, -click the **Scissors** button at the top left of the table. - -Alternatively, check the boxes for items which have not yet completed playout, -and click the **Trashcan** button, to the right of the Scissors, to remove them -from the schedule. If you remove the currently playing item, playout will skip -to the next item in the show automatically, so you should make sure you have -enough items remaining in the show to avoid dead air. - -![](img/delete-extra.png) - -If you have a long schedule displayed, and you wish to skip to the currently -playing item, click the button with the end arrow icon, to the right of the -**Trashcan**. - -To cancel the current show completely, click the red button to the right again. -A pop-up window will ask you to confirm the cancellation, as this action cannot -be undone. - -Items which are no longer available have an exclamation mark icon in the second -column. This may happen for media files which were part of previous shows, and -were removed from LibreTime's library (main storage or watched folders) -subsequently. Items which are included in forthcoming shows cannot be removed -from the Library via the LibreTime interface. - -### Adding content to a running show - -After you have found the items that you want using the search tools, you can -then drag and drop them from the library table on the left side of the page into -the shows on the right side, including the current playing show. - -If the current show has nothing playing out at the time, the new item will begin -playing immediately. This manual triggering of playout can be used as a live -assist technique, in which the LibreTime server's soundcard output is mixed with -other sources such as microphones or telephone hybrids on its way to a -transmitter, or a separate stream encoder. For instance, a live show's host may -not wish to cut off a studio discussion in order to play music at a fixed time. - -![](img/drag-and-drop.png) - -You can also select multiple items using the **Select** menu button, just -beneath the simple search field, which has the options to **Select this page** -of search results, **Deselect this page** and **Deselect all**. Alternatively, -use the checkboxes on the left side of the library table to select specific -items. Then drag one of the items into the show to add all of the selected -items, or click the **Add to selected show** button, which has a plus icon. If -you wish, you can also use the **Trashcan** button to permanently remove items -from LibreTime's library. Only *admin* users have permission to delete all -items. - -To insert checkbox selected items at a specific time in the show schedule, click -one of the songs in the schedule table. Then click the **Add to selected show** -button in the library table. This will insert the library songs after the -selected scheduled song. - -To add a single item at the insertion point, double-click on it in the library. -There is no need to select or drag the item first. - -Right-clicking on a song in the schedule table will show a pop-up. This pop-up -menu also enables you to audition the entire show in advance of playout, or -remove the item that was clicked on from the show. - -Multiple insertion points can be enabled, so that the same item is inserted into -the schedule at different times. For example, you may wish to play a news report -every hour, or a station ident after every five music files. - ---- - -## Playlists {#playlists} - -### Creating a new playlist - -You can create a new playlist on the toolbar of the **Playlists** page. - -![](img/Playlist-Editor.png) - -Enter a **Name** and **Description** for the playlist, then click the **Save** button. Setting good quality metadata here will help you find the playlist using the search box later, so you should be as descriptive as possible. - -### Adding content to a playlist - -With a playlist open, drag and drop items from the search results on the left into the playlist on the right. Jingles and voice tracks can be added before, after or between music items. - -After adding files to the playlist, the total playlist time is displayed in the top right corner. The duration of an individual file is shown in each row of the playlist in a white font, and beneath this figure the time since the beginning of the playlist is displayed in a smaller light grey font. This elapsed time figure can be used as a time check for voice tracks, although this option may limit the re-usability of the voice track. - -To audition a playlist file in your web browser, click the white triangle button on the left side of its row. (If the format of the file is not supported by your browser, the triangle in this button will be greyed out). If audition of the file format is supported, a pop-up window will open, with the playlist starting at the file you clicked. - -Click the small white **x** icon on the right hand side of each row to remove a file from the playlist. You can also drag and drop files to re-order them, or click the **Shuffle** button to re-order files automatically. - -When your playlist is complete, click the **New** button in the top left corner to create another playlist, click the close icon (a white cross in a black circle) in the top right corner, or browse to another page of the LibreTime interface. - -If you want to edit the playlist content or metadata later, you can find it by **Title**, **Creator**, **Last Modified** date, **Length**, **Owner** or **Year** using one of the search tools on the Library page. Click the playlist in the search results list, and then click **Edit** from the pop-up menu. You can also **Preview** the entire playlist in a pop-up audition window, **Duplicate** or **Delete** one of your playlists from this menu. - -## Smartblocks {#smartblocks} - -### Creating a Smartblock - -![](img/Smartblock-options.png) - -Smart blocks are automatically filled with media files from the LibreTime library, according to the criteria that you specify. This feature is intended to save staff time, compared to selecting items for a playlist manually, and can be used to schedule shows that operate in a consistent format. - -To create a smart block, click the **Smartblocks** button on the left sidebar, and select **New** from the toolbar. Like a playlist, smart blocks can have a title and description, which you can edit. This helps you find relevant smart blocks in searches. - -Fill out the smart block's **Name**, **Search Criteria**, and **Limit to** sections. The search criteria can be any one of LibreTime's metadata categories, such as **Title**, **Creator** or **Genre**. The modifier depends on whether the metadata in question contains letters or numbers. For example, **Title** has modifiers including *contains* and *starts with*, whereas the modifiers for **BPM** include *is greater than* and *is in the range*. - -If you have a large number of files which meet the criteria that you specify, you may wish to limit the duration of the smart block using the **Limit to** field, so that it fits within the show you have in mind. Select **hours**, **minutes** or **items** from the drop-down menu, and click the **Generate** button again, if it is a static smart block. Then click the **Save** button. - -> Note: Smart Blocks by default will never overflow the Time Limit. For instance if you set a time limit of 1 hour. It will add tracks to the schedule until it can't add any more tracks without exceeding the hour. This is to prevent tracks from being cut-off because they exceed the time limit of a show. -> -> If you want a smartblock to schedule tracks until it is longer than the Time Limit you can check "Allow last track to exceed time limit". This will make LibreTime add tracks that meet the criteria until it equals or is longer than the time limit. This is helpful for avoiding dead air on shows that are being autoscheduled. - -![](img/Smartblock-advanced.png) - -You can also set the **smart block type**. A **Static** smart block will save the criteria and generate the block content immediately. This enables you to edit the contents of the block in the **Library** page before adding it to a show. A **Dynamic** smart block will only save the criteria, and the specific content will be generated at the time the block is added to a show. After that, the content of the show can be changed or re-ordered in the **Now Playing** page.  - -Click the **plus button** on the left to add OR criteria, such as **Creator** containing *beck* OR *jimi*. To add AND criteria, such as **Creator** containing *jimi* AND BPM in the range *120* to *130*, click the **plus button** on the right. (The criteria are not case sensitive). Click **Preview** to see the results. - -> If you see the message *0 files meet the criteria*, it might mean that the files in the Library have not been tagged with the correct metadata. See the chapter [Preparing media](preparing-media) for tips on tagging content. - -![](img/Smartblock-content.png) - -If you don't like the ordering which is generated, click the **Shuffle** button, or drag and drop the smart block contents into the order that you prefer. You can also remove items or add new items manually from the Library. Changes to static smart block contents are saved automatically when you add items, remove or re-order them, or click the **Generate** button. Click the **Save** button in the upper right corner to save any changes to smart block criteria. - -By default, a smart block will not contain repeated items, which will limit the duration of the block if you do not have sufficient items meeting the specified criteria in your **Library**. To override the default behaviour, check the **Allow Repeat Tracks** box. The **Sort tracks by** menu offers the options of **random**, **newest** or **oldest** items first. - -Smart blocks can be added to shows in the same way as a manually created playlist is added. Smart blocks can also be added to one or more playlists. In the case of a playlist containing a static smart block, click **Expand Static Block** to view the contents. For a dynamic smart block, you can review the criteria and duration limit by clicking **Expand Dynamic Block**. - -Once created, smart blocks can be found under the Smartblocks tab and refined at any time. They can be re-opened by right-clicking on the smart block and selecting **Edit** from the pop-up menu. - ---- - -## Podcasts {#podcasts} - -The Podcasts page allows you add subscriptions to podcasts which are often used to syndicated audio files using a URL called a RSS feed. This allows your LibreTime instance to automatically download new shows from the web. - -In order to add a podcast you need to get the RSS feed. All podcasts available on iTunes have a RSS feed but it is sometimes hidden. See this issue on our github page [#510](https://github.com/LibreTime/libretime/issues/510) for more information. RSS feeds that do not end in *.xml* may be accepted by LibreTime but might fail to download episodes; in that case, download the episode using a podcast client such as [gpodder](https://gpodder.github.io/) and then manually upload and schedule the episode. Podcast feeds coming from Anchor.fm have been known to have this issue. - -The podcast interfaces provides you with the ability to generate [Smartblocks](library#smartblocks) that can be used in conjunction with autoloading playlists to schedule the newest episode of a podcast without human intervention. - - - - - -
    -### Podcasts Dashboard - -![](img/Podcasts_Dashboard.png) - -The podcasts dashboard is similar to the tracks view, allowing you to add, edit, and remove -podcasts by the toolbar, in addition to sorting by columns. - -To add a podcast, click on the **+ Add** button on the toolbar and provide the podcast's RSS feed, which usually ends in *.xml*. -Once the podcast's feed is recognized, the editor pane opens for the podcast. - -### Editor - -![](img/Podcasts_Editor.png) - -In the podcasts editor, you can rename the podcast, update settings for the podcast, and manage episodes. -A search box is available to search for episodes within the feed. - -- To import an episode directly into LibreTime, double-click on an episode or select it and click **+ Import**. The podcast will appear under tracks with the Podcast Name as the Album. -- To delete an episode from LibreTime, select the episode and click on the red trash can on the toolbar. -- If you would like LibreTime to automatically download the latest episodes of a podcast, make sure *Download latest episodes* is checked. This can be used in conjunction with Smartblocks and Playlists to automate downloading and scheduling shows that are received via podcast feed. - -## Webstreams {#webstreams} - - - - - -### Adding a webstream -A web stream URL and metadata can be added to the LibreTime library, so that a remote stream can be searched for and scheduled to be *pulled* into a show. For example, at the top of the hour your station may pull a news report from journalists working in another studio. This is a different concept from **Master Source** and **Show Source** remote streams which are *pushed* into the LibreTime playout schedule. - -To add a web stream, click the **+ New** button on the left side of the Webstreams page. Like a playlist, web streams in the Library can have a title and **Description**, which may help you find them in searches later. - -![](img/webstream.jpg) - -The **Stream URL** setting must include the *port number* (such as 8000) and *mount point* (such as remote\_stream) of the remote stream, in addition to the streaming server name. A **Default Length** for the remote stream can also be set. If the stream is added at the end of a show which becomes overbooked as a result, it will be faded out when the show ends. - -Note: LibreTime checks the remote webstream's status upon editing stream settings, so an offline stream will result in an error. There are many tools such as [BUTT](https://danielnoethen.de/butt/) and [MIXXX](https://www.mixxx.org) that can be used to send a test stream to LibreTime can save it; read more [here](../live-shows-with-mixxx/index.md). diff --git a/docs/_docs/listener-stats.md b/docs/_docs/listener-stats.md new file mode 100644 index 000000000..31ae21d6d --- /dev/null +++ b/docs/_docs/listener-stats.md @@ -0,0 +1,15 @@ +--- +title: Listener Statistics +layout: article +category: manager +--- + +The Listener Stats page on the Analytics menu shows graphs of listener connections to the configured streaming servers for the selected date and time range.  On the right side, a green **Status** indicator shows **OK** if the connection to the streaming server is active. + +![](img/portfolio/stream-stats.jpg) + +If the status indicator is red, check that the **Admin User** and **Admin Password** settings are correct under **Additional Options** for the named mount point, such as *libretime\_128*, on the **Streams** page of the **Settings** menu. + +By default, statistics for the last 24 hours of streaming are shown. To change this date and time range, click the calendar and clock icons in the lower left corner of the page, then click the magnifying glass icon. + +> To choose which particular streams should have statistics displayed, click the check boxes for the individual colour-coded mount points, just below the graph. diff --git a/docs/_docs/microsite.md b/docs/_docs/microsite.md index 7c3badc62..1cc6035d7 100644 --- a/docs/_docs/microsite.md +++ b/docs/_docs/microsite.md @@ -1,13 +1,10 @@ --- layout: article title: Built-in Microsite -git: microsite.md category: admin --- -## Overview - -![](img/radio-page.png) +![](/img/radio-page.png) LibreTime includes a mini-site, which can be accessed at _serverIP_ (for local installations), or _libretime.yourdomain.com_ or _cloudvmIP_ (for installations to a server on a domain). The site includes your diff --git a/docs/_docs/playlists.md b/docs/_docs/playlists.md new file mode 100644 index 000000000..62e3e8680 --- /dev/null +++ b/docs/_docs/playlists.md @@ -0,0 +1,61 @@ +--- +layout: article +title: Playlists and Smartblocks +category: interface +--- + +### Creating a new playlist + +You can create a new playlist on the toolbar of the **Playlists** page. + +![](img/Playlist-Editor.png) + +Enter a **Name** and **Description** for the playlist, then click the **Save** button. Setting good quality metadata here will help you find the playlist using the search box later, so you should be as descriptive as possible. + +### Adding content to a playlist + +With a playlist open, drag and drop items from the search results on the left into the playlist on the right. Jingles and voice tracks can be added before, after or between music items. + +After adding files to the playlist, the total playlist time is displayed in the top right corner. The duration of an individual file is shown in each row of the playlist in a white font, and beneath this figure the time since the beginning of the playlist is displayed in a smaller light grey font. This elapsed time figure can be used as a time check for voice tracks, although this option may limit the re-usability of the voice track. + +To audition a playlist file in your web browser, click the white triangle button on the left side of its row. (If the format of the file is not supported by your browser, the triangle in this button will be greyed out). If audition of the file format is supported, a pop-up window will open, with the playlist starting at the file you clicked. + +Click the small white **x** icon on the right hand side of each row to remove a file from the playlist. You can also drag and drop files to re-order them, or click the **Shuffle** button to re-order files automatically. + +When your playlist is complete, click the **New** button in the top left corner to create another playlist, click the close icon (a white cross in a black circle) in the top right corner, or browse to another page of the LibreTime interface. + +If you want to edit the playlist content or metadata later, you can find it by **Title**, **Creator**, **Last Modified** date, **Length**, **Owner** or **Year** using one of the search tools on the Library page. Click the playlist in the search results list, and then click **Edit** from the pop-up menu. You can also **Preview** the entire playlist in a pop-up audition window, **Duplicate** or **Delete** one of your playlists from this menu. + +### Creating a Smartblock + +![](img/Smartblock-options.png) + +Smart blocks are automatically filled with media files from the LibreTime library, according to the criteria that you specify. This feature is intended to save staff time, compared to selecting items for a playlist manually, and can be used to schedule shows that operate in a consistent format. + +To create a smart block, click the **Smartblocks** button on the left sidebar, and select **New** from the toolbar. Like a playlist, smart blocks can have a title and description, which you can edit. This helps you find relevant smart blocks in searches. + +Fill out the smart block's **Name**, **Search Criteria**, and **Limit to** sections. The search criteria can be any one of LibreTime's metadata categories, such as **Title**, **Creator** or **Genre**. The modifier depends on whether the metadata in question contains letters or numbers. For example, **Title** has modifiers including *contains* and *starts with*, whereas the modifiers for **BPM** include *is greater than* and *is in the range*. + +If you have a large number of files which meet the criteria that you specify, you may wish to limit the duration of the smart block using the **Limit to** field, so that it fits within the show you have in mind. Select **hours**, **minutes** or **items** from the drop-down menu, and click the **Generate** button again, if it is a static smart block. Then click the **Save** button. + +> Note: Smart Blocks by default will never overflow the Time Limit. For instance if you set a time limit of 1 hour. It will add tracks to the schedule until it can't add any more tracks without exceeding the hour. This is to prevent tracks from being cut-off because they exceed the time limit of a show. +> +> If you want a smartblock to schedule tracks until it is longer than the Time Limit you can check "Allow last track to exceed time limit". This will make LibreTime add tracks that meet the criteria until it equals or is longer than the time limit. This is helpful for avoiding dead air on shows that are being autoscheduled. + +![](img/Smartblock-advanced.png) + +You can also set the **smart block type**. A **Static** smart block will save the criteria and generate the block content immediately. This enables you to edit the contents of the block in the **Library** page before adding it to a show. A **Dynamic** smart block will only save the criteria, and the specific content will be generated at the time the block is added to a show. After that, the content of the show can be changed or re-ordered in the **Now Playing** page.  + +Click the **plus button** on the left to add OR criteria, such as **Creator** containing *beck* OR *jimi*. To add AND criteria, such as **Creator** containing *jimi* AND BPM in the range *120* to *130*, click the **plus button** on the right. (The criteria are not case sensitive). Click **Preview** to see the results. + +> If you see the message *0 files meet the criteria*, it might mean that the files in the Library have not been tagged with the correct metadata. See the chapter [Preparing media](preparing-media) for tips on tagging content. + +![](img/Smartblock-content.png) + +If you don't like the ordering which is generated, click the **Shuffle** button, or drag and drop the smart block contents into the order that you prefer. You can also remove items or add new items manually from the Library. Changes to static smart block contents are saved automatically when you add items, remove or re-order them, or click the **Generate** button. Click the **Save** button in the upper right corner to save any changes to smart block criteria. + +By default, a smart block will not contain repeated items, which will limit the duration of the block if you do not have sufficient items meeting the specified criteria in your **Library**. To override the default behaviour, check the **Allow Repeat Tracks** box. The **Sort tracks by** menu offers the options of **random**, **newest** or **oldest** items first. + +Smart blocks can be added to shows in the same way as a manually created playlist is added. Smart blocks can also be added to one or more playlists. In the case of a playlist containing a static smart block, click **Expand Static Block** to view the contents. For a dynamic smart block, you can review the criteria and duration limit by clicking **Expand Dynamic Block**. + +Once created, smart blocks can be found under the Smartblocks tab and refined at any time. They can be re-opened by right-clicking on the smart block and selecting **Edit** from the pop-up menu. diff --git a/docs/_docs/analytics.md b/docs/_docs/playout-history.md similarity index 92% rename from docs/_docs/analytics.md rename to docs/_docs/playout-history.md index 828e9b6f6..bde7c662b 100644 --- a/docs/_docs/analytics.md +++ b/docs/_docs/playout-history.md @@ -1,19 +1,16 @@ --- layout: article -title: Analytics -git: analytics.md +title: Playout History category: manager --- -## History {#history} - On the History menu, the **Playout History** page enables you to view a list of files played within a specific date and time range. This page is designed to help your station prepare reports for music royalty collection societies and regulatory agencies. Search results can be copied to the clipboard using the **Copy** button, exported as data in **CSV** format (comma separated values), exported as a document in **PDF** format, or displayed in a printer-friendly format using the **Print** button. (Your web browser must have an Adobe Flash plugin installed for these buttons to appear). Press the **Esc** key to return to the LibreTime interface once the print job is complete. This page has three tabs: **Log Sheet**, **File Summary** and **Show Summary**. On any of these tabs, you can select a date and time range by clicking the calendar and clock icons in the upper left corner of the page. Then click the search button, which has a magnifying glass icon, to the right. A list of files played during that date and time range will appear further down the page. -![](img/log-sheet.png) +![](/img/log-sheet.png) In the **Log Sheet** tab, the playout history is sorted by **Start Time** and **End Time** by default. @@ -29,11 +26,11 @@ Log entries can also be manually deleted, using the button with the trashcan ico ## History Templates -![](img/history-templates.png) +![](/img/history-templates.png) The **History Templates** page on the History menu enables you to prepare reports with the exact content required by regulatory agencies in the territories that you are broadcasting to. You can begin creating a custom template by clicking the button **New Log Sheet Template** or the button **New File Summary Template**. -![](img/new-hist-temp.png) +![](/img/new-hist-temp.png) Either of these actions opens a page in which you can name the new template, and add or remove elements from the list on the left. To add a new element from the list on the right, click the plus icon for the item you require. If the element you require is not listed, you can use the **Add New Field** box at the lower end of the right side column. Select *string*, *boolean*, *integer*, or *float*, depending on the type of data that you wish to log, and then click the **+ Add** button. @@ -288,17 +285,3 @@ Replace host, user and password values with appropriate values for your external Then make the new script executable and create a cron job to launch it every minute, as in step 8 above. Steps 3 to 7 above should be carried out on the external web server so that it can convert the two temporary files uploaded via FTP into public schedule data. If you have secure shell access (SSH) to the remote web server, you could write a script to use the secure copy command (scp) instead of ftp. - ---- - -## Streaming Listener Statistics - -The Listener Stats page on the Analytics menu shows graphs of listener connections to the configured streaming servers for the selected date and time range.  On the right side, a green **Status** indicator shows **OK** if the connection to the streaming server is active. - -![](img/portfolio/stream-stats.jpg) - -If the status indicator is red, check that the **Admin User** and **Admin Password** settings are correct under **Additional Options** for the named mount point, such as *libretime\_128*, on the **Streams** page of the **Settings** menu. - -By default, statistics for the last 24 hours of streaming are shown. To change this date and time range, click the calendar and clock icons in the lower left corner of the page, then click the magnifying glass icon. - -> To choose which particular streams should have statistics displayed, click the check boxes for the individual colour-coded mount points, just below the graph. diff --git a/docs/_docs/podcasts.md b/docs/_docs/podcasts.md new file mode 100644 index 000000000..8da09ffc2 --- /dev/null +++ b/docs/_docs/podcasts.md @@ -0,0 +1,37 @@ +--- +title: Podcasts +layout: article +category: interface +--- + +The Podcasts page allows you add subscriptions to podcasts which are often used to syndicated audio files using a URL called a RSS feed. This allows your LibreTime instance to automatically download new shows from the web. + +In order to add a podcast you need to get the RSS feed. All podcasts available on iTunes have a RSS feed but it is sometimes hidden. See this issue on our github page [#510](https://github.com/LibreTime/libretime/issues/510) for more information. RSS feeds that do not end in *.xml* may be accepted by LibreTime but might fail to download episodes; in that case, download the episode using a podcast client such as [gpodder](https://gpodder.github.io/) and then manually upload and schedule the episode. Podcast feeds coming from Anchor.fm have been known to have this issue. + +The podcast interfaces provides you with the ability to generate [Smartblocks](library#smartblocks) that can be used in conjunction with autoloading playlists to schedule the newest episode of a podcast without human intervention. + + + + + +
    +### Podcasts Dashboard + +![](/img/Podcasts_Dashboard.png) + +The podcasts dashboard is similar to the tracks view, allowing you to add, edit, and remove +podcasts by the toolbar, in addition to sorting by columns. + +To add a podcast, click on the **+ Add** button on the toolbar and provide the podcast's RSS feed, which usually ends in *.xml*. +Once the podcast's feed is recognized, the editor pane opens for the podcast. + +### Editor + +![](/img/Podcasts_Editor.png) + +In the podcasts editor, you can rename the podcast, update settings for the podcast, and manage episodes. +A search box is available to search for episodes within the feed. + +- To import an episode directly into LibreTime, double-click on an episode or select it and click **+ Import**. The podcast will appear under tracks with the Podcast Name as the Album. +- To delete an episode from LibreTime, select the episode and click on the red trash can on the toolbar. +- If you would like LibreTime to automatically download the latest episodes of a podcast, make sure *Download latest episodes* is checked. This can be used in conjunction with Smartblocks and Playlists to automate downloading and scheduling shows that are received via podcast feed. diff --git a/docs/_docs/preparing-media.md b/docs/_docs/preparing-media.md index 0c8382d81..aa53cf824 100644 --- a/docs/_docs/preparing-media.md +++ b/docs/_docs/preparing-media.md @@ -1,8 +1,7 @@ --- layout: article -title: Preparing Media for LibreTime -git: preparing-media.md -category: dj +title: Preparing Media for Upload +category: interface --- Before uploading media to an LibreTime server, there are a number of factors which should be considered. Getting your ingest workflow right will save you a lot of time later. @@ -13,14 +12,14 @@ LibreTime automatically imports any metadata that is in the files' ID3 tags. If There are a number of programs available which can be used to correct mistakes or incomplete information in ID3 tags. You can use a music library manager (like Apple Music, Rhythmbox, or Windows Media Player) to edit ID3 tags as well, but you may be required to import the files into your library, which may not always be convenient. -| Windows | macOS | Linux | -|---------|-------|-------| -| [Mp3tag](https://www.mp3tag.de/en/index.html) | [MusicBrainz Picard](https://picard.musicbrainz.org/) | [Ex Falso](http://code.google.com/p/quodlibet/) | -| [TagScanner](https://www.xdlab.ru/en/) | | | +- [TagScanner](https://www.xdlab.ru/en/) (Windows) +- [Mp3tag](https://www.mp3tag.de/en/index.html) (Windows) +- [MusicBrainz Picard](https://picard.musicbrainz.org/) (Mac, Windows, Linux) +- [Ex Falso](http://code.google.com/p/quodlibet/) (Linux) The *Tags From Path* feature of Ex Falso is a particularly useful time saver if you have a large archive of untagged files. Sometimes there is useful creator or title information in the file name or directory path structure, which can be converted into an ID3 tag automatically. -![](img/Screenshot175-Ex_Falso.png) +![](/img/Screenshot175-Ex_Falso.png) ## Metadata in legacy character sets @@ -42,19 +41,19 @@ To actually convert all of the tags and strip any legacy ID3v1 tag present from The name of the original character set follows the **-e** option. Other legacy character sets that mid3iconv can convert to UTF-8 include: -KOI8-R: Russian -KOI8-U: Ukrainian + KOI8-R: Russian + KOI8-U: Ukrainian -GBK: Traditional Chinese -GB2312: Simplified Chinese + GBK: Traditional Chinese + GB2312: Simplified Chinese -EUC-KR: Korean -EUC-JP: Japanese + EUC-KR: Korean + EUC-JP: Japanese -CP1253: Greek -CP1254: Turkish -CP1255: Hebrew -CP1256: Arabic + CP1253: Greek + CP1254: Turkish + CP1255: Hebrew + CP1256: Arabic ## Audio loudness @@ -94,4 +93,4 @@ Before importing media, it is good practice to check for any silent sections in Quiet introductions or extended fades can also lead to apparent gaps in your broadcast playout. This is more common when playing back audio from ripped CDs or dubbed from tape or vinyl; this issue is not as common with digitally-purchased music. For best results, long periods of silence should be removed from files before uploading to Libretime. -![](img/Screenshot126-Debra_silence.png) +![](/img/Screenshot126-Debra_silence.png) diff --git a/docs/_docs/reverse-proxy.md b/docs/_docs/reverse-proxy.md new file mode 100644 index 000000000..e5a039a8e --- /dev/null +++ b/docs/_docs/reverse-proxy.md @@ -0,0 +1,118 @@ +--- +title: Reverse Proxy +layout: article +category: install +--- + + +In some deployments, the LibreTime server is deployed behind a reverse proxy, +for example in containerization use-cases such as Docker and LXC. LibreTime +makes extensive use of its API for some site functionality, which causes +[Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) +to occur. By default, CORS requests are blocked by your browser and the origins +need to be added to the **Allowed CORS URLs** block in +[**General Settings**](settings). These origins should include any +domains that will be used externally to connect to your reverse proxy that you +want handled by LibreTime. These URLS can also be set during the first run configuration +that is displayed when you first install LibreTime + +### Reverse Proxy Basics + +A reverse proxy allows the LibreTime server to not be connected to the open internet. In +this configuration, it is rather behind another server that proxies traffic to it from +users. This provides some advantages in the containerization space, as this means that +the containers can be on their own internal network, protected from outside access. + +A reverse proxy also allows SSL to be terminated in a single location for multiple sites. +This means that all your traffic to the proxy from clients is encrypted, but the reverse +proxy's traffic to the containers on the internal network is not. All the SSL certificates +live on the reverse proxy and can be renewed there instead of on the individual +containers. + +### Setup + +There are known bugs when using LibreTime behind a reverse proxy ([#957](https://github.com/LibreTime/libretime/issues/957) +tracks the issue and contains a temporary workaround). For SSL redirection to work, you +need two domains: one for LibreTime and one for Icecast. Here, these will be +`libretime.example.com` and `icecast.example.com`. + +You will also require two VMs, servers or containers. Alternatively the reverse proxy can +be located on the server, proxying connections to containers also on the host. Setting up +a containerization environment is beyond the scope of this guide. It assumes that you have +Nginx set up on `proxy` and LibreTime will be installed on `libretime`. You will need root +access on both. `libretime` also needs to be able to be accessed from `proxy` +(`ping libretime` on `proxy`). + +On `libretime`, install LibreTime as described in the [install guide](quickstart). In short +this means run the following commands: + +``` +git clone https://github.com/LibreTime/libretime.git +cd libretime +sudo ./install -fiap +``` + +Once it has installed, replace `localhost` in +`/etc/icecast2/icecast.xml` with the following: + +``` +icecast.example.com +``` + +This is the hostname that people listening to your stream will connect to and what +LibreTime will use to stream out to them. You will then need to restart Icecast: + +``` +sudo systemctl restart icecast2 +``` + +On `proxy`, run the following: + +``` +cat << EOF | sudo tee /etc/nginx/sites-available/libretime.conf +server { + listen 80; + server_name libretime.example.com; + location / { + rewrite ^ https://$server_name$request_uri? permanent; + } +} +server { + listen 443 ssl; + server_name libretime.example.com; + ssl_certificate /etc/letsencrypt/live/libretime.example.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/libretime.example.com/privkey.pem; + add_header Strict-Transport-Security "max-age=15552000;"; + add_header X-Frame-Options "SAMEORIGIN"; + client_max_body_size 512M; + location / { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_pass http://libretime/; + } +} +EOF +``` + +This Nginx configuration ensures that all traffic uses SSL to the reverse proxy, and +traffic is proxied to `libretime`. + +Next, the SSL certificate needs to be generated and the site activated. + +``` +sudo apt install certbot +sudo systemctl stop nginx +sudo certbot certonly -d libretime.example.com -a standalone +sudo systemctl start nginx +``` + +You can now go to [https://libretime.example.com](https://libretime.example.com) and go +through the installer. On `General Settings`, you need to change the Webserver Port to +`443` and add the following CORS URLs: + +``` +https://libretime.example.com +http://libretime.example.com +https://localhost +http://localhost +``` \ No newline at end of file diff --git a/docs/_docs/rights-royalties.md b/docs/_docs/rights-royalties.md index 9d0b633bf..9f7d779dc 100644 --- a/docs/_docs/rights-royalties.md +++ b/docs/_docs/rights-royalties.md @@ -1,7 +1,7 @@ --- layout: article title: Rights and Royalties -git: rights-royalties.md +category: manager --- If you're new to broadcasting, or have not streamed your station online before, diff --git a/docs/_docs/scheduling-shows.md b/docs/_docs/scheduling-shows.md index 0799704a5..81a8493af 100644 --- a/docs/_docs/scheduling-shows.md +++ b/docs/_docs/scheduling-shows.md @@ -1,7 +1,7 @@ --- layout: article title: Scheduling Shows -git: scheduling-shows.md +category: interface --- ## Scheduling Shows @@ -18,7 +18,7 @@ The main workflow in LibreTime is **Upload** media -> create a show on the **Cal Once you log in, click on the big blue button on the left navigation that says **Upload**. -![](img/Select_files.png) +![](/img/Select_files.png) Select the type of media you are uploading (Music, Station IDs, etc.) by using the dropdown box at the top of the pane. After that, either drag and drop media into the area below or click the @@ -27,11 +27,11 @@ dashed rectangle to open a file browser. Once your files have uploaded and have been successfully imported (as shown in the pane on the right), click on **Calendar** on the left navigation. -![](img/Screenshot558-Add_Show.png) +![](/img/Screenshot558-Add_Show.png) Click on the blue **+ New Show** button to add a new show. -![](img/Screenshot560-Show_when.png) +![](/img/Screenshot560-Show_when.png) At the very minimum, fill out the show's name and when the show will take place. If the show will repeat regularly, check the **Repeats?** box and fill out the repeat information. A description of all fields of the New Show box @@ -64,13 +64,13 @@ of the pane to add your show to the calendar. Once your show is created, click on it to open its context menu. Select **Schedule Tracks** to open the track scheduler. -![](img/Screenshot561-Add_show_content.png) +![](/img/Screenshot561-Add_show_content.png) The track scheduler behaves similar to iTunes or Windows Media Player: media browser on the left, playlist on the right. Find the tracks that you'd like to schedule by using the search box or sorting columns and then drag them into the playlist. -![](img/Screenshot562-Drag_show_content.png) +![](/img/Screenshot562-Drag_show_content.png) The bar at the end of the show's playlist will show the amount of time the show is underscheduled or overscheduled. Shows that are underscheduled will have dead air at the end and shows that are overscheduled @@ -81,63 +81,8 @@ Show playback will start and end as per each show's start and end times, allowin LibreTime for running your station or using LibreTime as a part of your live setup to cover when DJs are not present. When media is playing, the **On Air** indicator at the top will turn red. -![](img/on-air-status.png) +![](/img/on-air-status.png) You can listen to your stream by going to `yourserverIP:8000` or by clicking the **Listen** button under the On Air indicator. ---- - -## Calendar Functions {#calendar} - -The Calendar page of the LibreTime administration interface has three views: **day**, **week** and **month**, which can be switched using the grey buttons in the top right corner. By default, the **month** view is shown, with today's date highlighted by a pale grey background. - -![](img/Screenshot451-Calendar.png) - -In the top left corner of the page, you can go back or forward through the **Calendar** by clicking on the buttons which have a small grey triangle in a white circle. Click the **today** button to jump to today's date in the current view. (The **today** button will be greyed out if you are already viewing that date). In the **day** or **week** views, there is also a drop-down menu which allows you to set the resolution displayed for the calendar, ranging from one minute per row to sixty minutes per row. - -![](img/Screenshot452-Calendar_resolution.png) - -### Editing a show - -Show configuration and metadata can be changed at any time, except for **Date/Time Start** and **Record from Line In?** options, which are fixed after broadcast of that show commences. Click the show in the Calendar, and select **Edit Show** from the pop-up context menu. This opens the **Update Show** box, which is almost exactly the same as the **Add this Show** box. Click the **+ Update show** button at the top or bottom of the box when you are done. - -![](img/Screenshot459-Update_show.png) - -Episodes of repeating shows also have an **Instance Description** field in which you can add details for that particular episode. Click the episode in the Calendar, click **Edit** on the pop-up menu, then click **Edit this instance**. After entering an Instance Description, click the **+ Update show** button. - -![](img/Screenshot583-Show_instance_description_vC9ooiT.png) - -Alternatively, individual shows can be clicked on and dragged to new days and times in the calendar. However, LibreTime will not allow you to drag a future show into the past, or drag and drop instances of a repeated show. In the **Day** and **Week** views, show length can be adjusted by clicking on the lower edge of the show box, and dragging the edge of the box upwards or downwards. The new show length is calculated automatically. - -### Adding content to a show - -To add content to a show, click the show in any view on the Calendar, and select **Schedule Tracks** from the pop-up menu. Shows that do not yet contain any scheduled content are marked with a red exclamation mark icon, to the right of the show start and end times in the top bar. Shows partially filled with content have a yellow exclamation mark icon. During playout of the show, a green play icon will also be shown in the top bar. - -![](img/Screenshot488-Add_remove_content.png) - -The **Schedule Tracks** action opens a window with the name of the show. Like when using the **Now Playing** page, you can search for content items and add them to the show schedule on the right side of the page. Refer to the *Now Playing* chapter for details. - -When your show has all the required content, click the **OK** button in the bottom right corner to close the window. Back in the **Calendar**, click the show and select **View** from the pop-up menu to view a list of content now included in the show. - -![](img/Screenshot489-Show_Content.png) - -The **Contents of Show** window is a read-only interface featuring an orange bar which indicates how much media has been added to the show. Click the **OK** button in the bottom right corner, or the white **x** icon in the top right corner, to close the window. - -![](img/Screenshot353-Contents_of_show.png) - -### Removing content from a show - -To remove an individual item from a show, click on the show in the **Calendar**, and select **Schedule Tracks** from the pop-up menu. In the window which opens, click any item you wish to remove from the show, then click **Delete** on the pop-up menu, or check the box in the item's row then click the **Remove** icon at the top of the table. To remove all files and playlists from a show, click on the show in the **Calendar**, and select **Clear Show** from the pop-up menu.  - -### Deleting an upcoming show - -To delete an upcoming instance of a repeating show, click on the show in the **Calendar**, and select **Delete**, then **Delete Instance** from the pop-up menu. If you wish to delete all future instances of a repeating show, select **Delete Instance and All Following** from the pop-up menu. - -![](img/Screenshot490-Delete_this_instance.png) - -You cannot delete or remove content from shows that have already played out. These shows have only one option on the pop-up menu, which is **View**. - -### Cancelling playout - -If you wish to cancel playout of a show while it is running, click on the show in the **Calendar** and select **Cancel Show** from the pop-up menu. This action cannot be undone. \ No newline at end of file diff --git a/docs/_docs/ssl.md b/docs/_docs/ssl.md new file mode 100644 index 000000000..69897856c --- /dev/null +++ b/docs/_docs/ssl.md @@ -0,0 +1,140 @@ +--- +title: SSL Configuration +layout: article +category: install +--- + +To increase the security of your server, you can enable encrypted access to the LibreTime administration interface, and direct your users towards this more secure login page. The main advantage of using this encryption is that your remote users' login names and passwords are not sent in plain text across the public Internet or untrusted local networks, such as shared Wi-Fi access points. + +### Deploying a certificate with Certbot + +One of the fastest, easiest, and cheapest ways to get an SSL certificate is through [Certbot](https://certbot.eff.org/), as created by the +[Electronic Frontier Foundation](https://www.eff.org/). There are some requirements for this process: +- you have an HTTP website (already installed and configured by default by the LibreTime installer) and +- this website is open to the public internet (likely via. port forwarding if your computer is behind a firewall) and +- the server is accessible to the public via. port 80 + +If you aren't able to verify all three requirements, you may want to try a self-signed certificate (see next section). + +These instructions come from Certbot's website and assume that you are using an Apache web server +running on Ubuntu 18.04 LTS (the Apache web server is installed with LibreTime by default). +Instructions for other Debian-based OSes are similar, but check with Certbot for clarification. + +Note: all instructions require you to have sudo priveledges + +First, add Certbot's PPA using: + +``` + sudo apt-get update + sudo apt-get install software-properties-common + sudo add-apt-repository universe + sudo add-apt-repository ppa:certbot/certbot + sudo apt-get update +``` + +Next, install Certbot and install the SSL certificate using the below commands: + +``` +sudo apt-get install certbot python3-certbot-apache +sudo certbot --apache # get and install the certificate +sudo certbot certonly --apache # to only get the certificate, not install it using Certbot +``` + +You can test certificate renewal by running `sudo certbot renew --dry-run`. +Head to your server's IP address to check to see that the installation worked. + +### Deploying a self-signed certificate + +The Debian/Ubuntu package *ssl-cert* creates a *snakeoil* certificate and key based on your server's hostname. This gratis certificate and key pair created under the */etc/ssl/certs*/ and */etc/ssl/private/* directories will not be recognised by users' browsers without manual intervention. You can install the *ssl-cert* package with the command: + + sudo apt-get install ssl-cert + +If the hostname of your server does not match the domain name you intend to use with the LibreTime virtual host, the user's browser will present an additional security warning. You can set the domain name of the certificate by editing the file */usr/share/ssl-cert/ssleay.cnf* to replace the *@HostName@* variable: + + commonName = @HostName@ + +with the domain name used by LibreTime: + + commonName = airtime.example.com + +Then save the file and regenerate the certificate with the command: + + sudo make-ssl-cert generate-default-snakeoil --force-overwrite + +You should enable additional Apache modules for page redirections, custom headers and secure access: + + sudo a2enmod alias headers ssl + +Next, edit the virtual host configuration for your LibreTime server to include a stanza for the https:// interface on port 443 and a redirect for logins from port 80: + + sudo nano /etc/apache2/sites-available/airtime-vhost.conf + +Using the following configuration for Apache 2.2 as a guide, replace *airtime.example.com* with the name of your server and *admin@example.com* with your email address. The older SSLv2 and SSLv3 protocols and SSL compression should be disabled, as they are generally believed to be insecure. You may wish to create a *ServerAlias* for users to access the administration interface over https:// if required. + +On port 80, Apache's *alias* module is used to set a *Redirect permanent* for the login page. Optionally, access could be denied to all sites except *localhost* and any other LibreTime servers on your network, so that unencrypted communication between LibreTime components can continue. + +``` + + SSLEngine on + SSLProtocol All -SSLv2 -SSLv3 + SSLCompression off + SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem + SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key + Header always set Strict-Transport-Security "max-age=31536000" + + ServerName airtime.example.com + #ServerAlias www.example.com + + ServerAdmin admin@example.com + + DocumentRoot /usr/share/airtime/php/airtime_mvc/public + DirectoryIndex index.php + + + Options -Indexes FollowSymLinks MultiViews + AllowOverride all + Order allow,deny + Allow from all + + + + + ServerName airtime.example.com + + ServerAdmin admin@example.com + + DocumentRoot /usr/share/airtime/php/airtime_mvc/public + Redirect permanent /login https://airtime.example.com/login + + SetEnv APPLICATION_ENV "production" + + + Options -Indexes FollowSymLinks MultiViews + AllowOverride All + Order allow,deny + Allow from all + + +``` + +Save the file with **Ctrl+O** and exit the **nano** editor with **Ctrl+X**. Then restart Apache with the command: + + sudo service apache restart + +When attempting to log into your server via http:// in future, you should be redirected to https:// automatically. + +### Importing a self-signed certificate into the browser + +The first time you access an LibreTime server with a self-signed certificate over https:// your browser will block the login page and display a security warning. In **Mozilla Firefox**, you can click **Technical Details** to confirm that the warning is due to the certificate being self-signed before clicking the **Add Exception** button. In **Google Chrome**, the button to click on the security warning page is **Proceed Anyway**. + +![](/img/Screenshot547-connection_untrusted.png) + +On the next page in Firefox, click the **Get Certificate** button to inspect the details of the self-signed certificate. If all is well, click the **Confirm Security Exception** button. You should now be able to proceed to the https:// login page.   + +![](/img/Screenshot548-confirm_exception.png) + +If the users of your LibreTime server wish to avoid going through these steps, or they do not trust the remote LibreTime server to be what it claims to be, it is also possible to import a trusted local copy of a certificate file into the browser. For example, in Firefox version 30 preferences, you can go into the **Advanced** section, click the **Certificates** tab, then click the **View Certificates** button. On the **Servers** tab of the **Certificate Manager**, there is an **Import** button which enables you to load a certificate file from the local computer. + +### Mixed encrypted and unencrypted content + +Whether your certificate is self-signed or not, you will see browser security warnings whenever a https:// page is delivering unencrypted content, such as the stream from an Icecast server. In Firefox, an exclamation mark icon is displayed in the address bar of the **Listen** pop-up. diff --git a/docs/_docs/users.md b/docs/_docs/users.md index a46951070..f441ca045 100644 --- a/docs/_docs/users.md +++ b/docs/_docs/users.md @@ -1,8 +1,7 @@ --- layout: article title: Managing Users -git: users.md -category: manager +category: interface --- > Note: if your Airtime server is accessible from the public Internet (ex. being hosted in a cloud VM) diff --git a/docs/_docs/webstreams.md b/docs/_docs/webstreams.md new file mode 100644 index 000000000..6f53960a9 --- /dev/null +++ b/docs/_docs/webstreams.md @@ -0,0 +1,21 @@ +--- +title: Webstreams +layout: article +category: interface +--- + + + + + + +### Adding a webstream +A web stream URL and metadata can be added to the LibreTime library, so that a remote stream can be searched for and scheduled to be *pulled* into a show. For example, at the top of the hour your station may pull a news report from journalists working in another studio. This is a different concept from **Master Source** and **Show Source** remote streams which are *pushed* into the LibreTime playout schedule. + +To add a web stream, click the **+ New** button on the left side of the Webstreams page. Like a playlist, web streams in the Library can have a title and **Description**, which may help you find them in searches later. + +![](/img/webstream.jpg) + +The **Stream URL** setting must include the *port number* (such as 8000) and *mount point* (such as remote\_stream) of the remote stream, in addition to the streaming server name. A **Default Length** for the remote stream can also be set. If the stream is added at the end of a show which becomes overbooked as a result, it will be faded out when the show ends. + +Note: LibreTime checks the remote webstream's status upon editing stream settings, so an offline stream will result in an error. There are many tools such as [BUTT](https://danielnoethen.de/butt/) and [MIXXX](https://www.mixxx.org) that can be used to send a test stream to LibreTime can save it; read more [here](../live-shows-with-mixxx/index.md). diff --git a/docs/_includes/fans.html b/docs/_includes/fans.html index 00c3e3ff1..6c14eee8d 100644 --- a/docs/_includes/fans.html +++ b/docs/_includes/fans.html @@ -3,10 +3,9 @@

    Our Biggest Fans

    -
    - +

    -
    "We needed a solution for remote broadcasting
    during the Coronavirus pandemic without having to
    use remote desktop at 1 a.m.
    Saved. Our. Butts."
    Zachary Klosko - Operations at WRIR diff --git a/docs/_includes/footer.html b/docs/_includes/footer.html index 77960f284..1a4129663 100644 --- a/docs/_includes/footer.html +++ b/docs/_includes/footer.html @@ -1,8 +1,53 @@ -
    - -
    \ No newline at end of file + +
    +

    Contribute

    + + + + +
    + +
    +

    Support

    + + + + +
    + +
    +

    Community

    + + + + +
    +
    + +
    + +
    +
    +

    Code and docs licensed under GPLv2. More details here.

    +
    + +
    + + \ No newline at end of file diff --git a/docs/_includes/navbar.html b/docs/_includes/navbar.html index a79fd1787..380f446f3 100644 --- a/docs/_includes/navbar.html +++ b/docs/_includes/navbar.html @@ -7,11 +7,15 @@ diff --git a/docs/_includes/toc.html b/docs/_includes/toc.html deleted file mode 100644 index 509015625..000000000 --- a/docs/_includes/toc.html +++ /dev/null @@ -1,112 +0,0 @@ -{% capture tocWorkspace %} - {% comment %} - Version 1.0.12 - https://github.com/allejo/jekyll-toc - - "...like all things liquid - where there's a will, and ~36 hours to spare, there's usually a/some way" ~jaybe - - Usage: - {% include toc.html html=content sanitize=true class="inline_toc" id="my_toc" h_min=2 h_max=3 %} - - Parameters: - * html (string) - the HTML of compiled markdown generated by kramdown in Jekyll - - Optional Parameters: - * sanitize (bool) : false - when set to true, the headers will be stripped of any HTML in the TOC - * class (string) : '' - a CSS class assigned to the TOC - * id (string) : '' - an ID to assigned to the TOC - * h_min (int) : 1 - the minimum TOC header level to use; any header lower than this value will be ignored - * h_max (int) : 6 - the maximum TOC header level to use; any header greater than this value will be ignored - * ordered (bool) : false - when set to true, an ordered list will be outputted instead of an unordered list - * item_class (string) : '' - add custom class(es) for each list item; has support for '%level%' placeholder, which is the current heading level - * baseurl (string) : '' - add a base url to the TOC links for when your TOC is on another page than the actual content - * anchor_class (string) : '' - add custom class(es) for each anchor element - * skipNoIDs (bool) : false - skip headers that do not have an `id` attribute - - Output: - An ordered or unordered list representing the table of contents of a markdown block. This snippet will only - generate the table of contents and will NOT output the markdown given to it - {% endcomment %} - - {% capture my_toc %}{% endcapture %} - {% assign orderedList = include.ordered | default: false %} - {% assign skipNoIDs = include.skipNoIDs | default: false %} - {% assign minHeader = include.h_min | default: 1 %} - {% assign maxHeader = include.h_max | default: 6 %} - {% assign nodes = include.html | split: ' maxHeader %} - {% continue %} - {% endif %} - - {% assign _workspace = node | split: '' | first }}>{% endcapture %} - {% assign header = _workspace[0] | replace: _hAttrToStrip, '' %} - - {% assign indentAmount = headerLevel | minus: minHeader %} - {% assign space = '' %} - {% for i in (1..indentAmount) %} - {% assign space = space | prepend: ' ' %} - {% endfor %} - - {% if include.item_class and include.item_class != blank %} - {% capture listItemClass %}{:.{{ include.item_class | replace: '%level%', headerLevel }}}{% endcapture %} - {% endif %} - - {% capture anchor_body %}{% if include.sanitize %}{{ header | strip_html }}{% else %}{{ header }}{% endif %}{% endcapture %} - {% capture anchor_body %}{{ anchor_body | replace: "|", "\|" }}{% endcapture %} - - {% if html_id %} - {% capture list_item %}[{{ anchor_body }}]({% if include.baseurl %}{{ include.baseurl }}{% endif %}#{{ html_id }}){% endcapture %} - {% else %} - {% capture list_item %}{{ anchor_body }}{% endcapture %} - {% endif %} - - {% capture my_toc %}{{ my_toc }} -{{ space }}{{ listModifier }} {{ listItemClass }} {{ list_item }}{% if include.anchor_class %}{:.{{ include.anchor_class }}}{% endif %}{% endcapture %} - {% endfor %} - - {% if include.class and include.class != blank %} - {% capture my_toc %}{:.{{ include.class }}} -{{ my_toc | lstrip }}{% endcapture %} - {% endif %} - - {% if include.id %} - {% capture my_toc %}{: #{{ include.id }}} -{{ my_toc | lstrip }}{% endcapture %} - {% endif %} -{% endcapture %}{% assign tocWorkspace = '' %}{{ my_toc | markdownify | strip }} diff --git a/docs/_layouts/404.html b/docs/_layouts/404.html index 85dad0a60..4b8865762 100644 --- a/docs/_layouts/404.html +++ b/docs/_layouts/404.html @@ -4,7 +4,7 @@ layout: default
    - 404 Tape + 404 Tape

    Page not found :(

    The requested page could not be found.

    diff --git a/docs/_layouts/article.html b/docs/_layouts/article.html index f518edaa3..2bf62614d 100644 --- a/docs/_layouts/article.html +++ b/docs/_layouts/article.html @@ -8,14 +8,30 @@ layout: default
    + + + + + + +
    +

    @@ -25,10 +41,16 @@ layout: default
    -

    Contents

    +

    In This Section

    -
    {% include toc.html html=content class=toc %}
    +
      + {% for doc in site.docs %} + {% if doc.category == page.category %} +
    • {{doc.title}}
    • + {% endif %} + {% endfor %} +
    diff --git a/docs/_layouts/docindex.html b/docs/_layouts/docindex.html index 228fff4f4..10f2bd1f9 100644 --- a/docs/_layouts/docindex.html +++ b/docs/_layouts/docindex.html @@ -25,7 +25,7 @@ layout: default
    -

    Configure

    +

    Configure

    We strive to embrace and drive change in our industry.

    @@ -65,7 +65,7 @@ layout: default
    -

    FAQs

    +

    FAQs

    Have questions?
    We have answers.

    @@ -76,7 +76,7 @@ layout: default
    -
    +
    @@ -98,11 +98,11 @@ layout: default
    -

    For DJs

    +

    Using Libretime

      {% for doc in site.docs %} - {% if doc.category == "dj" %} + {% if doc.category == "interface" %}
    • {{doc.title}}
    • {% endif %} {% endfor %} @@ -110,7 +110,7 @@ layout: default
    - +

    For Program Managers

      @@ -122,14 +122,14 @@ layout: default
    -
    +

    For System Administrators

    diff --git a/docs/_layouts/faq.html b/docs/_layouts/faq.html deleted file mode 100644 index a30181d17..000000000 --- a/docs/_layouts/faq.html +++ /dev/null @@ -1,504 +0,0 @@ - - - - - FAQ | Docs - UI Kit - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - -
    - - - -
    -
    -
    -
    -

    Frequently Asked Questions

    - - -
    -
    -
    - - - - - - - -
    - - -
    -
    -
    - - -
    -
    -
    -
    -
    -
    -
    What is Docs UI Kit?
    - -
    - -
    -
    Docs UI Kit is beautiful Open Source Bootstrap 4 UI Kit under MIT license. The UI Kit comes with more than 10 beautiful complete pages and includes a lot of reusable and customizable UI Blocks. Furthermore, Docs UI Kit includes 4 different Documentation Layouts.
    -
    -
    - -
    - -
    -
    Yes! It's just absolutely FREE and comes with a simple license! If you have any questions or doubts, please feel free to reach us here.
    -
    -
    - -
    - -
    -
    Yes! Docs UI Kit is absolutely Free and you can use in both personal and commercial projects without any attribution. However, we would much appreciate for any attribution.
    -
    -
    - -
    -
    -
    Where can I download Docs UI Kit?
    - -
    - -
    -
    You can download Docs UI Kit on Htmlstream website or on the Github page.
    -
    -
    - -
    - -
    -
    Corrently, all our freebies comes with community support and you can open an issue on the product github page. We will do our best to answers your questions.
    -
    -
    - -
    - -
    -
    Sure! Please feel free to contribute - we appreciate any community help! You can pull request on github page.
    -
    -
    - -
    - -
    -
    You can check out our documenttion for detailed examples.
    -
    -
    - -
    - -
    -
    Docs UI Kit comes with 10+ example pages.
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    Installation Group Item #1
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    Update Group Item #1
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    Gitlab Group Item #1
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -

    Can't find what you're looking for?

    -
    Let us help you right now!
    - Submit a Request -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_layouts/splash.html b/docs/_layouts/splash.html index 5c98b15f7..b7135bfe0 100644 --- a/docs/_layouts/splash.html +++ b/docs/_layouts/splash.html @@ -88,6 +88,13 @@
    + + + + + + + @@ -96,7 +103,6 @@

    Key features include

    -
    @@ -214,7 +220,7 @@
    - + {% include fans.html %}
    @@ -222,7 +228,6 @@

    Get started with LibreTime today

    -

    Requires: 1 Ghz processor, 2 GB RAM, wired ethernet connection with static IP address and you.
    What are you waiting for?

    @@ -238,6 +243,7 @@
    + {% include footer.html %} {% include scripts.html %} diff --git a/docs/css/creative.min.css b/docs/css/creative.min.css index 8a44eb7a7..982cb1936 100755 --- a/docs/css/creative.min.css +++ b/docs/css/creative.min.css @@ -13,7 +13,7 @@ body{ } hr{ - max-width:50px;border-width:3px;border-color:#f05f40 + max-width:auto;border-width:2px;border-color:#f05f40 } hr.light{ @@ -56,9 +56,9 @@ th{ } tr, td{ - padding: 15px; + padding: 5px; text-align: left; - border-bottom: 1px solid #f05f40 + border: 2px solid #f05f40 } /* Theme Colors */ @@ -205,11 +205,8 @@ header.masthead p{font-weight:300} /* Blockquotes */ blockquote { -border: 1px solid #212529; -border-left: 10px solid #f05f40; +border: 2px solid #f05f40; border-radius: 0px; -background: #212529; -color: white; font-size: .9rem; margin: 10px; padding: 10px 20px; @@ -235,8 +232,7 @@ font-size: .9rem; } pre.highlight { -border: 1px solid #212529; -border-left: 10px solid #212529; +border-left: 2px solid #212529; background: white; color: black; font-size: .9rem; @@ -258,8 +254,8 @@ padding: 10px 20px; margin: 0 3em 3em 0; border-radius: 50%; padding: .25em; - width: 80px; - height: 80px; + width: 50px; + height: 50px; background-color: #F8F8F8; } @@ -325,13 +321,66 @@ padding: 10px 20px; padding: 10px 0px; } -/* Page Title */ -.allcaps { - text-transform: uppercase +/* Gradients */ +.gradient-dark { + background-color: #343a40; + background-image: -webkit-gradient(linear, left top, right top, from(#343a40), to(#000)); + background-image: -webkit-linear-gradient(left, #343a40, #000); + background-image: -o-linear-gradient(left, #343a40, #000); + background-image: linear-gradient(to right, #343a40, #000); } - .center { - margin: auto; - width: 50%; - padding: 10px; +.gradient-primary { + background-color: transparent; + background-image: -webkit-gradient(linear, left top, right top, from(#FF7657), to(#FF6745)); + background-image: -webkit-linear-gradient(left, #FF7657, #FF6745); + background-image: -o-linear-gradient(left, #FF7657, #FF6745); + background-image: linear-gradient(to right, #FF7657, #FF6745); +} + +/*------------------------------------ + List +------------------------------------*/ +.list-line { + list-style: none; + padding-left: 0; +} + +.list-line li { + position: relative; + color: #FF6745; + padding-left: 1.5rem; +} + +.list-line li::before { + position: absolute; + top: 0; + left: 0; + content: "—"; + color: #FF6745; +} + +.list-icon { + list-style: none; + padding-left: 0; +} + +.list-icon li { + position: relative; + color: #212529; + padding-left: 1.8rem; + margin-bottom: 1.8rem; +} + +.list-icon li::before { + position: absolute; + top: 0; + left: 0; +} + +.list-icon-star li::before { + content: "\f005"; + font-family: "Font Awesome 5 Free"; + font-weight: 400; + color: #007bff; } \ No newline at end of file diff --git a/quickstart.sh b/quickstart.sh new file mode 100644 index 000000000..b20706c22 --- /dev/null +++ b/quickstart.sh @@ -0,0 +1,9 @@ +# /bin/bash +# This script allows for a one-liner to download and install Libretime +# using the default settings. Assumes a clean server setup. Needs sudo. + +git clone https://github.com/LibreTime/libretime.git + +cd libretime + +bash install -fiap \ No newline at end of file From 0cd5faa973c1930cc3b5fef73ce9452b9d696edc Mon Sep 17 00:00:00 2001 From: Zachary Klosko Date: Mon, 2 Nov 2020 13:52:53 -0500 Subject: [PATCH 03/12] Nevermind --- docs/_docs/host-configuration.md | 5 ++++- docs/_layouts/splash.html | 25 ++++++++++++---------- docs/{_layouts/docindex.html => docs.html} | 0 docs/docs.md | 4 ---- quickstart.sh | 9 -------- 5 files changed, 18 insertions(+), 25 deletions(-) rename docs/{_layouts/docindex.html => docs.html} (100%) delete mode 100644 docs/docs.md delete mode 100644 quickstart.sh diff --git a/docs/_docs/host-configuration.md b/docs/_docs/host-configuration.md index 18b6007e4..a2acedb97 100644 --- a/docs/_docs/host-configuration.md +++ b/docs/_docs/host-configuration.md @@ -124,8 +124,11 @@ upload_max_filesize = 40M post_max_size = 40M ``` -For quick reference, 1024 MB = 1 GB and 2048 MB = 2 GB, but most will be okay with rounding to the nearest thousand. After updating the config file, restart Apache by `sudo systemctl apache restart`. +For quick reference, 1024 MB = 1 GB and 2048 MB = 2 GB, but most will be okay with rounding to the nearest thousand. After updating the config file, restart Apache. +``` +sudo systemctl apache restart +``` ### Playout settings {#playout} diff --git a/docs/_layouts/splash.html b/docs/_layouts/splash.html index b7135bfe0..5ec2fad05 100644 --- a/docs/_layouts/splash.html +++ b/docs/_layouts/splash.html @@ -84,7 +84,6 @@

    The platform is a fork of the older AirTime project which is no longer actively maintained, and the evolution of Open Source broadcasting for both current AirTime and new users looking to continue with the platform on their own infrastructure.

    - Key Features
    @@ -223,20 +222,24 @@ {% include fans.html %} -
    +
    -

    Get started with LibreTime today

    +

    Get started

    + + +

    - Requires: 1 Ghz processor, 2 GB RAM, wired ethernet connection with static IP address and you.
    What are you waiting for? -

    +

    Install Libretime in just three commands:

    + git clone https://github.com/LibreTime/libretime.git +
    + cd libretime +
    + sudo bash install -fiap + +

    + Next: Host Configuration

    diff --git a/docs/_layouts/docindex.html b/docs/docs.html similarity index 100% rename from docs/_layouts/docindex.html rename to docs/docs.html diff --git a/docs/docs.md b/docs/docs.md deleted file mode 100644 index 6d6191810..000000000 --- a/docs/docs.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Docs -layout: docindex ---- \ No newline at end of file diff --git a/quickstart.sh b/quickstart.sh deleted file mode 100644 index b20706c22..000000000 --- a/quickstart.sh +++ /dev/null @@ -1,9 +0,0 @@ -# /bin/bash -# This script allows for a one-liner to download and install Libretime -# using the default settings. Assumes a clean server setup. Needs sudo. - -git clone https://github.com/LibreTime/libretime.git - -cd libretime - -bash install -fiap \ No newline at end of file From 79b8aab4fa915bc6ce53252b4cb80a4ee6f12d8e Mon Sep 17 00:00:00 2001 From: Zachary Klosko Date: Mon, 2 Nov 2020 15:43:48 -0500 Subject: [PATCH 04/12] Fixing broken urls --- .../application/controllers/LocaleController.php | 2 +- .../views/scripts/form/add-show-autoplaylist.phtml | 2 +- airtime_mvc/locale/ast/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/az/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/cs_CZ/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/da_DK/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/de_AT/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/de_DE/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/el_GR/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/en_CA/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/en_GB/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/en_US/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/es_ES/LC_MESSAGES/airtime.po | 4 ++-- airtime_mvc/locale/fr_FR/LC_MESSAGES/airtime.po | 4 ++-- airtime_mvc/locale/hr_HR/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/hu_HU/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/hy/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/hy_AM/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/id_ID/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/it_IT/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/ja/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/ja_JP/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/ka/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/ko_KR/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/lt/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/nl_NL/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/pl_PL/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/pt_BR/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/ro_RO/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/ru_RU/LC_MESSAGES/airtime.po | 4 ++-- airtime_mvc/locale/si/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/sr_RS/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/sr_RS@latin/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/template/airtime.po | 2 +- airtime_mvc/locale/tr/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/locale/zh_CN/LC_MESSAGES/airtime.po | 2 +- airtime_mvc/public/js/airtime/library/library.js | 10 +++++----- airtime_mvc/public/js/airtime/schedule/add-show.js | 2 +- 38 files changed, 45 insertions(+), 45 deletions(-) diff --git a/airtime_mvc/application/controllers/LocaleController.php b/airtime_mvc/application/controllers/LocaleController.php index e1c1a0e77..cbec559ce 100644 --- a/airtime_mvc/application/controllers/LocaleController.php +++ b/airtime_mvc/application/controllers/LocaleController.php @@ -477,7 +477,7 @@ final class LocaleController extends Zend_Controller_Action "Disabled" => _("Disabled"), "Cancel upload" => _("Cancel upload"), "Type" => _("Type"), - "Autoloading playlists' contents are added to shows one hour before the show airs. More information" => _("Autoloading playlists' contents are added to shows one hour before the show airs. More information"), + "Autoloading playlists' contents are added to shows one hour before the show airs. More information" => _("Autoloading playlists' contents are added to shows one hour before the show airs. More information"), "Podcast settings saved" => _("Podcast settings saved"), "Are you sure you want to delete this user?" => _("Are you sure you want to delete this user?"), "Can't delete yourself!" => _("Can't delete yourself!"), diff --git a/airtime_mvc/application/views/scripts/form/add-show-autoplaylist.phtml b/airtime_mvc/application/views/scripts/form/add-show-autoplaylist.phtml index a694866a8..98dd8a093 100644 --- a/airtime_mvc/application/views/scripts/form/add-show-autoplaylist.phtml +++ b/airtime_mvc/application/views/scripts/form/add-show-autoplaylist.phtml @@ -11,7 +11,7 @@
    -

    More information"); ?>

    +

    More information"); ?>

    - - - - - - - - - - \ No newline at end of file diff --git a/docs/_layouts/splash.html b/docs/_layouts/splash.html index 5ec2fad05..a9af2876c 100644 --- a/docs/_layouts/splash.html +++ b/docs/_layouts/splash.html @@ -227,6 +227,7 @@

    Get started

    +
    diff --git a/docs/faq.html b/docs/faq.html new file mode 100644 index 000000000..4be984cac --- /dev/null +++ b/docs/faq.html @@ -0,0 +1,298 @@ +--- +layout: default +--- + + +
    +
    +
    +
    +

    Frequently Asked Questions

    + + +
    +
    +
    + + + + + + + +
    + + +

    +

    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    What is Docs UI Kit?
    + +
    + +
    +
    Docs UI Kit is beautiful Open Source Bootstrap 4 UI Kit under MIT license. The UI Kit comes with more than 10 beautiful complete pages and includes a lot of reusable and customizable UI Blocks. Furthermore, Docs UI Kit includes 4 different Documentation Layouts.
    +
    +
    + +
    + +
    +
    Yes! It's just absolutely FREE and comes with a simple license! If you have any questions or doubts, please feel free to reach us here.
    +
    +
    + +
    + +
    +
    Yes! Docs UI Kit is absolutely Free and you can use in both personal and commercial projects without any attribution. However, we would much appreciate for any attribution.
    +
    +
    + +
    +
    +
    Where can I download Docs UI Kit?
    + +
    + +
    +
    You can download Docs UI Kit on Htmlstream website or on the Github page.
    +
    +
    + +
    + +
    +
    Corrently, all our freebies comes with community support and you can open an issue on the product github page. We will do our best to answers your questions.
    +
    +
    + +
    + +
    +
    Sure! Please feel free to contribute - we appreciate any community help! You can pull request on github page.
    +
    +
    + +
    + +
    +
    You can check out our documenttion for detailed examples.
    +
    +
    + +
    + +
    +
    Docs UI Kit comes with 10+ example pages.
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    Installation Group Item #1
    + +
    + +
    +
    + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
    +
    +
    + +
    + +
    +
    + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
    +
    +
    + +
    + +
    +
    + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    Update Group Item #1
    + +
    + +
    +
    + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
    +
    +
    + +
    + +
    +
    + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
    +
    +
    + +
    + +
    +
    + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    Gitlab Group Item #1
    + +
    + +
    +
    + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
    +
    +
    + +
    + +
    +
    + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
    +
    +
    + +
    + +
    +
    + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +

    Can't find what you're looking for?

    +
    Open an issue on our Github!
    + Submit a Request +
    +
    +
    From ce24d4159559c540baac3ee3affa8b966a1cbcdc Mon Sep 17 00:00:00 2001 From: Zachary Klosko Date: Tue, 3 Nov 2020 18:04:09 -0500 Subject: [PATCH 07/12] Starting to update FAQ page, continuing to fix links --- docs/_config.yml | 2 +- .../{icecast-shoutcast.md => icecast.md} | 3 +- docs/_docs/playlists.md | 23 ++- docs/_docs/settings.md | 50 ++---- docs/_docs/status.md | 18 +++ docs/faq.html | 152 +----------------- 6 files changed, 59 insertions(+), 189 deletions(-) rename docs/_docs/{icecast-shoutcast.md => icecast.md} (98%) create mode 100644 docs/_docs/status.md diff --git a/docs/_config.yml b/docs/_config.yml index 56dc964f4..6a38ed42f 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,7 +1,7 @@ title: LibreTime description: An open source radio automation server made for (and by) low-power FM stations and the rest of us. logo: 144px.png -baseurl: "/" # The subpath of your site, e.g. /blog +baseurl: "/libretime" # The subpath of your site, e.g. /blog url: "https://libretime.org" # The base hostname & protocol for your site, e.g. http://example.com layouts_dir: _layouts diff --git a/docs/_docs/icecast-shoutcast.md b/docs/_docs/icecast.md similarity index 98% rename from docs/_docs/icecast-shoutcast.md rename to docs/_docs/icecast.md index 9ff2ee0d7..184087be8 100644 --- a/docs/_docs/icecast-shoutcast.md +++ b/docs/_docs/icecast.md @@ -1,7 +1,6 @@ --- layout: article -title: Icecast and Shoutcast Stream Configuration -git: icecast-shoutcast.md +title: Icecast Configuration category: admin --- diff --git a/docs/_docs/playlists.md b/docs/_docs/playlists.md index 62e3e8680..584eae882 100644 --- a/docs/_docs/playlists.md +++ b/docs/_docs/playlists.md @@ -4,11 +4,17 @@ title: Playlists and Smartblocks category: interface --- +> **About Autoloading Playlists** +> +> Libretime will schedule tracks from a selected playlist an hour before a show is +> scheduled to air. This is a great way to automatically schedule weekly shows which are received +> via. podcasts. + ### Creating a new playlist You can create a new playlist on the toolbar of the **Playlists** page. -![](img/Playlist-Editor.png) +![](/img/Playlist-Editor.png) Enter a **Name** and **Description** for the playlist, then click the **Save** button. Setting good quality metadata here will help you find the playlist using the search box later, so you should be as descriptive as possible. @@ -28,7 +34,7 @@ If you want to edit the playlist content or metadata later, you can find it by * ### Creating a Smartblock -![](img/Smartblock-options.png) +![](/img/Smartblock-options.png) Smart blocks are automatically filled with media files from the LibreTime library, according to the criteria that you specify. This feature is intended to save staff time, compared to selecting items for a playlist manually, and can be used to schedule shows that operate in a consistent format. @@ -38,19 +44,20 @@ Fill out the smart block's **Name**, **Search Criteria**, and **Limit to** secti If you have a large number of files which meet the criteria that you specify, you may wish to limit the duration of the smart block using the **Limit to** field, so that it fits within the show you have in mind. Select **hours**, **minutes** or **items** from the drop-down menu, and click the **Generate** button again, if it is a static smart block. Then click the **Save** button. -> Note: Smart Blocks by default will never overflow the Time Limit. For instance if you set a time limit of 1 hour. It will add tracks to the schedule until it can't add any more tracks without exceeding the hour. This is to prevent tracks from being cut-off because they exceed the time limit of a show. -> -> If you want a smartblock to schedule tracks until it is longer than the Time Limit you can check "Allow last track to exceed time limit". This will make LibreTime add tracks that meet the criteria until it equals or is longer than the time limit. This is helpful for avoiding dead air on shows that are being autoscheduled. +> Note: Smart Blocks by default will not overflow the length of a scheduled show. +> This is to prevent tracks from being cut-off because they exceed the time limit of a show. +> If you want a smartblock to schedule tracks until it is longer than the Time Limit you can check **"Allow last track to exceed time limit"** +> (helpful for avoiding dead air on autoscheduled shows). -![](img/Smartblock-advanced.png) +![](/img/Smartblock-advanced.png) You can also set the **smart block type**. A **Static** smart block will save the criteria and generate the block content immediately. This enables you to edit the contents of the block in the **Library** page before adding it to a show. A **Dynamic** smart block will only save the criteria, and the specific content will be generated at the time the block is added to a show. After that, the content of the show can be changed or re-ordered in the **Now Playing** page.  Click the **plus button** on the left to add OR criteria, such as **Creator** containing *beck* OR *jimi*. To add AND criteria, such as **Creator** containing *jimi* AND BPM in the range *120* to *130*, click the **plus button** on the right. (The criteria are not case sensitive). Click **Preview** to see the results. -> If you see the message *0 files meet the criteria*, it might mean that the files in the Library have not been tagged with the correct metadata. See the chapter [Preparing media](preparing-media) for tips on tagging content. +> If you see the message **0 files meet the criteria**, it might mean that the files in the Library have not been tagged with the correct metadata. See the chapter [Preparing media](/docs/preparing-media) for tips on tagging content. -![](img/Smartblock-content.png) +![](/img/Smartblock-content.png) If you don't like the ordering which is generated, click the **Shuffle** button, or drag and drop the smart block contents into the order that you prefer. You can also remove items or add new items manually from the Library. Changes to static smart block contents are saved automatically when you add items, remove or re-order them, or click the **Generate** button. Click the **Save** button in the upper right corner to save any changes to smart block criteria. diff --git a/docs/_docs/settings.md b/docs/_docs/settings.md index 5cfb28f0f..a017efc09 100644 --- a/docs/_docs/settings.md +++ b/docs/_docs/settings.md @@ -1,20 +1,20 @@ --- layout: article title: Settings -git: settings.md +category: admin --- ## General Settings -![](img/station-info-settings.png) +![](/img/station-info-settings.png) On the **Settings** menu, click **General** to set your **Station Name**. This text is shown in your web browser's title bar when your station staff are logged into LibreTime, and optionally in stream metadata. You can also set a **Station Description** and **Station Logo** here. -![](img/general-playback-settings.png) +![](/img/general-playback-settings.png) The **Default Interface Language** drop-down menu sets the default localization for your LibreTime instance, and the **Station Timezone** drop-down menu can be @@ -50,12 +50,12 @@ podcast to LibreTime. This can also be done manually by the generate smartblock and playlist button under a podcasts settings which is why it is disabled by default. -![](img/api-settings.png) +![](/img/api-settings.png) You can enable live, read-only access to the LibreTime schedule calendar for your station's public website with the **Public LibreTime API** option, if you wish. (There is more about this feature in the -[*Exporting the schedule*](exporting-the-schedule) chapter, in the +[*Exporting the schedule*](/docs/playout-history) chapter, in the *Advanced Configuration* section of this book). The **Allowed CORS URLs** is intended to deal with situations where you want a @@ -71,7 +71,7 @@ The **Tune-In Settings** section is intended for stations that have partnered with TuneIn to automatically push their now playing metadata to TuneIn. This hasn't been tested and also requires special credentials from TuneIn. -![](img/dangerous-options.png) +![](/img/dangerous-options.png) The **Dangerous Options** section provides an administrator the ability to erase the entire LibreTime library. @@ -79,13 +79,13 @@ the entire LibreTime library. When you are done remember click the **Save** button at the top or bottom of the form. -![](img/save-button.png) +![](/img/save-button.png) Individual LibreTime users can choose another interface localization when they log in, or set personal preferences for localization and time zone by clicking their username on the right side of the menu bar. -![](img/Screenshot475-Edit_own_user_account.png) +![](/img/Screenshot475-Edit_own_user_account.png) ---- @@ -120,7 +120,7 @@ The second checkbox under Global Settings enables the sending of **Icecast Vorbi The **Stream Label** radio button allows you to set the metadata that will be sent with direct streams; *Artist* and *Title*, *Show*, *Artist* and *Title*, or *Station name* and *Show name*. -![](img/Screenshot481-Global_stream_settings.png) +![](/img/Screenshot481-Global_stream_settings.png) The **Off Air Metadata** field configures the text that will be sent to any configured streaming servers, and from there on to media players, when Airtime is not streaming any output. @@ -144,7 +144,7 @@ Input streams must have a **Port** for the remote broadcaster to connect to, whi To avoid further confusion, it is also recommended that you set a different **Mount Point** (the specific filename for broadcasters and listeners to connect to) from that used on your public Icecast or SHOUTcast server. -![](img/Screenshot401-Input_stream_settings.png) +![](/img/Screenshot401-Input_stream_settings.png) If your Airtime server is behind a firewall, and you wish remote broadcasters to connect input streams to it across the public Internet, you may need to click the **Override** link to set a **Connection URL** which is available from outside your local area network. This URL might be based on the domain name assigned to the router which forwards the appropriate port to your Airtime server. Then click **OK** to save the new connection URL. @@ -152,11 +152,11 @@ If your Airtime server is behind a firewall, and you wish remote broadcasters to In the Master Panel, available input source streams are shown with an orange line connecting the source to the switch, which you could think of like a patch cable connecting a source to a broadcast mixer. When that switch is active, another orange line connects the switch to the On Air indicator, like a patch cable connecting a mixer to a transmitter. -![](img/libretime-master-source-stream.png) +![](/img/libretime-master-source-stream.png) If you have checked the **Auto Switch On** box in the Stream Settings page, the **Master Source** switch will move automatically to the active position, on the left, when an authenticated master source connects to Airtime. Otherwise, you can activate the switches manually by clicking the left side of each switch, or deactivate them by clicking the right side. The switches do not have to be dragged with the mouse, in the way that a switch on a hardware mixer would be pushed sideways. **Show Source** live input streams can also be manually activated or deactivated in the same way. If neither the show source or master source is active then LibreTime will default to what is scheduled to play in the Calendar. -![](img/libretime-show-source-stream.png) +![](/img/libretime-show-source-stream.png) If you have the **Auto Switch Off** box checked LibreTime will resume scheduled playback whenever a stream disconnects. Otherwise you will need to slide to disable a source after a DJ stops streaming. @@ -170,37 +170,19 @@ On the right side of the page, you can configure up to three independent output To configure another stream, click the bar with the stream number to expand its box, and make sure **Enabled** is checked. Enter at least the streaming **Server** IP address or domain name, and **Port** details. The default port for Icecast and SHOUTcast servers is 8000. -![](img/Screenshot402-Output_stream_settings.png) +![](/img/Screenshot402-Output_stream_settings.png) Click **Additional Options** to expand a box in which you can enter the usernames, passwords and metadata to send to the streaming server. The default **Username** for Icecast servers is *source*, and if this the name in use on your streaming server, you can leave this field empty. The **Admin User** and **Admin Password** settings are optional, and are used to query the streaming server for audience numbers by the **Listener Stats** page on the **System** menu. You can also set the specific **Mount Point** that listeners will connect to here. Then click one of the **Save** buttons in the upper or lower right corner of the page to update the Airtime server's settings. -![](img/Screenshot482-Additional_options.png) +![](/img/Screenshot482-Additional_options.png) Airtime supports output to Icecast in Ogg Vorbis, Ogg Opus, MP3 and AAC formats. When selecting a SHOUTcast server from the **Service Type** drop-down menu, you are restricted to using MP3 or AAC formats only, so the choice of Ogg Vorbis and Opus formats is greyed out in the **Stream Type** drop-down menu. The SHOUTcast username for stream sources is fixed, so you do not need to enter this value under **Additional Options**, but you will usually have to enter a password. -![](img/Screenshot404-Shoutcast_output_stream.png) +![](/img/Screenshot404-Shoutcast_output_stream.png) Any connection problems between Liquidsoap and Icecast or SHOUTcast are shown on the Stream Settings page. For example, if you enter the wrong password, you will see an *Authentication Required* error message. To fix this, enter the correct password in the **Additional Options** box, and click the **Save** button. If the streaming server is down for any reason, or you have entered an incorrect **Server** name or **Port** number, you will see the message *Can not connect to the streaming server*. -![](img/Screenshot405-Connection_timeout.png) - ------- - - -## The Status page {#status} - -On the **Settings** menu, the **Status** page provides an overview of the health and resource usage of the various -services that make up a LibreTime system. If all is well, you will only see green check mark icons in the -**Status** column. This page also shows how much **Disk Space** you have used on the disk partition containing the media storage folder. - -![](img/Screenshot521-System_status_240.png) - -If any of the check mark icons in the **Status** column have changed to a red warning sign, contact your system -administrator for assistance. (The chapter [Troubleshooting](troubleshooting) contains some tips). LibreTime will -do its best to restart any failing services, but sometimes manual intervention may be required; for example, in the case of hardware failure. - -If you have run out of storage space, a LibreTime user with *admin* privileges could log in and delete media files -that are no longer required from the **Library**. Alternatively, you could ask your system administrator to install additional storage capacity. +![](/img/Screenshot405-Connection_timeout.png) diff --git a/docs/_docs/status.md b/docs/_docs/status.md new file mode 100644 index 000000000..9b35d669b --- /dev/null +++ b/docs/_docs/status.md @@ -0,0 +1,18 @@ +--- +layout: article +title: System Status +category: admin +--- + +On the **Settings** menu, the **Status** page provides an overview of the health and resource usage of the various +services that make up a LibreTime system. If all is well, you will only see green check mark icons in the +**Status** column. This page also shows how much **Disk Space** you have used on the disk partition containing the media storage folder. + +![](/img/Screenshot521-System_status_240.png) + +If any of the check mark icons in the **Status** column have changed to a red warning sign, contact your system +administrator for assistance. (The chapter [Troubleshooting](/docs/troubleshooting) contains some tips). LibreTime will +do its best to restart any failing services, but sometimes manual intervention may be required; for example, in the case of hardware failure. + +If you have run out of storage space, a LibreTime user with *admin* privileges could log in and delete media files +that are no longer required from the **Library**. Alternatively, you could ask your system administrator to install additional storage capacity. diff --git a/docs/faq.html b/docs/faq.html index 4be984cac..3068f897c 100644 --- a/docs/faq.html +++ b/docs/faq.html @@ -34,30 +34,13 @@ layout: default
    -
    -

    Stay up to date for any updates and contributions!

    +

    Stay up to date for any abouts and contributions!

    @@ -74,8 +57,9 @@ layout: default
    -
    +
    +
    What is Docs UI Kit?
    @@ -150,134 +134,14 @@ layout: default
    -
    Docs UI Kit comes with 10+ example pages.
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    Installation Group Item #1
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    Update Group Item #1
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    Gitlab Group Item #1
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    - -
    - -
    -
    - Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. -
    -
    -
    - -
    From 3474714471b0ccae972b8746f5717f8a7bef921e Mon Sep 17 00:00:00 2001 From: Zachary Klosko Date: Tue, 3 Nov 2020 18:08:55 -0500 Subject: [PATCH 08/12] Fixing non visable header image --- docs/_config.yml | 1 - docs/_layouts/splash.html | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/_config.yml b/docs/_config.yml index 6a38ed42f..f63603173 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -10,7 +10,6 @@ includes_dir: _includes favicon: favicon.ico # For splash page layout only -headimage: /img/header.jpg headtext: Open Source Radio Automation headsubtext: Everything you need to get your terrestrial / on-line radio station up and broadcasting like a pro - without breaking the bank! # items below - only for first front page link diff --git a/docs/_layouts/splash.html b/docs/_layouts/splash.html index a9af2876c..c42b8820b 100644 --- a/docs/_layouts/splash.html +++ b/docs/_layouts/splash.html @@ -23,7 +23,7 @@ header.masthead { padding-top: 10rem; padding-bottom: calc(10rem - 56px); - background-image: url("{{ site.headimage }}"); + background-image: url("/img/header.jpg"); background-position: center center; background-size: cover; } From 6dac3e3f079ecfec5e99ff1f542c8ebae9cf6531 Mon Sep 17 00:00:00 2001 From: Zachary Klosko Date: Sat, 7 Nov 2020 22:50:28 -0500 Subject: [PATCH 09/12] Filling out FAQ and small revisions --- docs/_config.yml | 2 +- docs/_docs/api.md | 2 +- docs/_docs/contribute.md | 11 ++++--- docs/_docs/host-configuration.md | 7 ++--- docs/_docs/interface-localization.md | 2 +- docs/_docs/listener-stats.md | 6 ++-- docs/_docs/microsite.md | 7 ++--- docs/_docs/playlists.md | 2 +- docs/_docs/podcasts.md | 2 +- docs/_docs/troubleshooting.md | 43 +--------------------------- docs/_docs/upgrading.md | 2 +- docs/docs.html | 4 +-- docs/faq.html | 14 ++++----- 13 files changed, 31 insertions(+), 73 deletions(-) diff --git a/docs/_config.yml b/docs/_config.yml index f63603173..632b65935 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,7 +1,7 @@ title: LibreTime description: An open source radio automation server made for (and by) low-power FM stations and the rest of us. logo: 144px.png -baseurl: "/libretime" # The subpath of your site, e.g. /blog +baseurl: "/" # The subpath of your site, e.g. /blog url: "https://libretime.org" # The base hostname & protocol for your site, e.g. http://example.com layouts_dir: _layouts diff --git a/docs/_docs/api.md b/docs/_docs/api.md index a72c5798b..c9fe7bbbb 100644 --- a/docs/_docs/api.md +++ b/docs/_docs/api.md @@ -1,7 +1,7 @@ --- layout: article title: LibreTime API Usage -git: api.md +category: dev --- The LibreTime API enables many types of information about the broadcast schedule and configuration to be retrieved from the LibreTime server. Other than the live-info and week-info data fetched by website widgets (see the chapter *Exporting the schedule*), all API requests must be authenticated using the secret API key stored in the file */etc/airtime/api\_client.cfg* on the LibreTime server. This key is autogenerated during LibreTime installation and should be unique for each server. diff --git a/docs/_docs/contribute.md b/docs/_docs/contribute.md index 085964343..8d17af009 100644 --- a/docs/_docs/contribute.md +++ b/docs/_docs/contribute.md @@ -23,21 +23,24 @@ Have an idea that would make LibreTime even better than it is right now? Start a ## Help translate LibreTime LibreTime can run in over 15 different languages due to the gracious help of our volunteers. Is your language not -supported? Follow [this guide](interface-localization) to add your language to LibreTime! +supported? Follow [this guide](/docs/interface-localization) to add your language to LibreTime! ## Help write documentation for LibreTime -Our site is now run by Jekyll. After cloning our repo locally, enter the `/docs` directory and run +Our site is now run by Jekyll, who has an installation guide [here](https://jekyllrb.com/docs/installation/) to help get you started. + After cloning our repo locally, enter the `/docs` directory and run ``` bundle install jekyll serve ``` +If you get an error, try running `bundle exec jekyll serve`. The site should start running on `http://127.0.0.1:4000/`. + ## Help write code for LibreTime Are you familar with coding in PHP? Have you made projects in Liquidsoap and some of the other services we use? -Take a look at the bugs and feature requests [here](https://github.com/LibreTime/libretime/issues), and then +Take a look at the [list of bugs and feature requests](https://github.com/LibreTime/libretime/issues), and then fork our repo and have a go! Just use the **Fork** button at the top of our **Code** page, clone the forked repo to your desktop, open up a favorite editor and make some changes, and then commit, push, and open a pull request. Knowledge on how to use [Github](https://guides.github.com/activities/hello-world/) and [Git](https://git-scm.com/docs/gittutorial) @@ -47,7 +50,7 @@ will suit you well, use the links for a quick 101. Before submitting code to the project, it's a good idea to test it first. To do this, it's easiest to install LibreTime in a virtural machine on your local system or in a cloud VM. Instructions on how to set up a virtural -instance of LibreTime with Vagrant are located [here](vagrant). +instance of LibreTime with Vagrant are located [here](/docs/vagrant). If you would like to try LibreTime in a Docker image, Odclive has instructions [here](https://github.com/kessibi/libretime-docker) for setting up a test image diff --git a/docs/_docs/host-configuration.md b/docs/_docs/host-configuration.md index a2acedb97..b09ab13e9 100644 --- a/docs/_docs/host-configuration.md +++ b/docs/_docs/host-configuration.md @@ -211,15 +211,12 @@ for the changes to take effect. ## Setting the server time -Accurate time keeping on your server is vital for LibreTime performance. You can confirm that the date and time of your server are set correctly with the **date** command: - - date - +Accurate time keeping on your server is vital for LibreTime performance. You can confirm that the date and time of your server are set correctly with the `date` command. The server should respond with the date, time, time zone and year in a format similar to the following example: Tue Jul 2 15:08:57 BST 2013 -If the time on your server is wrong, it is recommended that you take LibreTime off-air until the problem is fixed. +If the time on your server is wrong, it is strongly recommended that you take LibreTime off-air until the problem is fixed. ### Configuring NTP diff --git a/docs/_docs/interface-localization.md b/docs/_docs/interface-localization.md index b3c4764cc..11f0f43fe 100644 --- a/docs/_docs/interface-localization.md +++ b/docs/_docs/interface-localization.md @@ -1,7 +1,7 @@ --- layout: article title: Interface Localization -git: interface-localization.md +category: admin --- The LibreTime administration interface can be localized using the standard GNU **gettext** method. Using GitHub for this task means you don't have to tackle the whole of a localization yourself; just as much as you can manage. diff --git a/docs/_docs/listener-stats.md b/docs/_docs/listener-stats.md index 31ae21d6d..539316e41 100644 --- a/docs/_docs/listener-stats.md +++ b/docs/_docs/listener-stats.md @@ -6,10 +6,10 @@ category: manager The Listener Stats page on the Analytics menu shows graphs of listener connections to the configured streaming servers for the selected date and time range.  On the right side, a green **Status** indicator shows **OK** if the connection to the streaming server is active. -![](img/portfolio/stream-stats.jpg) +![](/img/portfolio/stream-stats.jpg) If the status indicator is red, check that the **Admin User** and **Admin Password** settings are correct under **Additional Options** for the named mount point, such as *libretime\_128*, on the **Streams** page of the **Settings** menu. -By default, statistics for the last 24 hours of streaming are shown. To change this date and time range, click the calendar and clock icons in the lower left corner of the page, then click the magnifying glass icon. +To choose which particular streams should have statistics displayed, click the check boxes for the individual colour-coded mount points, just below the graph. -> To choose which particular streams should have statistics displayed, click the check boxes for the individual colour-coded mount points, just below the graph. +By default, statistics for the last 24 hours of streaming are shown. To change this date and time range, click the calendar and clock icons in the lower left corner of the page, then click the magnifying glass icon. \ No newline at end of file diff --git a/docs/_docs/microsite.md b/docs/_docs/microsite.md index 1cc6035d7..bbe3dcbb8 100644 --- a/docs/_docs/microsite.md +++ b/docs/_docs/microsite.md @@ -6,10 +6,9 @@ category: admin ![](/img/radio-page.png) -LibreTime includes a mini-site, which can be accessed at _serverIP_ (for local installations), or -_libretime.yourdomain.com_ or _cloudvmIP_ (for installations to a server on a domain). The site includes your +LibreTime includes a microsite, which can be accessed at _serverIP_ or a domain you've set up for your server. The site includes your logo and station description (set under Settings > General), the login button to the LibreTime interface, the schedule for the next seven days, -podcast tabs, and a live feed of your station with the currently playing artist and track displayed. +podcast tabs, and a live feed of your station with information on the the currently playing artist and track. ## Modifying the LibreTime Radio Page @@ -26,4 +25,4 @@ html { } ``` -Place the new background image in the */usr/share/airtime/public/css/radio-page/img/* folder and change the `background:` entry's URL to point to the new image. The new image should be at least 1280 x 720 in pixel size to avoid being blurry. +Place the new background image in the `/usr/share/airtime/public/css/radio-page/img/` folder and change the `background:` entry's URL to point to the new image. The new image should be at least 1280 x 720 in pixel size to avoid being blurry. diff --git a/docs/_docs/playlists.md b/docs/_docs/playlists.md index 584eae882..d93ca3611 100644 --- a/docs/_docs/playlists.md +++ b/docs/_docs/playlists.md @@ -44,7 +44,7 @@ Fill out the smart block's **Name**, **Search Criteria**, and **Limit to** secti If you have a large number of files which meet the criteria that you specify, you may wish to limit the duration of the smart block using the **Limit to** field, so that it fits within the show you have in mind. Select **hours**, **minutes** or **items** from the drop-down menu, and click the **Generate** button again, if it is a static smart block. Then click the **Save** button. -> Note: Smart Blocks by default will not overflow the length of a scheduled show. +> **Note:** Smart Blocks by default will not overflow the length of a scheduled show. > This is to prevent tracks from being cut-off because they exceed the time limit of a show. > If you want a smartblock to schedule tracks until it is longer than the Time Limit you can check **"Allow last track to exceed time limit"** > (helpful for avoiding dead air on autoscheduled shows). diff --git a/docs/_docs/podcasts.md b/docs/_docs/podcasts.md index 8da09ffc2..9cfe8ca8c 100644 --- a/docs/_docs/podcasts.md +++ b/docs/_docs/podcasts.md @@ -8,7 +8,7 @@ The Podcasts page allows you add subscriptions to podcasts which are often used In order to add a podcast you need to get the RSS feed. All podcasts available on iTunes have a RSS feed but it is sometimes hidden. See this issue on our github page [#510](https://github.com/LibreTime/libretime/issues/510) for more information. RSS feeds that do not end in *.xml* may be accepted by LibreTime but might fail to download episodes; in that case, download the episode using a podcast client such as [gpodder](https://gpodder.github.io/) and then manually upload and schedule the episode. Podcast feeds coming from Anchor.fm have been known to have this issue. -The podcast interfaces provides you with the ability to generate [Smartblocks](library#smartblocks) that can be used in conjunction with autoloading playlists to schedule the newest episode of a podcast without human intervention. +The podcast interfaces provides you with the ability to generate [Smartblocks](/docs/playlists) that can be used in conjunction with autoloading playlists to schedule the newest episode of a podcast without human intervention. diff --git a/docs/_docs/troubleshooting.md b/docs/_docs/troubleshooting.md index e5212c3a2..c57a1b8c3 100644 --- a/docs/_docs/troubleshooting.md +++ b/docs/_docs/troubleshooting.md @@ -4,29 +4,6 @@ title: Troubleshooting git: troubleshooting.md --- -## Restarting services - -LibreTime is effectively a web site running on a LAPP stack, so individual components of the system can be started, stopped, restarted or checked in the server console using the **systemctl** command: - -``` -sudo systemctl start|stop|restart|status libretime-liquidsoap -sudo systemctl start|stop|restart|status libretime-playout -sudo systemctl start|stop|restart|status libretime-celery -sudo systemctl start|stop|restart|status libretime-analyzer -sudo systemctl start|stop|restart|status apache2 -sudo systemctl start|stop|restart|status rabbitmq-server -``` - -For example, to restart the Airtime playout engine, you could enter the command: - -``` -sudo systemctl restart libretime-playout -``` - -## Log files {#logs} - -Airtime stores log files under the directory path */var/log/airtime/* which can be useful for diagnosing the cause of any problems. Copies of these log files may be requested by LibreTime developers while they are providing technical support for your Airtime deployment. - ## Test tones {#tones} If you need to test your computer's soundcard, you can use `speaker-test`, a tone generator for ALSA. @@ -89,22 +66,4 @@ rabbitmqctl set_permissions -p /airtime airtime "airtime-pypo|pypo-fetch|airtime-analyzer|media-monitor"   "airtime-pypo|pypo-fetch|airtime-analyzer|media-monitor"  "airtime-pypo|pypo-fetch|airtime-analyzer|media-monitor" -``` - -## Uninstall LibreTime {#uninstall} - -Hopefully it wasn't something that we did, but if you need to uninstall LibreTime for -any reason, cd to the directory of the installer and run -``` -sudo ./uninstall -``` - -If allowed, the installer will **permanently** delete all databases and media uploaded to -LibreTime. - -If it was something we did, please open an issue request on our Github page, located -[here](https://github.com/LibreTime/libretime/issues). - -Until we meet again, best of luck. - -<3 The LibreTime team \ No newline at end of file +``` \ No newline at end of file diff --git a/docs/_docs/upgrading.md b/docs/_docs/upgrading.md index 46772b4e7..691d7254b 100644 --- a/docs/_docs/upgrading.md +++ b/docs/_docs/upgrading.md @@ -1,7 +1,7 @@ --- layout: article title: Upgrading LibreTime -git: upgrading.md +category: admin --- ## LibreTime versioning diff --git a/docs/docs.html b/docs/docs.html index 10f2bd1f9..d086511dc 100644 --- a/docs/docs.html +++ b/docs/docs.html @@ -81,7 +81,7 @@ layout: default

    Can't find what you're looking for?

    -

    Let us help you!

    +

    Ask for help on our Github repo!

    @@ -129,7 +129,7 @@ layout: default diff --git a/docs/faq.html b/docs/faq.html index 3068f897c..69be788c8 100644 --- a/docs/faq.html +++ b/docs/faq.html @@ -40,7 +40,7 @@ layout: default
    -

    Stay up to date for any abouts and contributions!

    +

    Stay up to date with updates and contributions!

    @@ -73,21 +73,21 @@ layout: default
    -
    Yes! It's just absolutely FREE and comes with a simple license! If you have any questions or doubts, please feel free to reach us here.
    +
    Airtime stores log files under the directory path /var/log/airtime/. Copies of these log files may be requested by LibreTime developers while they are providing technical support for your Libretime deployment.
    -
    Yes! Docs UI Kit is absolutely Free and you can use in both personal and commercial projects without any attribution. However, we would much appreciate for any attribution.
    +
    On Ubuntu, LibreTime services are controlled by the system service daemon systemctl. To restart a service, use the command sudo systemctl restart servicename. Some of Libretime's services include libretime-liquidsoap, libretime-playout, libretime-celery, libretime-analyzer, apache2, and rabbitmq-server.
    @@ -138,8 +138,8 @@ layout: default
    -
    Hopefully it wasn't something that we did, but if you need to uninstall LibreTime for any reason, cd to the directory of the installer and run sudo ./uninstall. - If allowed, the installer will **permanently** delete all databases and media uploaded to LibreTime. +
    Hopefully it wasn't something that we did, but if you need to uninstall LibreTime for any reason, cd to the directory of the installer and run sudo bash uninstall. + If allowed, the installer will permanently delete all databases and media uploaded to LibreTime.

    If it was something we did, please open an issue request on our Github.

    From a5c67887a4c10fc9995ddf5ea6931a49a0290f68 Mon Sep 17 00:00:00 2001 From: Zachary Klosko Date: Mon, 9 Nov 2020 23:19:28 -0500 Subject: [PATCH 10/12] Assorted work --- docs/404.html | 15 +++++++++++++-- docs/_config.yml | 2 +- docs/_docs/backing-up-the-server.md | 3 +-- docs/_docs/station-setup.md | 17 +++++++++++++++++ docs/_docs/widgets.md | 7 +++++++ docs/_layouts/404.html | 16 ---------------- docs/docs.html | 8 ++++---- docs/faq.html | 4 ++-- install | 2 +- 9 files changed, 46 insertions(+), 28 deletions(-) create mode 100644 docs/_docs/station-setup.md create mode 100644 docs/_docs/widgets.md delete mode 100644 docs/_layouts/404.html diff --git a/docs/404.html b/docs/404.html index 81dc7a640..f88978408 100644 --- a/docs/404.html +++ b/docs/404.html @@ -1,4 +1,15 @@ --- permalink: /404.html -layout: 404 ---- \ No newline at end of file +layout: default +--- + +
    +
    +

    Page not found :(

    +

    The requested page could not be found.

    + + + Back to Docs + +
    +
    \ No newline at end of file diff --git a/docs/_config.yml b/docs/_config.yml index 632b65935..fa1d1f3fb 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,7 +1,7 @@ title: LibreTime description: An open source radio automation server made for (and by) low-power FM stations and the rest of us. logo: 144px.png -baseurl: "/" # The subpath of your site, e.g. /blog +baseurl: "zklosko.github.io/libretime/" # The subpath of your site, e.g. /blog url: "https://libretime.org" # The base hostname & protocol for your site, e.g. http://example.com layouts_dir: _layouts diff --git a/docs/_docs/backing-up-the-server.md b/docs/_docs/backing-up-the-server.md index 9228d5eb8..311878148 100644 --- a/docs/_docs/backing-up-the-server.md +++ b/docs/_docs/backing-up-the-server.md @@ -1,8 +1,7 @@ --- layout: article -title: Backing Up The Server +title: Backing Up Libretime category: admin -git: backing-up-the-server.md --- ## Database Backup diff --git a/docs/_docs/station-setup.md b/docs/_docs/station-setup.md new file mode 100644 index 000000000..2376031d8 --- /dev/null +++ b/docs/_docs/station-setup.md @@ -0,0 +1,17 @@ +--- +layout: article +title: Libretime for Terrestrial Broadcasters +category: manager +--- + +## How to + +### 1. Prepare your studio +### 2. Install Ubuntu 18.04 LTS +### 3. Install Libretime +### 4. Configure soundcard + +### 5. Set up SSH tunneling (optional) + +SSH tunneling is similar to using a VPN but with the need to manually connect to individual computers +and ports instead of gaining access to the entire network. \ No newline at end of file diff --git a/docs/_docs/widgets.md b/docs/_docs/widgets.md new file mode 100644 index 000000000..871f94c6b --- /dev/null +++ b/docs/_docs/widgets.md @@ -0,0 +1,7 @@ +--- +title: Widgets +category: interface +layout: article +--- + + \ No newline at end of file diff --git a/docs/_layouts/404.html b/docs/_layouts/404.html deleted file mode 100644 index 4b8865762..000000000 --- a/docs/_layouts/404.html +++ /dev/null @@ -1,16 +0,0 @@ ---- -layout: default ---- - -
    -
    - 404 Tape -

    Page not found :(

    -

    The requested page could not be found.

    - - - Back to Docs - -
    -
    - diff --git a/docs/docs.html b/docs/docs.html index d086511dc..6cd777328 100644 --- a/docs/docs.html +++ b/docs/docs.html @@ -26,7 +26,7 @@ layout: default

    Configure

    -

    We strive to embrace and drive change in our industry.

    +

    Prepare Libretime for primetime.

    @@ -36,7 +36,7 @@ layout: default

    Schedule Shows

    -

    We strive to embrace and drive change in our industry.

    +

    Putting in your first show? Start here.

    @@ -55,8 +55,8 @@ layout: default
    -

    Libretime for FM Radio

    -

    We strive to embrace and drive change in our industry.

    +

    Libretime for AM/FM Radio

    +

    Libretime isn't just for internet radio; use it to supercharge your terrestrial radio station too!

    diff --git a/docs/faq.html b/docs/faq.html index 69be788c8..bace1e2f7 100644 --- a/docs/faq.html +++ b/docs/faq.html @@ -93,12 +93,12 @@ layout: default
    -
    Where can I download Docs UI Kit?
    +
    Is LibreTime free?
    -
    You can download Docs UI Kit on Htmlstream website or on the Github page.
    +
    Absolutely, but "free as in freedom" free. While there is no cost to use the Libretime software at your radio station or in the cloud, please consider supporting the project financially or with your many talents. Our supporters allow the Libretime project to keep growing and improving.
    diff --git a/install b/install index ae2b8a870..0780f318b 100755 --- a/install +++ b/install @@ -1133,7 +1133,7 @@ fi if [ ! -d "/etc/airtime" ]; then loud "\n-----------------------------------------------------" - loud " * Installing Airtime * " + loud " * Installing Libretime * " loud "-----------------------------------------------------" verbose "\n * Creating /etc/airtime/ directory..." From 7d465c329c69822f9bcc29fefb0ab1c47fa91e62 Mon Sep 17 00:00:00 2001 From: Zachary Klosko Date: Wed, 11 Nov 2020 22:41:53 -0500 Subject: [PATCH 11/12] Attempting embed of Algolia Search --- docs/_config.yml | 2 +- docs/_docs/contribute.md | 8 +++--- docs/_docs/host-configuration.md | 4 +-- docs/_docs/multipass.md | 43 ++++++++++++++++++++++++++++++++ docs/_docs/station-setup.md | 20 ++++++++++++++- docs/_includes/footer.html | 2 +- docs/_includes/head.html | 5 ++++ docs/_includes/navbar.html | 5 ++++ docs/_includes/scripts.html | 35 +++++++++++++++++++++++++- multipass.yml | 20 +++++++++++++++ 10 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 docs/_docs/multipass.md create mode 100644 multipass.yml diff --git a/docs/_config.yml b/docs/_config.yml index fa1d1f3fb..9a5377186 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,7 +1,7 @@ title: LibreTime description: An open source radio automation server made for (and by) low-power FM stations and the rest of us. logo: 144px.png -baseurl: "zklosko.github.io/libretime/" # The subpath of your site, e.g. /blog +baseurl: "" # The subpath of your site, e.g. /blog url: "https://libretime.org" # The base hostname & protocol for your site, e.g. http://example.com layouts_dir: _layouts diff --git a/docs/_docs/contribute.md b/docs/_docs/contribute.md index 8d17af009..908f0b80a 100644 --- a/docs/_docs/contribute.md +++ b/docs/_docs/contribute.md @@ -46,17 +46,17 @@ your desktop, open up a favorite editor and make some changes, and then commit, Knowledge on how to use [Github](https://guides.github.com/activities/hello-world/) and [Git](https://git-scm.com/docs/gittutorial) will suit you well, use the links for a quick 101. -### Testing LibreTime in Vagrant +## Testing LibreTime Before submitting code to the project, it's a good idea to test it first. To do this, it's easiest to install -LibreTime in a virtural machine on your local system or in a cloud VM. Instructions on how to set up a virtural -instance of LibreTime with Vagrant are located [here](/docs/vagrant). +LibreTime in a virtural machine on your local system or in a cloud VM. We have instructions for setting up a virtural +instance of LibreTime with [Vagrant](/docs/vagrant) and [Multipass](/docs/multipass). If you would like to try LibreTime in a Docker image, Odclive has instructions [here](https://github.com/kessibi/libretime-docker) for setting up a test image and a more persistant install. -### Modifying the Database +## Modifying the Database LibreTime is designed to work with a [PostgreSQL](https://www.postgresql.org/) database server running locally. LibreTime uses [PropelORM](http://propelorm.org) to interact with the ZendPHP components and create the database. diff --git a/docs/_docs/host-configuration.md b/docs/_docs/host-configuration.md index b09ab13e9..0889f93f8 100644 --- a/docs/_docs/host-configuration.md +++ b/docs/_docs/host-configuration.md @@ -267,11 +267,11 @@ If the server time zone is not appropriate for integration with your station's o This command opens a menu in which you can select the continent that you require, by pressing the Enter key. -![](img/Screenshot15-Configuring_tzdata.png) +![](/img/Screenshot15-Configuring_tzdata.png) The next step is to select your nearest city, again by pressing the Enter key. The appropriate time zone is selected according to the information that you have entered. -![](img/Screenshot16-Configure_city.png) +![](/img/Screenshot16-Configure_city.png) The console output from the **dpkg-reconfigure tzdata** command will confirm the new setting: diff --git a/docs/_docs/multipass.md b/docs/_docs/multipass.md new file mode 100644 index 000000000..fe2b9077d --- /dev/null +++ b/docs/_docs/multipass.md @@ -0,0 +1,43 @@ +--- +title: Multipass +layout: article +category: dev +--- + +[Multipass](https://multipass.run) is a tool for easily setting up Ubuntu VMs on Windows, Mac, and Linux. +Similar to Docker, Multipass works through a CLI. To use, clone this repo and then open a Terminal (or Command Prompt) inside the created folder and run + +``` +multipass launch bionic -n ltTEST --cloud-init multipass.yaml +multipass shell ltTEST +``` + +Multipass isn't currently able to do an automated install from the cloud-init script. +After you enter the shell for the first time, you will still need to run the install script for LibreTime. + +``` +sudo ./libretime/install -fiap +``` + +The IP address of your new VM can be found by running `multipass list`. Copy and paste it into your web browser to access the LibreTime interface and complete the setup wizard. + +You can stop the VM with `multipass stop ltTEST` and restart with `multipass start ltTEST`. +If you want to delete the image and start again, run `multipass delete ltTEST && multipass purge`. + +--- +### Cloud-init options in libretimeTest.yaml + +You may wish to change the below fields as per your location. +``` +timezone: America/New York # change as needed +ntp: + pools: ['north-america.pool.ntp.org'] + servers: ['0.north-america.pool.ntp.org', '0.pool.ntp.org'] +``` + +If you are running your forked repo of LibreTime for testing purposes, +modify the URL on this line: + +``` +- cd / && git clone https://github.com/LibreTime/libretime.git +``` \ No newline at end of file diff --git a/docs/_docs/station-setup.md b/docs/_docs/station-setup.md index 2376031d8..8df401914 100644 --- a/docs/_docs/station-setup.md +++ b/docs/_docs/station-setup.md @@ -7,8 +7,26 @@ category: manager ## How to ### 1. Prepare your studio -### 2. Install Ubuntu 18.04 LTS + +The server or desktop you plan to run Libretime on should have a built-in soundcard +and ethernet port. A wired approach is strongly recommended over a wireless one. + +### 2. Install Ubuntu Server 18.04 LTS + +Download Ubuntu Server [here](https://ubuntu.com/download/server) (look under Option 3). +A standard install is recommended, on a RAID 1 array if possible (not required, but recommended). + +Installation checklist: + +- Set correct timezone +- Sync system with national time servers +- Open firewall ports 80 and 8000 +- Enable the SSH server for easier remote access (optional) + ### 3. Install Libretime + +See the [install page](/install). + ### 4. Configure soundcard ### 5. Set up SSH tunneling (optional) diff --git a/docs/_includes/footer.html b/docs/_includes/footer.html index 990f05361..334834bda 100644 --- a/docs/_includes/footer.html +++ b/docs/_includes/footer.html @@ -2,7 +2,7 @@
    - Docs UI Kit + Libretime

    LibreTime is an open source radio automation and broadcasting solution helping communities broadcast with ease.

    diff --git a/docs/_includes/head.html b/docs/_includes/head.html index 2a04fecbb..28c038fb2 100644 --- a/docs/_includes/head.html +++ b/docs/_includes/head.html @@ -12,6 +12,11 @@ + + + + + diff --git a/docs/_includes/navbar.html b/docs/_includes/navbar.html index 380f446f3..08b15514c 100644 --- a/docs/_includes/navbar.html +++ b/docs/_includes/navbar.html @@ -16,6 +16,11 @@ +
    diff --git a/docs/_includes/scripts.html b/docs/_includes/scripts.html index 325e79025..f5e1c878c 100644 --- a/docs/_includes/scripts.html +++ b/docs/_includes/scripts.html @@ -40,4 +40,37 @@ e.preventDefault(); scrollToTop(); } - \ No newline at end of file + + + + + + \ No newline at end of file diff --git a/multipass.yml b/multipass.yml new file mode 100644 index 000000000..99286dd35 --- /dev/null +++ b/multipass.yml @@ -0,0 +1,20 @@ +# Maintainer: Zachary Klosko (kloskoz@vcu.edu) + +hostname: libretimeTest +timezone: America/New York # change as needed +ntp: + pools: ['north-america.pool.ntp.org'] + servers: ['0.north-america.pool.ntp.org', '0.pool.ntp.org'] + +password: hackme +chpasswd: { expire: False } + +packages: + - git +apt_update: true +apt_upgrade: true + +# Clone repo on init (Change repo url if needed) +# If you need to clone a branch, use git clone --branch branchname repourl +runcmd: + - git clone https://github.com/LibreTime/libretime.git \ No newline at end of file From b9fece5d7a1eef8dcaa275b2cd24a89ab86c3b43 Mon Sep 17 00:00:00 2001 From: Zachary Klosko Date: Thu, 12 Nov 2020 16:54:33 -0500 Subject: [PATCH 12/12] Added Lunr.js site search, fixed errors with jekyll serve --- docs/Gemfile.lock | 14 +- docs/_includes/navbar.html | 4 +- docs/_includes/search-lunr.html | 71 + docs/docs.html | 1 + docs/faq.html | 1 + docs/lunr.js | 2977 +++++++++++++++++++++++++++++++ docs/search.html | 17 + 7 files changed, 3075 insertions(+), 10 deletions(-) create mode 100644 docs/_includes/search-lunr.html create mode 100644 docs/lunr.js create mode 100644 docs/search.html diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 36c00c0f1..105186ce6 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -4,17 +4,17 @@ GEM addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) colorator (1.1.0) - concurrent-ruby (1.1.6) - em-websocket (0.5.1) + concurrent-ruby (1.1.7) + em-websocket (0.5.2) eventmachine (>= 0.12.9) http_parser.rb (~> 0.6.0) eventmachine (1.2.7) ffi (1.13.1) forwardable-extended (2.6.0) http_parser.rb (0.6.0) - i18n (1.8.3) + i18n (1.8.5) concurrent-ruby (~> 1.0) - jekyll (4.1.0) + jekyll (4.1.1) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) @@ -33,7 +33,7 @@ GEM sassc (> 2.0.1, < 3.0) jekyll-watch (2.2.1) listen (~> 3.0) - kramdown (2.2.1) + kramdown (2.3.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) @@ -44,12 +44,12 @@ GEM mercenary (0.4.0) pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (4.0.5) + public_suffix (4.0.6) rb-fsevent (0.10.4) rb-inotify (0.10.1) ffi (~> 1.0) rexml (3.2.4) - rouge (3.20.0) + rouge (3.24.0) safe_yaml (1.0.5) sassc (2.4.0) ffi (~> 1.9) diff --git a/docs/_includes/navbar.html b/docs/_includes/navbar.html index 08b15514c..2a434310b 100644 --- a/docs/_includes/navbar.html +++ b/docs/_includes/navbar.html @@ -17,9 +17,7 @@ Star on Github
    diff --git a/docs/_includes/search-lunr.html b/docs/_includes/search-lunr.html new file mode 100644 index 000000000..54f79c453 --- /dev/null +++ b/docs/_includes/search-lunr.html @@ -0,0 +1,71 @@ + + + + + + +
    +

    +
    +
    +
      +
      \ No newline at end of file diff --git a/docs/docs.html b/docs/docs.html index 6cd777328..dc36a0410 100644 --- a/docs/docs.html +++ b/docs/docs.html @@ -1,5 +1,6 @@ --- layout: default +title: Docs ---
      diff --git a/docs/faq.html b/docs/faq.html index bace1e2f7..601ca103a 100644 --- a/docs/faq.html +++ b/docs/faq.html @@ -1,5 +1,6 @@ --- layout: default +title: FAQ --- diff --git a/docs/lunr.js b/docs/lunr.js new file mode 100644 index 000000000..f208eba5a --- /dev/null +++ b/docs/lunr.js @@ -0,0 +1,2977 @@ +/** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.1.5 + * Copyright (C) 2017 Oliver Nightingale + * @license MIT + */ + +;(function(){ + +/** + * A convenience function for configuring and constructing + * a new lunr Index. + * + * A lunr.Builder instance is created and the pipeline setup + * with a trimmer, stop word filter and stemmer. + * + * This builder object is yielded to the configuration function + * that is passed as a parameter, allowing the list of fields + * and other builder parameters to be customised. + * + * All documents _must_ be added within the passed config function. + * + * @example + * var idx = lunr(function () { + * this.field('title') + * this.field('body') + * this.ref('id') + * + * documents.forEach(function (doc) { + * this.add(doc) + * }, this) + * }) + * + * @see {@link lunr.Builder} + * @see {@link lunr.Pipeline} + * @see {@link lunr.trimmer} + * @see {@link lunr.stopWordFilter} + * @see {@link lunr.stemmer} + * @namespace {function} lunr + */ +var lunr = function (config) { + var builder = new lunr.Builder + + builder.pipeline.add( + lunr.trimmer, + lunr.stopWordFilter, + lunr.stemmer + ) + + builder.searchPipeline.add( + lunr.stemmer + ) + + config.call(builder, builder) + return builder.build() +} + +lunr.version = "2.1.5" +/*! + * lunr.utils + * Copyright (C) 2017 Oliver Nightingale + */ + +/** + * A namespace containing utils for the rest of the lunr library + */ +lunr.utils = {} + +/** + * Print a warning message to the console. + * + * @param {String} message The message to be printed. + * @memberOf Utils + */ +lunr.utils.warn = (function (global) { + /* eslint-disable no-console */ + return function (message) { + if (global.console && console.warn) { + console.warn(message) + } + } + /* eslint-enable no-console */ +})(this) + +/** + * Convert an object to a string. + * + * In the case of `null` and `undefined` the function returns + * the empty string, in all other cases the result of calling + * `toString` on the passed object is returned. + * + * @param {Any} obj The object to convert to a string. + * @return {String} string representation of the passed object. + * @memberOf Utils + */ +lunr.utils.asString = function (obj) { + if (obj === void 0 || obj === null) { + return "" + } else { + return obj.toString() + } +} +lunr.FieldRef = function (docRef, fieldName, stringValue) { + this.docRef = docRef + this.fieldName = fieldName + this._stringValue = stringValue +} + +lunr.FieldRef.joiner = "/" + +lunr.FieldRef.fromString = function (s) { + var n = s.indexOf(lunr.FieldRef.joiner) + + if (n === -1) { + throw "malformed field ref string" + } + + var fieldRef = s.slice(0, n), + docRef = s.slice(n + 1) + + return new lunr.FieldRef (docRef, fieldRef, s) +} + +lunr.FieldRef.prototype.toString = function () { + if (this._stringValue == undefined) { + this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef + } + + return this._stringValue +} +/** + * A function to calculate the inverse document frequency for + * a posting. This is shared between the builder and the index + * + * @private + * @param {object} posting - The posting for a given term + * @param {number} documentCount - The total number of documents. + */ +lunr.idf = function (posting, documentCount) { + var documentsWithTerm = 0 + + for (var fieldName in posting) { + if (fieldName == '_index') continue // Ignore the term index, its not a field + documentsWithTerm += Object.keys(posting[fieldName]).length + } + + var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5) + + return Math.log(1 + Math.abs(x)) +} + +/** + * A token wraps a string representation of a token + * as it is passed through the text processing pipeline. + * + * @constructor + * @param {string} [str=''] - The string token being wrapped. + * @param {object} [metadata={}] - Metadata associated with this token. + */ +lunr.Token = function (str, metadata) { + this.str = str || "" + this.metadata = metadata || {} +} + +/** + * Returns the token string that is being wrapped by this object. + * + * @returns {string} + */ +lunr.Token.prototype.toString = function () { + return this.str +} + +/** + * A token update function is used when updating or optionally + * when cloning a token. + * + * @callback lunr.Token~updateFunction + * @param {string} str - The string representation of the token. + * @param {Object} metadata - All metadata associated with this token. + */ + +/** + * Applies the given function to the wrapped string token. + * + * @example + * token.update(function (str, metadata) { + * return str.toUpperCase() + * }) + * + * @param {lunr.Token~updateFunction} fn - A function to apply to the token string. + * @returns {lunr.Token} + */ +lunr.Token.prototype.update = function (fn) { + this.str = fn(this.str, this.metadata) + return this +} + +/** + * Creates a clone of this token. Optionally a function can be + * applied to the cloned token. + * + * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token. + * @returns {lunr.Token} + */ +lunr.Token.prototype.clone = function (fn) { + fn = fn || function (s) { return s } + return new lunr.Token (fn(this.str, this.metadata), this.metadata) +} +/*! + * lunr.tokenizer + * Copyright (C) 2017 Oliver Nightingale + */ + +/** + * A function for splitting a string into tokens ready to be inserted into + * the search index. Uses `lunr.tokenizer.separator` to split strings, change + * the value of this property to change how strings are split into tokens. + * + * This tokenizer will convert its parameter to a string by calling `toString` and + * then will split this string on the character in `lunr.tokenizer.separator`. + * Arrays will have their elements converted to strings and wrapped in a lunr.Token. + * + * @static + * @param {?(string|object|object[])} obj - The object to convert into tokens + * @returns {lunr.Token[]} + */ +lunr.tokenizer = function (obj) { + if (obj == null || obj == undefined) { + return [] + } + + if (Array.isArray(obj)) { + return obj.map(function (t) { + return new lunr.Token(lunr.utils.asString(t).toLowerCase()) + }) + } + + var str = obj.toString().trim().toLowerCase(), + len = str.length, + tokens = [] + + for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) { + var char = str.charAt(sliceEnd), + sliceLength = sliceEnd - sliceStart + + if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) { + + if (sliceLength > 0) { + tokens.push( + new lunr.Token (str.slice(sliceStart, sliceEnd), { + position: [sliceStart, sliceLength], + index: tokens.length + }) + ) + } + + sliceStart = sliceEnd + 1 + } + + } + + return tokens +} + +/** + * The separator used to split a string into tokens. Override this property to change the behaviour of + * `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens. + * + * @static + * @see lunr.tokenizer + */ +lunr.tokenizer.separator = /[\s\-]+/ +/*! + * lunr.Pipeline + * Copyright (C) 2017 Oliver Nightingale + */ + +/** + * lunr.Pipelines maintain an ordered list of functions to be applied to all + * tokens in documents entering the search index and queries being ran against + * the index. + * + * An instance of lunr.Index created with the lunr shortcut will contain a + * pipeline with a stop word filter and an English language stemmer. Extra + * functions can be added before or after either of these functions or these + * default functions can be removed. + * + * When run the pipeline will call each function in turn, passing a token, the + * index of that token in the original list of all tokens and finally a list of + * all the original tokens. + * + * The output of functions in the pipeline will be passed to the next function + * in the pipeline. To exclude a token from entering the index the function + * should return undefined, the rest of the pipeline will not be called with + * this token. + * + * For serialisation of pipelines to work, all functions used in an instance of + * a pipeline should be registered with lunr.Pipeline. Registered functions can + * then be loaded. If trying to load a serialised pipeline that uses functions + * that are not registered an error will be thrown. + * + * If not planning on serialising the pipeline then registering pipeline functions + * is not necessary. + * + * @constructor + */ +lunr.Pipeline = function () { + this._stack = [] +} + +lunr.Pipeline.registeredFunctions = Object.create(null) + +/** + * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token + * string as well as all known metadata. A pipeline function can mutate the token string + * or mutate (or add) metadata for a given token. + * + * A pipeline function can indicate that the passed token should be discarded by returning + * null. This token will not be passed to any downstream pipeline functions and will not be + * added to the index. + * + * Multiple tokens can be returned by returning an array of tokens. Each token will be passed + * to any downstream pipeline functions and all will returned tokens will be added to the index. + * + * Any number of pipeline functions may be chained together using a lunr.Pipeline. + * + * @interface lunr.PipelineFunction + * @param {lunr.Token} token - A token from the document being processed. + * @param {number} i - The index of this token in the complete list of tokens for this document/field. + * @param {lunr.Token[]} tokens - All tokens for this document/field. + * @returns {(?lunr.Token|lunr.Token[])} + */ + +/** + * Register a function with the pipeline. + * + * Functions that are used in the pipeline should be registered if the pipeline + * needs to be serialised, or a serialised pipeline needs to be loaded. + * + * Registering a function does not add it to a pipeline, functions must still be + * added to instances of the pipeline for them to be used when running a pipeline. + * + * @param {lunr.PipelineFunction} fn - The function to check for. + * @param {String} label - The label to register this function with + */ +lunr.Pipeline.registerFunction = function (fn, label) { + if (label in this.registeredFunctions) { + lunr.utils.warn('Overwriting existing registered function: ' + label) + } + + fn.label = label + lunr.Pipeline.registeredFunctions[fn.label] = fn +} + +/** + * Warns if the function is not registered as a Pipeline function. + * + * @param {lunr.PipelineFunction} fn - The function to check for. + * @private + */ +lunr.Pipeline.warnIfFunctionNotRegistered = function (fn) { + var isRegistered = fn.label && (fn.label in this.registeredFunctions) + + if (!isRegistered) { + lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn) + } +} + +/** + * Loads a previously serialised pipeline. + * + * All functions to be loaded must already be registered with lunr.Pipeline. + * If any function from the serialised data has not been registered then an + * error will be thrown. + * + * @param {Object} serialised - The serialised pipeline to load. + * @returns {lunr.Pipeline} + */ +lunr.Pipeline.load = function (serialised) { + var pipeline = new lunr.Pipeline + + serialised.forEach(function (fnName) { + var fn = lunr.Pipeline.registeredFunctions[fnName] + + if (fn) { + pipeline.add(fn) + } else { + throw new Error('Cannot load unregistered function: ' + fnName) + } + }) + + return pipeline +} + +/** + * Adds new functions to the end of the pipeline. + * + * Logs a warning if the function has not been registered. + * + * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline. + */ +lunr.Pipeline.prototype.add = function () { + var fns = Array.prototype.slice.call(arguments) + + fns.forEach(function (fn) { + lunr.Pipeline.warnIfFunctionNotRegistered(fn) + this._stack.push(fn) + }, this) +} + +/** + * Adds a single function after a function that already exists in the + * pipeline. + * + * Logs a warning if the function has not been registered. + * + * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline. + * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline. + */ +lunr.Pipeline.prototype.after = function (existingFn, newFn) { + lunr.Pipeline.warnIfFunctionNotRegistered(newFn) + + var pos = this._stack.indexOf(existingFn) + if (pos == -1) { + throw new Error('Cannot find existingFn') + } + + pos = pos + 1 + this._stack.splice(pos, 0, newFn) +} + +/** + * Adds a single function before a function that already exists in the + * pipeline. + * + * Logs a warning if the function has not been registered. + * + * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline. + * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline. + */ +lunr.Pipeline.prototype.before = function (existingFn, newFn) { + lunr.Pipeline.warnIfFunctionNotRegistered(newFn) + + var pos = this._stack.indexOf(existingFn) + if (pos == -1) { + throw new Error('Cannot find existingFn') + } + + this._stack.splice(pos, 0, newFn) +} + +/** + * Removes a function from the pipeline. + * + * @param {lunr.PipelineFunction} fn The function to remove from the pipeline. + */ +lunr.Pipeline.prototype.remove = function (fn) { + var pos = this._stack.indexOf(fn) + if (pos == -1) { + return + } + + this._stack.splice(pos, 1) +} + +/** + * Runs the current list of functions that make up the pipeline against the + * passed tokens. + * + * @param {Array} tokens The tokens to run through the pipeline. + * @returns {Array} + */ +lunr.Pipeline.prototype.run = function (tokens) { + var stackLength = this._stack.length + + for (var i = 0; i < stackLength; i++) { + var fn = this._stack[i] + + tokens = tokens.reduce(function (memo, token, j) { + var result = fn(token, j, tokens) + + if (result === void 0 || result === '') return memo + + return memo.concat(result) + }, []) + } + + return tokens +} + +/** + * Convenience method for passing a string through a pipeline and getting + * strings out. This method takes care of wrapping the passed string in a + * token and mapping the resulting tokens back to strings. + * + * @param {string} str - The string to pass through the pipeline. + * @returns {string[]} + */ +lunr.Pipeline.prototype.runString = function (str) { + var token = new lunr.Token (str) + + return this.run([token]).map(function (t) { + return t.toString() + }) +} + +/** + * Resets the pipeline by removing any existing processors. + * + */ +lunr.Pipeline.prototype.reset = function () { + this._stack = [] +} + +/** + * Returns a representation of the pipeline ready for serialisation. + * + * Logs a warning if the function has not been registered. + * + * @returns {Array} + */ +lunr.Pipeline.prototype.toJSON = function () { + return this._stack.map(function (fn) { + lunr.Pipeline.warnIfFunctionNotRegistered(fn) + + return fn.label + }) +} +/*! + * lunr.Vector + * Copyright (C) 2017 Oliver Nightingale + */ + +/** + * A vector is used to construct the vector space of documents and queries. These + * vectors support operations to determine the similarity between two documents or + * a document and a query. + * + * Normally no parameters are required for initializing a vector, but in the case of + * loading a previously dumped vector the raw elements can be provided to the constructor. + * + * For performance reasons vectors are implemented with a flat array, where an elements + * index is immediately followed by its value. E.g. [index, value, index, value]. This + * allows the underlying array to be as sparse as possible and still offer decent + * performance when being used for vector calculations. + * + * @constructor + * @param {Number[]} [elements] - The flat list of element index and element value pairs. + */ +lunr.Vector = function (elements) { + this._magnitude = 0 + this.elements = elements || [] +} + + +/** + * Calculates the position within the vector to insert a given index. + * + * This is used internally by insert and upsert. If there are duplicate indexes then + * the position is returned as if the value for that index were to be updated, but it + * is the callers responsibility to check whether there is a duplicate at that index + * + * @param {Number} insertIdx - The index at which the element should be inserted. + * @returns {Number} + */ +lunr.Vector.prototype.positionForIndex = function (index) { + // For an empty vector the tuple can be inserted at the beginning + if (this.elements.length == 0) { + return 0 + } + + var start = 0, + end = this.elements.length / 2, + sliceLength = end - start, + pivotPoint = Math.floor(sliceLength / 2), + pivotIndex = this.elements[pivotPoint * 2] + + while (sliceLength > 1) { + if (pivotIndex < index) { + start = pivotPoint + } + + if (pivotIndex > index) { + end = pivotPoint + } + + if (pivotIndex == index) { + break + } + + sliceLength = end - start + pivotPoint = start + Math.floor(sliceLength / 2) + pivotIndex = this.elements[pivotPoint * 2] + } + + if (pivotIndex == index) { + return pivotPoint * 2 + } + + if (pivotIndex > index) { + return pivotPoint * 2 + } + + if (pivotIndex < index) { + return (pivotPoint + 1) * 2 + } +} + +/** + * Inserts an element at an index within the vector. + * + * Does not allow duplicates, will throw an error if there is already an entry + * for this index. + * + * @param {Number} insertIdx - The index at which the element should be inserted. + * @param {Number} val - The value to be inserted into the vector. + */ +lunr.Vector.prototype.insert = function (insertIdx, val) { + this.upsert(insertIdx, val, function () { + throw "duplicate index" + }) +} + +/** + * Inserts or updates an existing index within the vector. + * + * @param {Number} insertIdx - The index at which the element should be inserted. + * @param {Number} val - The value to be inserted into the vector. + * @param {function} fn - A function that is called for updates, the existing value and the + * requested value are passed as arguments + */ +lunr.Vector.prototype.upsert = function (insertIdx, val, fn) { + this._magnitude = 0 + var position = this.positionForIndex(insertIdx) + + if (this.elements[position] == insertIdx) { + this.elements[position + 1] = fn(this.elements[position + 1], val) + } else { + this.elements.splice(position, 0, insertIdx, val) + } +} + +/** + * Calculates the magnitude of this vector. + * + * @returns {Number} + */ +lunr.Vector.prototype.magnitude = function () { + if (this._magnitude) return this._magnitude + + var sumOfSquares = 0, + elementsLength = this.elements.length + + for (var i = 1; i < elementsLength; i += 2) { + var val = this.elements[i] + sumOfSquares += val * val + } + + return this._magnitude = Math.sqrt(sumOfSquares) +} + +/** + * Calculates the dot product of this vector and another vector. + * + * @param {lunr.Vector} otherVector - The vector to compute the dot product with. + * @returns {Number} + */ +lunr.Vector.prototype.dot = function (otherVector) { + var dotProduct = 0, + a = this.elements, b = otherVector.elements, + aLen = a.length, bLen = b.length, + aVal = 0, bVal = 0, + i = 0, j = 0 + + while (i < aLen && j < bLen) { + aVal = a[i], bVal = b[j] + if (aVal < bVal) { + i += 2 + } else if (aVal > bVal) { + j += 2 + } else if (aVal == bVal) { + dotProduct += a[i + 1] * b[j + 1] + i += 2 + j += 2 + } + } + + return dotProduct +} + +/** + * Calculates the cosine similarity between this vector and another + * vector. + * + * @param {lunr.Vector} otherVector - The other vector to calculate the + * similarity with. + * @returns {Number} + */ +lunr.Vector.prototype.similarity = function (otherVector) { + return this.dot(otherVector) / (this.magnitude() * otherVector.magnitude()) +} + +/** + * Converts the vector to an array of the elements within the vector. + * + * @returns {Number[]} + */ +lunr.Vector.prototype.toArray = function () { + var output = new Array (this.elements.length / 2) + + for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) { + output[j] = this.elements[i] + } + + return output +} + +/** + * A JSON serializable representation of the vector. + * + * @returns {Number[]} + */ +lunr.Vector.prototype.toJSON = function () { + return this.elements +} +/* eslint-disable */ +/*! + * lunr.stemmer + * Copyright (C) 2017 Oliver Nightingale + * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt + */ + +/** + * lunr.stemmer is an english language stemmer, this is a JavaScript + * implementation of the PorterStemmer taken from http://tartarus.org/~martin + * + * @static + * @implements {lunr.PipelineFunction} + * @param {lunr.Token} token - The string to stem + * @returns {lunr.Token} + * @see {@link lunr.Pipeline} + */ +lunr.stemmer = (function(){ + var step2list = { + "ational" : "ate", + "tional" : "tion", + "enci" : "ence", + "anci" : "ance", + "izer" : "ize", + "bli" : "ble", + "alli" : "al", + "entli" : "ent", + "eli" : "e", + "ousli" : "ous", + "ization" : "ize", + "ation" : "ate", + "ator" : "ate", + "alism" : "al", + "iveness" : "ive", + "fulness" : "ful", + "ousness" : "ous", + "aliti" : "al", + "iviti" : "ive", + "biliti" : "ble", + "logi" : "log" + }, + + step3list = { + "icate" : "ic", + "ative" : "", + "alize" : "al", + "iciti" : "ic", + "ical" : "ic", + "ful" : "", + "ness" : "" + }, + + c = "[^aeiou]", // consonant + v = "[aeiouy]", // vowel + C = c + "[^aeiouy]*", // consonant sequence + V = v + "[aeiou]*", // vowel sequence + + mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0 + meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1 + mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1 + s_v = "^(" + C + ")?" + v; // vowel in stem + + var re_mgr0 = new RegExp(mgr0); + var re_mgr1 = new RegExp(mgr1); + var re_meq1 = new RegExp(meq1); + var re_s_v = new RegExp(s_v); + + var re_1a = /^(.+?)(ss|i)es$/; + var re2_1a = /^(.+?)([^s])s$/; + var re_1b = /^(.+?)eed$/; + var re2_1b = /^(.+?)(ed|ing)$/; + var re_1b_2 = /.$/; + var re2_1b_2 = /(at|bl|iz)$/; + var re3_1b_2 = new RegExp("([^aeiouylsz])\\1$"); + var re4_1b_2 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + + var re_1c = /^(.+?[^aeiou])y$/; + var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + + var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + + var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + var re2_4 = /^(.+?)(s|t)(ion)$/; + + var re_5 = /^(.+?)e$/; + var re_5_1 = /ll$/; + var re3_5 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + + var porterStemmer = function porterStemmer(w) { + var stem, + suffix, + firstch, + re, + re2, + re3, + re4; + + if (w.length < 3) { return w; } + + firstch = w.substr(0,1); + if (firstch == "y") { + w = firstch.toUpperCase() + w.substr(1); + } + + // Step 1a + re = re_1a + re2 = re2_1a; + + if (re.test(w)) { w = w.replace(re,"$1$2"); } + else if (re2.test(w)) { w = w.replace(re2,"$1$2"); } + + // Step 1b + re = re_1b; + re2 = re2_1b; + if (re.test(w)) { + var fp = re.exec(w); + re = re_mgr0; + if (re.test(fp[1])) { + re = re_1b_2; + w = w.replace(re,""); + } + } else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = re_s_v; + if (re2.test(stem)) { + w = stem; + re2 = re2_1b_2; + re3 = re3_1b_2; + re4 = re4_1b_2; + if (re2.test(w)) { w = w + "e"; } + else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,""); } + else if (re4.test(w)) { w = w + "e"; } + } + } + + // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say) + re = re_1c; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + w = stem + "i"; + } + + // Step 2 + re = re_2; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = re_mgr0; + if (re.test(stem)) { + w = stem + step2list[suffix]; + } + } + + // Step 3 + re = re_3; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = re_mgr0; + if (re.test(stem)) { + w = stem + step3list[suffix]; + } + } + + // Step 4 + re = re_4; + re2 = re2_4; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = re_mgr1; + if (re.test(stem)) { + w = stem; + } + } else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = re_mgr1; + if (re2.test(stem)) { + w = stem; + } + } + + // Step 5 + re = re_5; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = re_mgr1; + re2 = re_meq1; + re3 = re3_5; + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) { + w = stem; + } + } + + re = re_5_1; + re2 = re_mgr1; + if (re.test(w) && re2.test(w)) { + re = re_1b_2; + w = w.replace(re,""); + } + + // and turn initial Y back to y + + if (firstch == "y") { + w = firstch.toLowerCase() + w.substr(1); + } + + return w; + }; + + return function (token) { + return token.update(porterStemmer); + } +})(); + +lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer') +/*! + * lunr.stopWordFilter + * Copyright (C) 2017 Oliver Nightingale + */ + +/** + * lunr.generateStopWordFilter builds a stopWordFilter function from the provided + * list of stop words. + * + * The built in lunr.stopWordFilter is built using this generator and can be used + * to generate custom stopWordFilters for applications or non English languages. + * + * @param {Array} token The token to pass through the filter + * @returns {lunr.PipelineFunction} + * @see lunr.Pipeline + * @see lunr.stopWordFilter + */ +lunr.generateStopWordFilter = function (stopWords) { + var words = stopWords.reduce(function (memo, stopWord) { + memo[stopWord] = stopWord + return memo + }, {}) + + return function (token) { + if (token && words[token.toString()] !== token.toString()) return token + } +} + +/** + * lunr.stopWordFilter is an English language stop word list filter, any words + * contained in the list will not be passed through the filter. + * + * This is intended to be used in the Pipeline. If the token does not pass the + * filter then undefined will be returned. + * + * @implements {lunr.PipelineFunction} + * @params {lunr.Token} token - A token to check for being a stop word. + * @returns {lunr.Token} + * @see {@link lunr.Pipeline} + */ +lunr.stopWordFilter = lunr.generateStopWordFilter([ + 'a', + 'able', + 'about', + 'across', + 'after', + 'all', + 'almost', + 'also', + 'am', + 'among', + 'an', + 'and', + 'any', + 'are', + 'as', + 'at', + 'be', + 'because', + 'been', + 'but', + 'by', + 'can', + 'cannot', + 'could', + 'dear', + 'did', + 'do', + 'does', + 'either', + 'else', + 'ever', + 'every', + 'for', + 'from', + 'get', + 'got', + 'had', + 'has', + 'have', + 'he', + 'her', + 'hers', + 'him', + 'his', + 'how', + 'however', + 'i', + 'if', + 'in', + 'into', + 'is', + 'it', + 'its', + 'just', + 'least', + 'let', + 'like', + 'likely', + 'may', + 'me', + 'might', + 'most', + 'must', + 'my', + 'neither', + 'no', + 'nor', + 'not', + 'of', + 'off', + 'often', + 'on', + 'only', + 'or', + 'other', + 'our', + 'own', + 'rather', + 'said', + 'say', + 'says', + 'she', + 'should', + 'since', + 'so', + 'some', + 'than', + 'that', + 'the', + 'their', + 'them', + 'then', + 'there', + 'these', + 'they', + 'this', + 'tis', + 'to', + 'too', + 'twas', + 'us', + 'wants', + 'was', + 'we', + 'were', + 'what', + 'when', + 'where', + 'which', + 'while', + 'who', + 'whom', + 'why', + 'will', + 'with', + 'would', + 'yet', + 'you', + 'your' +]) + +lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter') +/*! + * lunr.trimmer + * Copyright (C) 2017 Oliver Nightingale + */ + +/** + * lunr.trimmer is a pipeline function for trimming non word + * characters from the beginning and end of tokens before they + * enter the index. + * + * This implementation may not work correctly for non latin + * characters and should either be removed or adapted for use + * with languages with non-latin characters. + * + * @static + * @implements {lunr.PipelineFunction} + * @param {lunr.Token} token The token to pass through the filter + * @returns {lunr.Token} + * @see lunr.Pipeline + */ +lunr.trimmer = function (token) { + return token.update(function (s) { + return s.replace(/^\W+/, '').replace(/\W+$/, '') + }) +} + +lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer') +/*! + * lunr.TokenSet + * Copyright (C) 2017 Oliver Nightingale + */ + +/** + * A token set is used to store the unique list of all tokens + * within an index. Token sets are also used to represent an + * incoming query to the index, this query token set and index + * token set are then intersected to find which tokens to look + * up in the inverted index. + * + * A token set can hold multiple tokens, as in the case of the + * index token set, or it can hold a single token as in the + * case of a simple query token set. + * + * Additionally token sets are used to perform wildcard matching. + * Leading, contained and trailing wildcards are supported, and + * from this edit distance matching can also be provided. + * + * Token sets are implemented as a minimal finite state automata, + * where both common prefixes and suffixes are shared between tokens. + * This helps to reduce the space used for storing the token set. + * + * @constructor + */ +lunr.TokenSet = function () { + this.final = false + this.edges = {} + this.id = lunr.TokenSet._nextId + lunr.TokenSet._nextId += 1 +} + +/** + * Keeps track of the next, auto increment, identifier to assign + * to a new tokenSet. + * + * TokenSets require a unique identifier to be correctly minimised. + * + * @private + */ +lunr.TokenSet._nextId = 1 + +/** + * Creates a TokenSet instance from the given sorted array of words. + * + * @param {String[]} arr - A sorted array of strings to create the set from. + * @returns {lunr.TokenSet} + * @throws Will throw an error if the input array is not sorted. + */ +lunr.TokenSet.fromArray = function (arr) { + var builder = new lunr.TokenSet.Builder + + for (var i = 0, len = arr.length; i < len; i++) { + builder.insert(arr[i]) + } + + builder.finish() + return builder.root +} + +/** + * Creates a token set from a query clause. + * + * @private + * @param {Object} clause - A single clause from lunr.Query. + * @param {string} clause.term - The query clause term. + * @param {number} [clause.editDistance] - The optional edit distance for the term. + * @returns {lunr.TokenSet} + */ +lunr.TokenSet.fromClause = function (clause) { + if ('editDistance' in clause) { + return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance) + } else { + return lunr.TokenSet.fromString(clause.term) + } +} + +/** + * Creates a token set representing a single string with a specified + * edit distance. + * + * Insertions, deletions, substitutions and transpositions are each + * treated as an edit distance of 1. + * + * Increasing the allowed edit distance will have a dramatic impact + * on the performance of both creating and intersecting these TokenSets. + * It is advised to keep the edit distance less than 3. + * + * @param {string} str - The string to create the token set from. + * @param {number} editDistance - The allowed edit distance to match. + * @returns {lunr.Vector} + */ +lunr.TokenSet.fromFuzzyString = function (str, editDistance) { + var root = new lunr.TokenSet + + var stack = [{ + node: root, + editsRemaining: editDistance, + str: str + }] + + while (stack.length) { + var frame = stack.pop() + + // no edit + if (frame.str.length > 0) { + var char = frame.str.charAt(0), + noEditNode + + if (char in frame.node.edges) { + noEditNode = frame.node.edges[char] + } else { + noEditNode = new lunr.TokenSet + frame.node.edges[char] = noEditNode + } + + if (frame.str.length == 1) { + noEditNode.final = true + } else { + stack.push({ + node: noEditNode, + editsRemaining: frame.editsRemaining, + str: frame.str.slice(1) + }) + } + } + + // deletion + // can only do a deletion if we have enough edits remaining + // and if there are characters left to delete in the string + if (frame.editsRemaining > 0 && frame.str.length > 1) { + var char = frame.str.charAt(1), + deletionNode + + if (char in frame.node.edges) { + deletionNode = frame.node.edges[char] + } else { + deletionNode = new lunr.TokenSet + frame.node.edges[char] = deletionNode + } + + if (frame.str.length <= 2) { + deletionNode.final = true + } else { + stack.push({ + node: deletionNode, + editsRemaining: frame.editsRemaining - 1, + str: frame.str.slice(2) + }) + } + } + + // deletion + // just removing the last character from the str + if (frame.editsRemaining > 0 && frame.str.length == 1) { + frame.node.final = true + } + + // substitution + // can only do a substitution if we have enough edits remaining + // and if there are characters left to substitute + if (frame.editsRemaining > 0 && frame.str.length >= 1) { + if ("*" in frame.node.edges) { + var substitutionNode = frame.node.edges["*"] + } else { + var substitutionNode = new lunr.TokenSet + frame.node.edges["*"] = substitutionNode + } + + if (frame.str.length == 1) { + substitutionNode.final = true + } else { + stack.push({ + node: substitutionNode, + editsRemaining: frame.editsRemaining - 1, + str: frame.str.slice(1) + }) + } + } + + // insertion + // can only do insertion if there are edits remaining + if (frame.editsRemaining > 0) { + if ("*" in frame.node.edges) { + var insertionNode = frame.node.edges["*"] + } else { + var insertionNode = new lunr.TokenSet + frame.node.edges["*"] = insertionNode + } + + if (frame.str.length == 0) { + insertionNode.final = true + } else { + stack.push({ + node: insertionNode, + editsRemaining: frame.editsRemaining - 1, + str: frame.str + }) + } + } + + // transposition + // can only do a transposition if there are edits remaining + // and there are enough characters to transpose + if (frame.editsRemaining > 0 && frame.str.length > 1) { + var charA = frame.str.charAt(0), + charB = frame.str.charAt(1), + transposeNode + + if (charB in frame.node.edges) { + transposeNode = frame.node.edges[charB] + } else { + transposeNode = new lunr.TokenSet + frame.node.edges[charB] = transposeNode + } + + if (frame.str.length == 1) { + transposeNode.final = true + } else { + stack.push({ + node: transposeNode, + editsRemaining: frame.editsRemaining - 1, + str: charA + frame.str.slice(2) + }) + } + } + } + + return root +} + +/** + * Creates a TokenSet from a string. + * + * The string may contain one or more wildcard characters (*) + * that will allow wildcard matching when intersecting with + * another TokenSet. + * + * @param {string} str - The string to create a TokenSet from. + * @returns {lunr.TokenSet} + */ +lunr.TokenSet.fromString = function (str) { + var node = new lunr.TokenSet, + root = node, + wildcardFound = false + + /* + * Iterates through all characters within the passed string + * appending a node for each character. + * + * As soon as a wildcard character is found then a self + * referencing edge is introduced to continually match + * any number of any characters. + */ + for (var i = 0, len = str.length; i < len; i++) { + var char = str[i], + final = (i == len - 1) + + if (char == "*") { + wildcardFound = true + node.edges[char] = node + node.final = final + + } else { + var next = new lunr.TokenSet + next.final = final + + node.edges[char] = next + node = next + + // TODO: is this needed anymore? + if (wildcardFound) { + node.edges["*"] = root + } + } + } + + return root +} + +/** + * Converts this TokenSet into an array of strings + * contained within the TokenSet. + * + * @returns {string[]} + */ +lunr.TokenSet.prototype.toArray = function () { + var words = [] + + var stack = [{ + prefix: "", + node: this + }] + + while (stack.length) { + var frame = stack.pop(), + edges = Object.keys(frame.node.edges), + len = edges.length + + if (frame.node.final) { + words.push(frame.prefix) + } + + for (var i = 0; i < len; i++) { + var edge = edges[i] + + stack.push({ + prefix: frame.prefix.concat(edge), + node: frame.node.edges[edge] + }) + } + } + + return words +} + +/** + * Generates a string representation of a TokenSet. + * + * This is intended to allow TokenSets to be used as keys + * in objects, largely to aid the construction and minimisation + * of a TokenSet. As such it is not designed to be a human + * friendly representation of the TokenSet. + * + * @returns {string} + */ +lunr.TokenSet.prototype.toString = function () { + // NOTE: Using Object.keys here as this.edges is very likely + // to enter 'hash-mode' with many keys being added + // + // avoiding a for-in loop here as it leads to the function + // being de-optimised (at least in V8). From some simple + // benchmarks the performance is comparable, but allowing + // V8 to optimize may mean easy performance wins in the future. + + if (this._str) { + return this._str + } + + var str = this.final ? '1' : '0', + labels = Object.keys(this.edges).sort(), + len = labels.length + + for (var i = 0; i < len; i++) { + var label = labels[i], + node = this.edges[label] + + str = str + label + node.id + } + + return str +} + +/** + * Returns a new TokenSet that is the intersection of + * this TokenSet and the passed TokenSet. + * + * This intersection will take into account any wildcards + * contained within the TokenSet. + * + * @param {lunr.TokenSet} b - An other TokenSet to intersect with. + * @returns {lunr.TokenSet} + */ +lunr.TokenSet.prototype.intersect = function (b) { + var output = new lunr.TokenSet, + frame = undefined + + var stack = [{ + qNode: b, + output: output, + node: this + }] + + while (stack.length) { + frame = stack.pop() + + // NOTE: As with the #toString method, we are using + // Object.keys and a for loop instead of a for-in loop + // as both of these objects enter 'hash' mode, causing + // the function to be de-optimised in V8 + var qEdges = Object.keys(frame.qNode.edges), + qLen = qEdges.length, + nEdges = Object.keys(frame.node.edges), + nLen = nEdges.length + + for (var q = 0; q < qLen; q++) { + var qEdge = qEdges[q] + + for (var n = 0; n < nLen; n++) { + var nEdge = nEdges[n] + + if (nEdge == qEdge || qEdge == '*') { + var node = frame.node.edges[nEdge], + qNode = frame.qNode.edges[qEdge], + final = node.final && qNode.final, + next = undefined + + if (nEdge in frame.output.edges) { + // an edge already exists for this character + // no need to create a new node, just set the finality + // bit unless this node is already final + next = frame.output.edges[nEdge] + next.final = next.final || final + + } else { + // no edge exists yet, must create one + // set the finality bit and insert it + // into the output + next = new lunr.TokenSet + next.final = final + frame.output.edges[nEdge] = next + } + + stack.push({ + qNode: qNode, + output: next, + node: node + }) + } + } + } + } + + return output +} +lunr.TokenSet.Builder = function () { + this.previousWord = "" + this.root = new lunr.TokenSet + this.uncheckedNodes = [] + this.minimizedNodes = {} +} + +lunr.TokenSet.Builder.prototype.insert = function (word) { + var node, + commonPrefix = 0 + + if (word < this.previousWord) { + throw new Error ("Out of order word insertion") + } + + for (var i = 0; i < word.length && i < this.previousWord.length; i++) { + if (word[i] != this.previousWord[i]) break + commonPrefix++ + } + + this.minimize(commonPrefix) + + if (this.uncheckedNodes.length == 0) { + node = this.root + } else { + node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child + } + + for (var i = commonPrefix; i < word.length; i++) { + var nextNode = new lunr.TokenSet, + char = word[i] + + node.edges[char] = nextNode + + this.uncheckedNodes.push({ + parent: node, + char: char, + child: nextNode + }) + + node = nextNode + } + + node.final = true + this.previousWord = word +} + +lunr.TokenSet.Builder.prototype.finish = function () { + this.minimize(0) +} + +lunr.TokenSet.Builder.prototype.minimize = function (downTo) { + for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) { + var node = this.uncheckedNodes[i], + childKey = node.child.toString() + + if (childKey in this.minimizedNodes) { + node.parent.edges[node.char] = this.minimizedNodes[childKey] + } else { + // Cache the key for this node since + // we know it can't change anymore + node.child._str = childKey + + this.minimizedNodes[childKey] = node.child + } + + this.uncheckedNodes.pop() + } +} +/*! + * lunr.Index + * Copyright (C) 2017 Oliver Nightingale + */ + +/** + * An index contains the built index of all documents and provides a query interface + * to the index. + * + * Usually instances of lunr.Index will not be created using this constructor, instead + * lunr.Builder should be used to construct new indexes, or lunr.Index.load should be + * used to load previously built and serialized indexes. + * + * @constructor + * @param {Object} attrs - The attributes of the built search index. + * @param {Object} attrs.invertedIndex - An index of term/field to document reference. + * @param {Object} attrs.documentVectors - Document vectors keyed by document reference. + * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens. + * @param {string[]} attrs.fields - The names of indexed document fields. + * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms. + */ +lunr.Index = function (attrs) { + this.invertedIndex = attrs.invertedIndex + this.fieldVectors = attrs.fieldVectors + this.tokenSet = attrs.tokenSet + this.fields = attrs.fields + this.pipeline = attrs.pipeline +} + +/** + * A result contains details of a document matching a search query. + * @typedef {Object} lunr.Index~Result + * @property {string} ref - The reference of the document this result represents. + * @property {number} score - A number between 0 and 1 representing how similar this document is to the query. + * @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match. + */ + +/** + * Although lunr provides the ability to create queries using lunr.Query, it also provides a simple + * query language which itself is parsed into an instance of lunr.Query. + * + * For programmatically building queries it is advised to directly use lunr.Query, the query language + * is best used for human entered text rather than program generated text. + * + * At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported + * and will be combined with OR, e.g `hello world` will match documents that contain either 'hello' + * or 'world', though those that contain both will rank higher in the results. + * + * Wildcards can be included in terms to match one or more unspecified characters, these wildcards can + * be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding + * wildcards will increase the number of documents that will be found but can also have a negative + * impact on query performance, especially with wildcards at the beginning of a term. + * + * Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term + * hello in the title field will match this query. Using a field not present in the index will lead + * to an error being thrown. + * + * Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term + * boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported + * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2. + * Avoid large values for edit distance to improve query performance. + * + * To escape special characters the backslash character '\' can be used, this allows searches to include + * characters that would normally be considered modifiers, e.g. `foo\~2` will search for a term "foo~2" instead + * of attempting to apply a boost of 2 to the search term "foo". + * + * @typedef {string} lunr.Index~QueryString + * @example Simple single term query + * hello + * @example Multiple term query + * hello world + * @example term scoped to a field + * title:hello + * @example term with a boost of 10 + * hello^10 + * @example term with an edit distance of 2 + * hello~2 + */ + +/** + * Performs a search against the index using lunr query syntax. + * + * Results will be returned sorted by their score, the most relevant results + * will be returned first. + * + * For more programmatic querying use lunr.Index#query. + * + * @param {lunr.Index~QueryString} queryString - A string containing a lunr query. + * @throws {lunr.QueryParseError} If the passed query string cannot be parsed. + * @returns {lunr.Index~Result[]} + */ +lunr.Index.prototype.search = function (queryString) { + return this.query(function (query) { + var parser = new lunr.QueryParser(queryString, query) + parser.parse() + }) +} + +/** + * A query builder callback provides a query object to be used to express + * the query to perform on the index. + * + * @callback lunr.Index~queryBuilder + * @param {lunr.Query} query - The query object to build up. + * @this lunr.Query + */ + +/** + * Performs a query against the index using the yielded lunr.Query object. + * + * If performing programmatic queries against the index, this method is preferred + * over lunr.Index#search so as to avoid the additional query parsing overhead. + * + * A query object is yielded to the supplied function which should be used to + * express the query to be run against the index. + * + * Note that although this function takes a callback parameter it is _not_ an + * asynchronous operation, the callback is just yielded a query object to be + * customized. + * + * @param {lunr.Index~queryBuilder} fn - A function that is used to build the query. + * @returns {lunr.Index~Result[]} + */ +lunr.Index.prototype.query = function (fn) { + // for each query clause + // * process terms + // * expand terms from token set + // * find matching documents and metadata + // * get document vectors + // * score documents + + var query = new lunr.Query(this.fields), + matchingFields = Object.create(null), + queryVectors = Object.create(null), + termFieldCache = Object.create(null) + + fn.call(query, query) + + for (var i = 0; i < query.clauses.length; i++) { + /* + * Unless the pipeline has been disabled for this term, which is + * the case for terms with wildcards, we need to pass the clause + * term through the search pipeline. A pipeline returns an array + * of processed terms. Pipeline functions may expand the passed + * term, which means we may end up performing multiple index lookups + * for a single query term. + */ + var clause = query.clauses[i], + terms = null + + if (clause.usePipeline) { + terms = this.pipeline.runString(clause.term) + } else { + terms = [clause.term] + } + + for (var m = 0; m < terms.length; m++) { + var term = terms[m] + + /* + * Each term returned from the pipeline needs to use the same query + * clause object, e.g. the same boost and or edit distance. The + * simplest way to do this is to re-use the clause object but mutate + * its term property. + */ + clause.term = term + + /* + * From the term in the clause we create a token set which will then + * be used to intersect the indexes token set to get a list of terms + * to lookup in the inverted index + */ + var termTokenSet = lunr.TokenSet.fromClause(clause), + expandedTerms = this.tokenSet.intersect(termTokenSet).toArray() + + for (var j = 0; j < expandedTerms.length; j++) { + /* + * For each term get the posting and termIndex, this is required for + * building the query vector. + */ + var expandedTerm = expandedTerms[j], + posting = this.invertedIndex[expandedTerm], + termIndex = posting._index + + for (var k = 0; k < clause.fields.length; k++) { + /* + * For each field that this query term is scoped by (by default + * all fields are in scope) we need to get all the document refs + * that have this term in that field. + * + * The posting is the entry in the invertedIndex for the matching + * term from above. + */ + var field = clause.fields[k], + fieldPosting = posting[field], + matchingDocumentRefs = Object.keys(fieldPosting), + termField = expandedTerm + "/" + field + + /* + * To support field level boosts a query vector is created per + * field. This vector is populated using the termIndex found for + * the term and a unit value with the appropriate boost applied. + * + * If the query vector for this field does not exist yet it needs + * to be created. + */ + if (queryVectors[field] === undefined) { + queryVectors[field] = new lunr.Vector + } + + /* + * Using upsert because there could already be an entry in the vector + * for the term we are working with. In that case we just add the scores + * together. + */ + queryVectors[field].upsert(termIndex, 1 * clause.boost, function (a, b) { return a + b }) + + /** + * If we've already seen this term, field combo then we've already collected + * the matching documents and metadata, no need to go through all that again + */ + if (termFieldCache[termField]) { + continue + } + + for (var l = 0; l < matchingDocumentRefs.length; l++) { + /* + * All metadata for this term/field/document triple + * are then extracted and collected into an instance + * of lunr.MatchData ready to be returned in the query + * results + */ + var matchingDocumentRef = matchingDocumentRefs[l], + matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field), + metadata = fieldPosting[matchingDocumentRef], + fieldMatch + + if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) { + matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata) + } else { + fieldMatch.add(expandedTerm, field, metadata) + } + + } + + termFieldCache[termField] = true + } + } + } + } + + var matchingFieldRefs = Object.keys(matchingFields), + results = [], + matches = Object.create(null) + + for (var i = 0; i < matchingFieldRefs.length; i++) { + /* + * Currently we have document fields that match the query, but we + * need to return documents. The matchData and scores are combined + * from multiple fields belonging to the same document. + * + * Scores are calculated by field, using the query vectors created + * above, and combined into a final document score using addition. + */ + var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]), + docRef = fieldRef.docRef, + fieldVector = this.fieldVectors[fieldRef], + score = queryVectors[fieldRef.fieldName].similarity(fieldVector), + docMatch + + if ((docMatch = matches[docRef]) !== undefined) { + docMatch.score += score + docMatch.matchData.combine(matchingFields[fieldRef]) + } else { + var match = { + ref: docRef, + score: score, + matchData: matchingFields[fieldRef] + } + matches[docRef] = match + results.push(match) + } + } + + /* + * Sort the results objects by score, highest first. + */ + return results.sort(function (a, b) { + return b.score - a.score + }) +} + +/** + * Prepares the index for JSON serialization. + * + * The schema for this JSON blob will be described in a + * separate JSON schema file. + * + * @returns {Object} + */ +lunr.Index.prototype.toJSON = function () { + var invertedIndex = Object.keys(this.invertedIndex) + .sort() + .map(function (term) { + return [term, this.invertedIndex[term]] + }, this) + + var fieldVectors = Object.keys(this.fieldVectors) + .map(function (ref) { + return [ref, this.fieldVectors[ref].toJSON()] + }, this) + + return { + version: lunr.version, + fields: this.fields, + fieldVectors: fieldVectors, + invertedIndex: invertedIndex, + pipeline: this.pipeline.toJSON() + } +} + +/** + * Loads a previously serialized lunr.Index + * + * @param {Object} serializedIndex - A previously serialized lunr.Index + * @returns {lunr.Index} + */ +lunr.Index.load = function (serializedIndex) { + var attrs = {}, + fieldVectors = {}, + serializedVectors = serializedIndex.fieldVectors, + invertedIndex = {}, + serializedInvertedIndex = serializedIndex.invertedIndex, + tokenSetBuilder = new lunr.TokenSet.Builder, + pipeline = lunr.Pipeline.load(serializedIndex.pipeline) + + if (serializedIndex.version != lunr.version) { + lunr.utils.warn("Version mismatch when loading serialised index. Current version of lunr '" + lunr.version + "' does not match serialized index '" + serializedIndex.version + "'") + } + + for (var i = 0; i < serializedVectors.length; i++) { + var tuple = serializedVectors[i], + ref = tuple[0], + elements = tuple[1] + + fieldVectors[ref] = new lunr.Vector(elements) + } + + for (var i = 0; i < serializedInvertedIndex.length; i++) { + var tuple = serializedInvertedIndex[i], + term = tuple[0], + posting = tuple[1] + + tokenSetBuilder.insert(term) + invertedIndex[term] = posting + } + + tokenSetBuilder.finish() + + attrs.fields = serializedIndex.fields + + attrs.fieldVectors = fieldVectors + attrs.invertedIndex = invertedIndex + attrs.tokenSet = tokenSetBuilder.root + attrs.pipeline = pipeline + + return new lunr.Index(attrs) +} +/*! + * lunr.Builder + * Copyright (C) 2017 Oliver Nightingale + */ + +/** + * lunr.Builder performs indexing on a set of documents and + * returns instances of lunr.Index ready for querying. + * + * All configuration of the index is done via the builder, the + * fields to index, the document reference, the text processing + * pipeline and document scoring parameters are all set on the + * builder before indexing. + * + * @constructor + * @property {string} _ref - Internal reference to the document reference field. + * @property {string[]} _fields - Internal reference to the document fields to index. + * @property {object} invertedIndex - The inverted index maps terms to document fields. + * @property {object} documentTermFrequencies - Keeps track of document term frequencies. + * @property {object} documentLengths - Keeps track of the length of documents added to the index. + * @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing. + * @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing. + * @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index. + * @property {number} documentCount - Keeps track of the total number of documents indexed. + * @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75. + * @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2. + * @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space. + * @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index. + */ +lunr.Builder = function () { + this._ref = "id" + this._fields = [] + this.invertedIndex = Object.create(null) + this.fieldTermFrequencies = {} + this.fieldLengths = {} + this.tokenizer = lunr.tokenizer + this.pipeline = new lunr.Pipeline + this.searchPipeline = new lunr.Pipeline + this.documentCount = 0 + this._b = 0.75 + this._k1 = 1.2 + this.termIndex = 0 + this.metadataWhitelist = [] +} + +/** + * Sets the document field used as the document reference. Every document must have this field. + * The type of this field in the document should be a string, if it is not a string it will be + * coerced into a string by calling toString. + * + * The default ref is 'id'. + * + * The ref should _not_ be changed during indexing, it should be set before any documents are + * added to the index. Changing it during indexing can lead to inconsistent results. + * + * @param {string} ref - The name of the reference field in the document. + */ +lunr.Builder.prototype.ref = function (ref) { + this._ref = ref +} + +/** + * Adds a field to the list of document fields that will be indexed. Every document being + * indexed should have this field. Null values for this field in indexed documents will + * not cause errors but will limit the chance of that document being retrieved by searches. + * + * All fields should be added before adding documents to the index. Adding fields after + * a document has been indexed will have no effect on already indexed documents. + * + * @param {string} field - The name of a field to index in all documents. + */ +lunr.Builder.prototype.field = function (field) { + this._fields.push(field) +} + +/** + * A parameter to tune the amount of field length normalisation that is applied when + * calculating relevance scores. A value of 0 will completely disable any normalisation + * and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b + * will be clamped to the range 0 - 1. + * + * @param {number} number - The value to set for this tuning parameter. + */ +lunr.Builder.prototype.b = function (number) { + if (number < 0) { + this._b = 0 + } else if (number > 1) { + this._b = 1 + } else { + this._b = number + } +} + +/** + * A parameter that controls the speed at which a rise in term frequency results in term + * frequency saturation. The default value is 1.2. Setting this to a higher value will give + * slower saturation levels, a lower value will result in quicker saturation. + * + * @param {number} number - The value to set for this tuning parameter. + */ +lunr.Builder.prototype.k1 = function (number) { + this._k1 = number +} + +/** + * Adds a document to the index. + * + * Before adding fields to the index the index should have been fully setup, with the document + * ref and all fields to index already having been specified. + * + * The document must have a field name as specified by the ref (by default this is 'id') and + * it should have all fields defined for indexing, though null or undefined values will not + * cause errors. + * + * @param {object} doc - The document to add to the index. + */ +lunr.Builder.prototype.add = function (doc) { + var docRef = doc[this._ref] + + this.documentCount += 1 + + for (var i = 0; i < this._fields.length; i++) { + var fieldName = this._fields[i], + field = doc[fieldName], + tokens = this.tokenizer(field), + terms = this.pipeline.run(tokens), + fieldRef = new lunr.FieldRef (docRef, fieldName), + fieldTerms = Object.create(null) + + this.fieldTermFrequencies[fieldRef] = fieldTerms + this.fieldLengths[fieldRef] = 0 + + // store the length of this field for this document + this.fieldLengths[fieldRef] += terms.length + + // calculate term frequencies for this field + for (var j = 0; j < terms.length; j++) { + var term = terms[j] + + if (fieldTerms[term] == undefined) { + fieldTerms[term] = 0 + } + + fieldTerms[term] += 1 + + // add to inverted index + // create an initial posting if one doesn't exist + if (this.invertedIndex[term] == undefined) { + var posting = Object.create(null) + posting["_index"] = this.termIndex + this.termIndex += 1 + + for (var k = 0; k < this._fields.length; k++) { + posting[this._fields[k]] = Object.create(null) + } + + this.invertedIndex[term] = posting + } + + // add an entry for this term/fieldName/docRef to the invertedIndex + if (this.invertedIndex[term][fieldName][docRef] == undefined) { + this.invertedIndex[term][fieldName][docRef] = Object.create(null) + } + + // store all whitelisted metadata about this token in the + // inverted index + for (var l = 0; l < this.metadataWhitelist.length; l++) { + var metadataKey = this.metadataWhitelist[l], + metadata = term.metadata[metadataKey] + + if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) { + this.invertedIndex[term][fieldName][docRef][metadataKey] = [] + } + + this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata) + } + } + + } +} + +/** + * Calculates the average document length for this index + * + * @private + */ +lunr.Builder.prototype.calculateAverageFieldLengths = function () { + + var fieldRefs = Object.keys(this.fieldLengths), + numberOfFields = fieldRefs.length, + accumulator = {}, + documentsWithField = {} + + for (var i = 0; i < numberOfFields; i++) { + var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]), + field = fieldRef.fieldName + + documentsWithField[field] || (documentsWithField[field] = 0) + documentsWithField[field] += 1 + + accumulator[field] || (accumulator[field] = 0) + accumulator[field] += this.fieldLengths[fieldRef] + } + + for (var i = 0; i < this._fields.length; i++) { + var field = this._fields[i] + accumulator[field] = accumulator[field] / documentsWithField[field] + } + + this.averageFieldLength = accumulator +} + +/** + * Builds a vector space model of every document using lunr.Vector + * + * @private + */ +lunr.Builder.prototype.createFieldVectors = function () { + var fieldVectors = {}, + fieldRefs = Object.keys(this.fieldTermFrequencies), + fieldRefsLength = fieldRefs.length, + termIdfCache = Object.create(null) + + for (var i = 0; i < fieldRefsLength; i++) { + var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]), + field = fieldRef.fieldName, + fieldLength = this.fieldLengths[fieldRef], + fieldVector = new lunr.Vector, + termFrequencies = this.fieldTermFrequencies[fieldRef], + terms = Object.keys(termFrequencies), + termsLength = terms.length + + for (var j = 0; j < termsLength; j++) { + var term = terms[j], + tf = termFrequencies[term], + termIndex = this.invertedIndex[term]._index, + idf, score, scoreWithPrecision + + if (termIdfCache[term] === undefined) { + idf = lunr.idf(this.invertedIndex[term], this.documentCount) + termIdfCache[term] = idf + } else { + idf = termIdfCache[term] + } + + score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[field])) + tf) + scoreWithPrecision = Math.round(score * 1000) / 1000 + // Converts 1.23456789 to 1.234. + // Reducing the precision so that the vectors take up less + // space when serialised. Doing it now so that they behave + // the same before and after serialisation. Also, this is + // the fastest approach to reducing a number's precision in + // JavaScript. + + fieldVector.insert(termIndex, scoreWithPrecision) + } + + fieldVectors[fieldRef] = fieldVector + } + + this.fieldVectors = fieldVectors +} + +/** + * Creates a token set of all tokens in the index using lunr.TokenSet + * + * @private + */ +lunr.Builder.prototype.createTokenSet = function () { + this.tokenSet = lunr.TokenSet.fromArray( + Object.keys(this.invertedIndex).sort() + ) +} + +/** + * Builds the index, creating an instance of lunr.Index. + * + * This completes the indexing process and should only be called + * once all documents have been added to the index. + * + * @returns {lunr.Index} + */ +lunr.Builder.prototype.build = function () { + this.calculateAverageFieldLengths() + this.createFieldVectors() + this.createTokenSet() + + return new lunr.Index({ + invertedIndex: this.invertedIndex, + fieldVectors: this.fieldVectors, + tokenSet: this.tokenSet, + fields: this._fields, + pipeline: this.searchPipeline + }) +} + +/** + * Applies a plugin to the index builder. + * + * A plugin is a function that is called with the index builder as its context. + * Plugins can be used to customise or extend the behaviour of the index + * in some way. A plugin is just a function, that encapsulated the custom + * behaviour that should be applied when building the index. + * + * The plugin function will be called with the index builder as its argument, additional + * arguments can also be passed when calling use. The function will be called + * with the index builder as its context. + * + * @param {Function} plugin The plugin to apply. + */ +lunr.Builder.prototype.use = function (fn) { + var args = Array.prototype.slice.call(arguments, 1) + args.unshift(this) + fn.apply(this, args) +} +/** + * Contains and collects metadata about a matching document. + * A single instance of lunr.MatchData is returned as part of every + * lunr.Index~Result. + * + * @constructor + * @param {string} term - The term this match data is associated with + * @param {string} field - The field in which the term was found + * @param {object} metadata - The metadata recorded about this term in this field + * @property {object} metadata - A cloned collection of metadata associated with this document. + * @see {@link lunr.Index~Result} + */ +lunr.MatchData = function (term, field, metadata) { + var clonedMetadata = Object.create(null), + metadataKeys = Object.keys(metadata) + + // Cloning the metadata to prevent the original + // being mutated during match data combination. + // Metadata is kept in an array within the inverted + // index so cloning the data can be done with + // Array#slice + for (var i = 0; i < metadataKeys.length; i++) { + var key = metadataKeys[i] + clonedMetadata[key] = metadata[key].slice() + } + + this.metadata = Object.create(null) + this.metadata[term] = Object.create(null) + this.metadata[term][field] = clonedMetadata +} + +/** + * An instance of lunr.MatchData will be created for every term that matches a + * document. However only one instance is required in a lunr.Index~Result. This + * method combines metadata from another instance of lunr.MatchData with this + * objects metadata. + * + * @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one. + * @see {@link lunr.Index~Result} + */ +lunr.MatchData.prototype.combine = function (otherMatchData) { + var terms = Object.keys(otherMatchData.metadata) + + for (var i = 0; i < terms.length; i++) { + var term = terms[i], + fields = Object.keys(otherMatchData.metadata[term]) + + if (this.metadata[term] == undefined) { + this.metadata[term] = Object.create(null) + } + + for (var j = 0; j < fields.length; j++) { + var field = fields[j], + keys = Object.keys(otherMatchData.metadata[term][field]) + + if (this.metadata[term][field] == undefined) { + this.metadata[term][field] = Object.create(null) + } + + for (var k = 0; k < keys.length; k++) { + var key = keys[k] + + if (this.metadata[term][field][key] == undefined) { + this.metadata[term][field][key] = otherMatchData.metadata[term][field][key] + } else { + this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key]) + } + + } + } + } +} + +/** + * Add metadata for a term/field pair to this instance of match data. + * + * @param {string} term - The term this match data is associated with + * @param {string} field - The field in which the term was found + * @param {object} metadata - The metadata recorded about this term in this field + */ +lunr.MatchData.prototype.add = function (term, field, metadata) { + if (!(term in this.metadata)) { + this.metadata[term] = Object.create(null) + this.metadata[term][field] = metadata + return + } + + if (!(field in this.metadata[term])) { + this.metadata[term][field] = metadata + return + } + + var metadataKeys = Object.keys(metadata) + + for (var i = 0; i < metadataKeys.length; i++) { + var key = metadataKeys[i] + + if (key in this.metadata[term][field]) { + this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key]) + } else { + this.metadata[term][field][key] = metadata[key] + } + } +} +/** + * A lunr.Query provides a programmatic way of defining queries to be performed + * against a {@link lunr.Index}. + * + * Prefer constructing a lunr.Query using the {@link lunr.Index#query} method + * so the query object is pre-initialized with the right index fields. + * + * @constructor + * @property {lunr.Query~Clause[]} clauses - An array of query clauses. + * @property {string[]} allFields - An array of all available fields in a lunr.Index. + */ +lunr.Query = function (allFields) { + this.clauses = [] + this.allFields = allFields +} + +/** + * Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause. + * + * This allows wildcards to be added to the beginning and end of a term without having to manually do any string + * concatenation. + * + * The wildcard constants can be bitwise combined to select both leading and trailing wildcards. + * + * @constant + * @default + * @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour + * @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists + * @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists + * @see lunr.Query~Clause + * @see lunr.Query#clause + * @see lunr.Query#term + * @example query term with trailing wildcard + * query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING }) + * @example query term with leading and trailing wildcard + * query.term('foo', { + * wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING + * }) + */ +lunr.Query.wildcard = new String ("*") +lunr.Query.wildcard.NONE = 0 +lunr.Query.wildcard.LEADING = 1 +lunr.Query.wildcard.TRAILING = 2 + +/** + * A single clause in a {@link lunr.Query} contains a term and details on how to + * match that term against a {@link lunr.Index}. + * + * @typedef {Object} lunr.Query~Clause + * @property {string[]} fields - The fields in an index this clause should be matched against. + * @property {number} [boost=1] - Any boost that should be applied when matching this clause. + * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be. + * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline. + * @property {number} [wildcard=0] - Whether the term should have wildcards appended or prepended. + */ + +/** + * Adds a {@link lunr.Query~Clause} to this query. + * + * Unless the clause contains the fields to be matched all fields will be matched. In addition + * a default boost of 1 is applied to the clause. + * + * @param {lunr.Query~Clause} clause - The clause to add to this query. + * @see lunr.Query~Clause + * @returns {lunr.Query} + */ +lunr.Query.prototype.clause = function (clause) { + if (!('fields' in clause)) { + clause.fields = this.allFields + } + + if (!('boost' in clause)) { + clause.boost = 1 + } + + if (!('usePipeline' in clause)) { + clause.usePipeline = true + } + + if (!('wildcard' in clause)) { + clause.wildcard = lunr.Query.wildcard.NONE + } + + if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) { + clause.term = "*" + clause.term + } + + if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) { + clause.term = "" + clause.term + "*" + } + + this.clauses.push(clause) + + return this +} + +/** + * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause} + * to the list of clauses that make up this query. + * + * @param {string} term - The term to add to the query. + * @param {Object} [options] - Any additional properties to add to the query clause. + * @returns {lunr.Query} + * @see lunr.Query#clause + * @see lunr.Query~Clause + * @example adding a single term to a query + * query.term("foo") + * @example adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard + * query.term("foo", { + * fields: ["title"], + * boost: 10, + * wildcard: lunr.Query.wildcard.TRAILING + * }) + */ +lunr.Query.prototype.term = function (term, options) { + var clause = options || {} + clause.term = term + + this.clause(clause) + + return this +} +lunr.QueryParseError = function (message, start, end) { + this.name = "QueryParseError" + this.message = message + this.start = start + this.end = end +} + +lunr.QueryParseError.prototype = new Error +lunr.QueryLexer = function (str) { + this.lexemes = [] + this.str = str + this.length = str.length + this.pos = 0 + this.start = 0 + this.escapeCharPositions = [] +} + +lunr.QueryLexer.prototype.run = function () { + var state = lunr.QueryLexer.lexText + + while (state) { + state = state(this) + } +} + +lunr.QueryLexer.prototype.sliceString = function () { + var subSlices = [], + sliceStart = this.start, + sliceEnd = this.pos + + for (var i = 0; i < this.escapeCharPositions.length; i++) { + sliceEnd = this.escapeCharPositions[i] + subSlices.push(this.str.slice(sliceStart, sliceEnd)) + sliceStart = sliceEnd + 1 + } + + subSlices.push(this.str.slice(sliceStart, this.pos)) + this.escapeCharPositions.length = 0 + + return subSlices.join('') +} + +lunr.QueryLexer.prototype.emit = function (type) { + this.lexemes.push({ + type: type, + str: this.sliceString(), + start: this.start, + end: this.pos + }) + + this.start = this.pos +} + +lunr.QueryLexer.prototype.escapeCharacter = function () { + this.escapeCharPositions.push(this.pos - 1) + this.pos += 1 +} + +lunr.QueryLexer.prototype.next = function () { + if (this.pos >= this.length) { + return lunr.QueryLexer.EOS + } + + var char = this.str.charAt(this.pos) + this.pos += 1 + return char +} + +lunr.QueryLexer.prototype.width = function () { + return this.pos - this.start +} + +lunr.QueryLexer.prototype.ignore = function () { + if (this.start == this.pos) { + this.pos += 1 + } + + this.start = this.pos +} + +lunr.QueryLexer.prototype.backup = function () { + this.pos -= 1 +} + +lunr.QueryLexer.prototype.acceptDigitRun = function () { + var char, charCode + + do { + char = this.next() + charCode = char.charCodeAt(0) + } while (charCode > 47 && charCode < 58) + + if (char != lunr.QueryLexer.EOS) { + this.backup() + } +} + +lunr.QueryLexer.prototype.more = function () { + return this.pos < this.length +} + +lunr.QueryLexer.EOS = 'EOS' +lunr.QueryLexer.FIELD = 'FIELD' +lunr.QueryLexer.TERM = 'TERM' +lunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE' +lunr.QueryLexer.BOOST = 'BOOST' + +lunr.QueryLexer.lexField = function (lexer) { + lexer.backup() + lexer.emit(lunr.QueryLexer.FIELD) + lexer.ignore() + return lunr.QueryLexer.lexText +} + +lunr.QueryLexer.lexTerm = function (lexer) { + if (lexer.width() > 1) { + lexer.backup() + lexer.emit(lunr.QueryLexer.TERM) + } + + lexer.ignore() + + if (lexer.more()) { + return lunr.QueryLexer.lexText + } +} + +lunr.QueryLexer.lexEditDistance = function (lexer) { + lexer.ignore() + lexer.acceptDigitRun() + lexer.emit(lunr.QueryLexer.EDIT_DISTANCE) + return lunr.QueryLexer.lexText +} + +lunr.QueryLexer.lexBoost = function (lexer) { + lexer.ignore() + lexer.acceptDigitRun() + lexer.emit(lunr.QueryLexer.BOOST) + return lunr.QueryLexer.lexText +} + +lunr.QueryLexer.lexEOS = function (lexer) { + if (lexer.width() > 0) { + lexer.emit(lunr.QueryLexer.TERM) + } +} + +// This matches the separator used when tokenising fields +// within a document. These should match otherwise it is +// not possible to search for some tokens within a document. +// +// It is possible for the user to change the separator on the +// tokenizer so it _might_ clash with any other of the special +// characters already used within the search string, e.g. :. +// +// This means that it is possible to change the separator in +// such a way that makes some words unsearchable using a search +// string. +lunr.QueryLexer.termSeparator = lunr.tokenizer.separator + +lunr.QueryLexer.lexText = function (lexer) { + while (true) { + var char = lexer.next() + + if (char == lunr.QueryLexer.EOS) { + return lunr.QueryLexer.lexEOS + } + + // Escape character is '\' + if (char.charCodeAt(0) == 92) { + lexer.escapeCharacter() + continue + } + + if (char == ":") { + return lunr.QueryLexer.lexField + } + + if (char == "~") { + lexer.backup() + if (lexer.width() > 0) { + lexer.emit(lunr.QueryLexer.TERM) + } + return lunr.QueryLexer.lexEditDistance + } + + if (char == "^") { + lexer.backup() + if (lexer.width() > 0) { + lexer.emit(lunr.QueryLexer.TERM) + } + return lunr.QueryLexer.lexBoost + } + + if (char.match(lunr.QueryLexer.termSeparator)) { + return lunr.QueryLexer.lexTerm + } + } +} + +lunr.QueryParser = function (str, query) { + this.lexer = new lunr.QueryLexer (str) + this.query = query + this.currentClause = {} + this.lexemeIdx = 0 +} + +lunr.QueryParser.prototype.parse = function () { + this.lexer.run() + this.lexemes = this.lexer.lexemes + + var state = lunr.QueryParser.parseFieldOrTerm + + while (state) { + state = state(this) + } + + return this.query +} + +lunr.QueryParser.prototype.peekLexeme = function () { + return this.lexemes[this.lexemeIdx] +} + +lunr.QueryParser.prototype.consumeLexeme = function () { + var lexeme = this.peekLexeme() + this.lexemeIdx += 1 + return lexeme +} + +lunr.QueryParser.prototype.nextClause = function () { + var completedClause = this.currentClause + this.query.clause(completedClause) + this.currentClause = {} +} + +lunr.QueryParser.parseFieldOrTerm = function (parser) { + var lexeme = parser.peekLexeme() + + if (lexeme == undefined) { + return + } + + switch (lexeme.type) { + case lunr.QueryLexer.FIELD: + return lunr.QueryParser.parseField + case lunr.QueryLexer.TERM: + return lunr.QueryParser.parseTerm + default: + var errorMessage = "expected either a field or a term, found " + lexeme.type + + if (lexeme.str.length >= 1) { + errorMessage += " with value '" + lexeme.str + "'" + } + + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } +} + +lunr.QueryParser.parseField = function (parser) { + var lexeme = parser.consumeLexeme() + + if (lexeme == undefined) { + return + } + + if (parser.query.allFields.indexOf(lexeme.str) == -1) { + var possibleFields = parser.query.allFields.map(function (f) { return "'" + f + "'" }).join(', '), + errorMessage = "unrecognised field '" + lexeme.str + "', possible fields: " + possibleFields + + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + parser.currentClause.fields = [lexeme.str] + + var nextLexeme = parser.peekLexeme() + + if (nextLexeme == undefined) { + var errorMessage = "expecting term, found nothing" + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + switch (nextLexeme.type) { + case lunr.QueryLexer.TERM: + return lunr.QueryParser.parseTerm + default: + var errorMessage = "expecting term, found '" + nextLexeme.type + "'" + throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) + } +} + +lunr.QueryParser.parseTerm = function (parser) { + var lexeme = parser.consumeLexeme() + + if (lexeme == undefined) { + return + } + + parser.currentClause.term = lexeme.str.toLowerCase() + + if (lexeme.str.indexOf("*") != -1) { + parser.currentClause.usePipeline = false + } + + var nextLexeme = parser.peekLexeme() + + if (nextLexeme == undefined) { + parser.nextClause() + return + } + + switch (nextLexeme.type) { + case lunr.QueryLexer.TERM: + parser.nextClause() + return lunr.QueryParser.parseTerm + case lunr.QueryLexer.FIELD: + parser.nextClause() + return lunr.QueryParser.parseField + case lunr.QueryLexer.EDIT_DISTANCE: + return lunr.QueryParser.parseEditDistance + case lunr.QueryLexer.BOOST: + return lunr.QueryParser.parseBoost + default: + var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'" + throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) + } +} + +lunr.QueryParser.parseEditDistance = function (parser) { + var lexeme = parser.consumeLexeme() + + if (lexeme == undefined) { + return + } + + var editDistance = parseInt(lexeme.str, 10) + + if (isNaN(editDistance)) { + var errorMessage = "edit distance must be numeric" + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + parser.currentClause.editDistance = editDistance + + var nextLexeme = parser.peekLexeme() + + if (nextLexeme == undefined) { + parser.nextClause() + return + } + + switch (nextLexeme.type) { + case lunr.QueryLexer.TERM: + parser.nextClause() + return lunr.QueryParser.parseTerm + case lunr.QueryLexer.FIELD: + parser.nextClause() + return lunr.QueryParser.parseField + case lunr.QueryLexer.EDIT_DISTANCE: + return lunr.QueryParser.parseEditDistance + case lunr.QueryLexer.BOOST: + return lunr.QueryParser.parseBoost + default: + var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'" + throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) + } +} + +lunr.QueryParser.parseBoost = function (parser) { + var lexeme = parser.consumeLexeme() + + if (lexeme == undefined) { + return + } + + var boost = parseInt(lexeme.str, 10) + + if (isNaN(boost)) { + var errorMessage = "boost must be numeric" + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + parser.currentClause.boost = boost + + var nextLexeme = parser.peekLexeme() + + if (nextLexeme == undefined) { + parser.nextClause() + return + } + + switch (nextLexeme.type) { + case lunr.QueryLexer.TERM: + parser.nextClause() + return lunr.QueryParser.parseTerm + case lunr.QueryLexer.FIELD: + parser.nextClause() + return lunr.QueryParser.parseField + case lunr.QueryLexer.EDIT_DISTANCE: + return lunr.QueryParser.parseEditDistance + case lunr.QueryLexer.BOOST: + return lunr.QueryParser.parseBoost + default: + var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'" + throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) + } +} + + /** + * export the module via AMD, CommonJS or as a browser global + * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js + */ + ;(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(factory) + } else if (typeof exports === 'object') { + /** + * Node. Does not work with strict CommonJS, but + * only CommonJS-like enviroments that support module.exports, + * like Node. + */ + module.exports = factory() + } else { + // Browser globals (root is window) + root.lunr = factory() + } + }(this, function () { + /** + * Just return a value to define the module export. + * This example returns an object, but the module + * can return a function as the exported value. + */ + return lunr + })) +})(); diff --git a/docs/search.html b/docs/search.html new file mode 100644 index 000000000..ea1282b8c --- /dev/null +++ b/docs/search.html @@ -0,0 +1,17 @@ +--- +layout: default +--- + +
      +
      +{% include search-lunr.html %} + + + +
      + +
      +
      +
      \ No newline at end of file