├── resources └── locales │ ├── en │ └── default.po │ ├── default.pot │ ├── ru │ └── default.po │ ├── sv │ └── default.po │ ├── es │ └── default.po │ ├── nl │ └── default.po │ ├── fr │ └── default.po │ └── de │ └── default.po ├── .gitignore ├── sql ├── 0002.sql ├── 0004.sql ├── 0003.sql ├── 0005.sql ├── 0001.sql └── schema.sql ├── public ├── .htaccess ├── bootstrap │ ├── img │ │ ├── glyphicons-halflings.png │ │ └── glyphicons-halflings-white.png │ └── css │ │ ├── bootstrap-responsive.min.css │ │ └── bootstrap-responsive.css ├── js │ ├── timeago │ │ ├── jquery.timeago.tr.js │ │ ├── jquery.timeago.it.js │ │ ├── jquery.timeago.fr-short.js │ │ ├── jquery.timeago.hy.js │ │ ├── jquery.timeago.ja.js │ │ ├── jquery.timeago.ko.js │ │ ├── jquery.timeago.en-short.js │ │ ├── jquery.timeago.es-short.js │ │ ├── jquery.timeago.ro.js │ │ ├── jquery.timeago.pt-br-short.js │ │ ├── jquery.timeago.bg.js │ │ ├── jquery.timeago.sk.js │ │ ├── jquery.timeago.da.js │ │ ├── jquery.timeago.es.js │ │ ├── jquery.timeago.zh-CN.js │ │ ├── jquery.timeago.gl.js │ │ ├── jquery.timeago.no.js │ │ ├── jquery.timeago.pt.js │ │ ├── jquery.timeago.zh-TW.js │ │ ├── jquery.timeago.pt-br.js │ │ ├── jquery.timeago.ca.js │ │ ├── jquery.timeago.lt.js │ │ ├── jquery.timeago.de.js │ │ ├── jquery.timeago.si.js │ │ ├── jquery.timeago.sv.js │ │ ├── jquery.timeago.el.js │ │ ├── jquery.timeago.is.js │ │ ├── jquery.timeago.cy.js │ │ ├── jquery.timeago.id.js │ │ ├── jquery.timeago.rw.js │ │ ├── jquery.timeago.mk.js │ │ ├── jquery.timeago.fr.js │ │ ├── jquery.timeago.hu.js │ │ ├── jquery.timeago.jv.js │ │ ├── jquery.timeago.th.js │ │ ├── jquery.timeago.en.js │ │ ├── jquery.timeago.vi.js │ │ ├── jquery.timeago.nl.js │ │ ├── jquery.timeago.he.js │ │ ├── jquery.timeago.dv.js │ │ ├── jquery.timeago.uz.js │ │ ├── jquery.timeago.fa.js │ │ ├── jquery.timeago.pl.js │ │ ├── jquery.timeago.fi.js │ │ ├── README.md │ │ ├── jquery.timeago.et.js │ │ ├── jquery.timeago.cs.js │ │ ├── jquery.timeago.ky.js │ │ ├── jquery.timeago.ru.js │ │ ├── jquery.timeago.uk.js │ │ ├── jquery.timeago.bs.js │ │ ├── jquery.timeago.sl.js │ │ ├── jquery.timeago.rs.js │ │ ├── jquery.timeago.hr.js │ │ └── jquery.timeago.ar.js │ └── jquery.timeago.js ├── index.php └── css │ └── style.css ├── test.php ├── views ├── post.php ├── webmention-error.php ├── members.php ├── posts.php ├── index.php ├── webmention.php ├── _post-row.php ├── submit.php ├── layout.php ├── calendar.php ├── comment-full.php └── submit-full.php ├── README.md ├── composer.json ├── controllers ├── middleware.php ├── static.php ├── controllers.php └── webmention.php ├── lib ├── config.template.php ├── mf2.php └── helpers.php └── LICENSE.txt /resources/locales/en/default.po: -------------------------------------------------------------------------------- 1 | msgid "submitted ... from" 2 | msgstr "from" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.phar 3 | lib/config.php 4 | .DS_Store 5 | .idea/ 6 | -------------------------------------------------------------------------------- /sql/0002.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE posts 2 | ADD COLUMN `lang` char(2) DEFAULT 'en' AFTER id; 3 | -------------------------------------------------------------------------------- /sql/0004.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE posts 2 | ADD COLUMN `deleted` TINYINT(4) NOT NULL DEFAULT '0'; 3 | 4 | 5 | -------------------------------------------------------------------------------- /sql/0003.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE posts 2 | ADD COLUMN `tzoffset` int(11) NOT NULL DEFAULT 0 AFTER post_date; 3 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{REQUEST_FILENAME} !-f 3 | RewriteRule ^ index.php [QSA,L] 4 | -------------------------------------------------------------------------------- /public/bootstrap/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronpk/IndieNews/main/public/bootstrap/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /public/bootstrap/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronpk/IndieNews/main/public/bootstrap/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /test.php: -------------------------------------------------------------------------------- 1 | 2 | fetch('_post-row.php', [ 3 | 'post' => $post, 4 | 'position' => '', 5 | 'view' => 'single' 6 | ]) ?> 7 | 8 | -------------------------------------------------------------------------------- /views/webmention-error.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | ! 6 |
7 | 8 |

:

9 |

10 | 11 |
12 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IndieNews 2 | 3 | This site is live on https://news.indieweb.org. It is a news aggregator for [IndieWeb](https://indieweb.org)-related posts. 4 | 5 | To submit one of your posts, link to https://news.indieweb.org/en from one the post and send a [Webmention](https://indieweb.org/webmention) to https://news.indieweb.org/webmention. 6 | 7 | [Full instructions](https://news.indieweb.org/how-to-submit-a-post) 8 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.tr.js: -------------------------------------------------------------------------------- 1 | // Turkish 2 | jQuery.timeago.settings.strings = { 3 | suffixAgo: 'önce', 4 | suffixFromNow: null, 5 | seconds: '1 dakikadan', 6 | minute: '1 dakika', 7 | minutes: '%d dakika', 8 | hour: '1 saat', 9 | hours: '%d saat', 10 | day: '1 gün', 11 | days: '%d gün', 12 | month: '1 ay', 13 | months: '%d ay', 14 | year: '1 yıl', 15 | years: '%d yıl' 16 | }; 17 | -------------------------------------------------------------------------------- /sql/0005.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `blocks` ( 2 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 3 | `domain` varchar(512) DEFAULT NULL, 4 | `source_url` varchar(512) DEFAULT NULL, 5 | `date_created` datetime DEFAULT NULL, 6 | `note` longtext DEFAULT NULL, 7 | PRIMARY KEY (`id`), 8 | KEY `domain` (`domain`), 9 | KEY `source_url` (`source_url`) 10 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 11 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.it.js: -------------------------------------------------------------------------------- 1 | // Italian 2 | jQuery.timeago.settings.strings = { 3 | suffixAgo: "fa", 4 | suffixFromNow: "da ora", 5 | seconds: "meno di un minuto", 6 | minute: "circa un minuto", 7 | minutes: "%d minuti", 8 | hour: "circa un'ora", 9 | hours: "circa %d ore", 10 | day: "un giorno", 11 | days: "%d giorni", 12 | month: "circa un mese", 13 | months: "%d mesi", 14 | year: "circa un anno", 15 | years: "%d anni" 16 | }; -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.fr-short.js: -------------------------------------------------------------------------------- 1 | // French shortened 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: "il y a", 4 | prefixFromNow: "d'ici", 5 | seconds: "moins d'une minute", 6 | minute: "une minute", 7 | minutes: "%d minutes", 8 | hour: "une heure", 9 | hours: "%d heures", 10 | day: "un jour", 11 | days: "%d jours", 12 | month: "un mois", 13 | months: "%d mois", 14 | year: "un an", 15 | years: "%d ans" 16 | }; -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.hy.js: -------------------------------------------------------------------------------- 1 | // Armenian 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: null, 5 | suffixAgo: "առաջ", 6 | suffixFromNow: "հետո", 7 | seconds: "վայրկյաններ", 8 | minute: "մեկ րոպե", 9 | minutes: "%d րոպե", 10 | hour: "մեկ ժամ", 11 | hours: "%d ժամ", 12 | day: "մեկ օր", 13 | days: "%d օր", 14 | month: "մեկ ամիս", 15 | months: "%d ամիս", 16 | year: "մեկ տարի", 17 | years: "%d տարի" 18 | }; -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "slim/slim": "^4", 4 | "j4mie/idiorm": "1.3.*", 5 | "cakephp/i18n": "^5.0", 6 | "tantek/cassis": "dev-main", 7 | "p3k/utils": "^1.0", 8 | "p3k/xray": "^1.15", 9 | "mf2/mf2": "^0.4.6", 10 | "p3k/http": "^0.1.8", 11 | "slim/php-view": "^3.2", 12 | "slim/psr7": "^1.6" 13 | }, 14 | "autoload": { 15 | "files": [ 16 | "lib/helpers.php", 17 | "lib/mf2.php" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.ja.js: -------------------------------------------------------------------------------- 1 | // Japanese 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: "", 4 | prefixFromNow: "今から", 5 | suffixAgo: "前", 6 | suffixFromNow: "後", 7 | seconds: "1 分未満", 8 | minute: "約 1 分", 9 | minutes: "%d 分", 10 | hour: "約 1 時間", 11 | hours: "約 %d 時間", 12 | day: "約 1 日", 13 | days: "約 %d 日", 14 | month: "約 1 月", 15 | months: "約 %d 月", 16 | year: "約 1 年", 17 | years: "約 %d 年", 18 | wordSeparator: "" 19 | }; 20 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.ko.js: -------------------------------------------------------------------------------- 1 | // Korean 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: null, 5 | suffixAgo: "전", 6 | suffixFromNow: "후", 7 | seconds: "1분", 8 | minute: "약 1분", 9 | minutes: "%d분", 10 | hour: "약 1시간", 11 | hours: "약 %d시간", 12 | day: "하루", 13 | days: "%d일", 14 | month: "약 1개월", 15 | months: "%d개월", 16 | year: "약 1년", 17 | years: "%d년", 18 | wordSeparator: " ", 19 | numbers: [] 20 | }; 21 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.en-short.js: -------------------------------------------------------------------------------- 1 | // English shortened 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: null, 5 | suffixAgo: "", 6 | suffixFromNow: "", 7 | seconds: "1m", 8 | minute: "1m", 9 | minutes: "%dm", 10 | hour: "1h", 11 | hours: "%dh", 12 | day: "1d", 13 | days: "%dd", 14 | month: "1mo", 15 | months: "%dmo", 16 | year: "1yr", 17 | years: "%dyr", 18 | wordSeparator: " ", 19 | numbers: [] 20 | }; 21 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.es-short.js: -------------------------------------------------------------------------------- 1 | // Spanish shortened 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: null, 5 | suffixAgo: "", 6 | suffixFromNow: "", 7 | seconds: "1m", 8 | minute: "1m", 9 | minutes: "%dm", 10 | hour: "1h", 11 | hours: "%dh", 12 | day: "1d", 13 | days: "%dd", 14 | month: "1me", 15 | months: "%dme", 16 | year: "1a", 17 | years: "%da", 18 | wordSeparator: " ", 19 | numbers: [] 20 | }; 21 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.ro.js: -------------------------------------------------------------------------------- 1 | // Romanian 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: "acum", 4 | prefixFromNow: "in timp de", 5 | suffixAgo: "", 6 | suffixFromNow: "", 7 | seconds: "mai putin de un minut", 8 | minute: "un minut", 9 | minutes: "%d minute", 10 | hour: "o ora", 11 | hours: "%d ore", 12 | day: "o zi", 13 | days: "%d zile", 14 | month: "o luna", 15 | months: "%d luni", 16 | year: "un an", 17 | years: "%d ani" 18 | }; 19 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.pt-br-short.js: -------------------------------------------------------------------------------- 1 | // Portuguese Brasil shortened 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: null, 5 | suffixAgo: "", 6 | suffixFromNow: "", 7 | seconds: "1m", 8 | minute: "1m", 9 | minutes: "%dm", 10 | hour: "1h", 11 | hours: "%dh", 12 | day: "1d", 13 | days: "%dd", 14 | month: "1M", 15 | months: "%dM", 16 | year: "1a", 17 | years: "%da", 18 | wordSeparator: " ", 19 | numbers: [] 20 | }; 21 | -------------------------------------------------------------------------------- /views/members.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

5 | 6 |

7 | 8 | 13 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.bg.js: -------------------------------------------------------------------------------- 1 | // Bulgarian 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: "преди", 4 | prefixFromNow: "след", 5 | suffixAgo: null, 6 | suffixFromNow: null, 7 | seconds: "по-малко от минута", 8 | minute: "една минута", 9 | minutes: "%d минути", 10 | hour: "един час", 11 | hours: "%d часа", 12 | day: "един ден", 13 | days: "%d дни", 14 | month: "един месец", 15 | months: "%d месеца", 16 | year: "една година", 17 | years: "%d години" 18 | }; -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.sk.js: -------------------------------------------------------------------------------- 1 | // Slovak 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: "pred", 4 | prefixFromNow: null, 5 | suffixAgo: null, 6 | suffixFromNow: null, 7 | seconds: "menej než minútou", 8 | minute: "minútou", 9 | minutes: "%d minútami", 10 | hour: "hodinou", 11 | hours: "%d hodinami", 12 | day: "1 dňom", 13 | days: "%d dňami", 14 | month: "1 mesiacom", 15 | months: "%d mesiacmi", 16 | year: "1 rokom", 17 | years: "%d rokmi" 18 | }; 19 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.da.js: -------------------------------------------------------------------------------- 1 | // Danish 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: "for", 4 | prefixFromNow: "om", 5 | suffixAgo: "siden", 6 | suffixFromNow: "", 7 | seconds: "mindre end et minut", 8 | minute: "ca. et minut", 9 | minutes: "%d minutter", 10 | hour: "ca. en time", 11 | hours: "ca. %d timer", 12 | day: "en dag", 13 | days: "%d dage", 14 | month: "ca. en måned", 15 | months: "%d måneder", 16 | year: "ca. et år", 17 | years: "%d år" 18 | }; -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.es.js: -------------------------------------------------------------------------------- 1 | // Spanish 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: "hace", 4 | prefixFromNow: "dentro de", 5 | suffixAgo: "", 6 | suffixFromNow: "", 7 | seconds: "menos de un minuto", 8 | minute: "un minuto", 9 | minutes: "unos %d minutos", 10 | hour: "una hora", 11 | hours: "%d horas", 12 | day: "un día", 13 | days: "%d días", 14 | month: "un mes", 15 | months: "%d meses", 16 | year: "un año", 17 | years: "%d años" 18 | }; -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.zh-CN.js: -------------------------------------------------------------------------------- 1 | // Simplified Chinese 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: "从现在开始", 5 | suffixAgo: "之前", 6 | suffixFromNow: null, 7 | seconds: "不到1分钟", 8 | minute: "大约1分钟", 9 | minutes: "%d分钟", 10 | hour: "大约1小时", 11 | hours: "大约%d小时", 12 | day: "1天", 13 | days: "%d天", 14 | month: "大约1个月", 15 | months: "%d月", 16 | year: "大约1年", 17 | years: "%d年", 18 | numbers: [], 19 | wordSeparator: "" 20 | }; 21 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.gl.js: -------------------------------------------------------------------------------- 1 | // Galician 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: "hai", 4 | prefixFromNow: "dentro de", 5 | suffixAgo: "", 6 | suffixFromNow: "", 7 | seconds: "menos dun minuto", 8 | minute: "un minuto", 9 | minutes: "uns %d minutos", 10 | hour: "unha hora", 11 | hours: "%d horas", 12 | day: "un día", 13 | days: "%d días", 14 | month: "un mes", 15 | months: "%d meses", 16 | year: "un ano", 17 | years: "%d anos" 18 | }; 19 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.no.js: -------------------------------------------------------------------------------- 1 | // Norwegian 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: "for", 4 | prefixFromNow: "om", 5 | suffixAgo: "siden", 6 | suffixFromNow: "", 7 | seconds: "mindre enn et minutt", 8 | minute: "ca. et minutt", 9 | minutes: "%d minutter", 10 | hour: "ca. en time", 11 | hours: "ca. %d timer", 12 | day: "en dag", 13 | days: "%d dager", 14 | month: "ca. en måned", 15 | months: "%d måneder", 16 | year: "ca. et år", 17 | years: "%d år" 18 | }; -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.pt.js: -------------------------------------------------------------------------------- 1 | // Portuguese 2 | jQuery.timeago.settings.strings = { 3 | suffixAgo: "atrás", 4 | suffixFromNow: "a partir de agora", 5 | seconds: "menos de um minuto", 6 | minute: "cerca de um minuto", 7 | minutes: "%d minutos", 8 | hour: "cerca de uma hora", 9 | hours: "cerca de %d horas", 10 | day: "um dia", 11 | days: "%d dias", 12 | month: "cerca de um mês", 13 | months: "%d meses", 14 | year: "cerca de um ano", 15 | years: "%d anos" 16 | }; -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.zh-TW.js: -------------------------------------------------------------------------------- 1 | // Traditional Chinese, zh-tw 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: "從現在開始", 5 | suffixAgo: "之前", 6 | suffixFromNow: null, 7 | seconds: "不到1分鐘", 8 | minute: "大約1分鐘", 9 | minutes: "%d分鐘", 10 | hour: "大約1小時", 11 | hours: "%d小時", 12 | day: "大約1天", 13 | days: "%d天", 14 | month: "大約1個月", 15 | months: "%d個月", 16 | year: "大約1年", 17 | years: "%d年", 18 | numbers: [], 19 | wordSeparator: "" 20 | }; 21 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.pt-br.js: -------------------------------------------------------------------------------- 1 | // Brazilian Portuguese 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: "há", 4 | prefixFromNow: "em", 5 | suffixAgo: null, 6 | suffixFromNow: null, 7 | seconds: "alguns segundos", 8 | minute: "um minuto", 9 | minutes: "%d minutos", 10 | hour: "uma hora", 11 | hours: "%d horas", 12 | day: "um dia", 13 | days: "%d dias", 14 | month: "um mês", 15 | months: "%d meses", 16 | year: "um ano", 17 | years: "%d anos" 18 | }; 19 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.ca.js: -------------------------------------------------------------------------------- 1 | // Catalan 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: "fa", 4 | prefixFromNow: "d'aqui a", 5 | suffixAgo: null, 6 | suffixFromNow: null, 7 | seconds: "menys d'1 minut", 8 | minute: "1 minut", 9 | minutes: "uns %d minuts", 10 | hour: "1 hora", 11 | hours: "unes %d hores", 12 | day: "1 dia", 13 | days: "%d dies", 14 | month: "aproximadament un mes", 15 | months: "%d mesos", 16 | year: "aproximadament un any", 17 | years: "%d anys" 18 | }; -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.lt.js: -------------------------------------------------------------------------------- 1 | //Lithuanian 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: "prieš", 4 | prefixFromNow: null, 5 | suffixAgo: null, 6 | suffixFromNow: "nuo dabar", 7 | seconds: "%d sek.", 8 | minute: "min.", 9 | minutes: "%d min.", 10 | hour: "val.", 11 | hours: "%d val.", 12 | day: "1 d.", 13 | days: "%d d.", 14 | month: "mėn.", 15 | months: "%d mėn.", 16 | year: "metus", 17 | years: "%d metus", 18 | wordSeparator: " ", 19 | numbers: [] 20 | }; 21 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.de.js: -------------------------------------------------------------------------------- 1 | // German 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: "vor", 4 | prefixFromNow: "in", 5 | suffixAgo: "", 6 | suffixFromNow: "", 7 | seconds: "wenigen Sekunden", 8 | minute: "etwa einer Minute", 9 | minutes: "%d Minuten", 10 | hour: "etwa einer Stunde", 11 | hours: "%d Stunden", 12 | day: "etwa einem Tag", 13 | days: "%d Tagen", 14 | month: "etwa einem Monat", 15 | months: "%d Monaten", 16 | year: "etwa einem Jahr", 17 | years: "%d Jahren" 18 | }; -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.si.js: -------------------------------------------------------------------------------- 1 | // Sinhalese (SI) 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: null, 5 | suffixAgo: "පෙර", 6 | suffixFromNow: "පසුව", 7 | seconds: "තත්පර කිහිපයකට", 8 | minute: "මිනිත්තුවකට පමණ", 9 | minutes: "මිනිත්තු %d කට", 10 | hour: "පැයක් පමණ ", 11 | hours: "පැය %d කට පමණ", 12 | day: "දවසක ට", 13 | days: "දවස් %d කට ", 14 | month: "මාසයක් පමණ", 15 | months: "මාස %d කට", 16 | year: "වසරක් පමණ", 17 | years: "වසරක් %d කට පමණ" 18 | }; -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.sv.js: -------------------------------------------------------------------------------- 1 | // Swedish 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: "för", 4 | prefixFromNow: "om", 5 | suffixAgo: "sedan", 6 | suffixFromNow: "", 7 | seconds: "mindre än en minut", 8 | minute: "ungefär en minut", 9 | minutes: "%d minuter", 10 | hour: "ungefär en timme", 11 | hours: "ungefär %d timmar", 12 | day: "en dag", 13 | days: "%d dagar", 14 | month: "ungefär en månad", 15 | months: "%d månader", 16 | year: "ungefär ett år", 17 | years: "%d år" 18 | }; -------------------------------------------------------------------------------- /controllers/middleware.php: -------------------------------------------------------------------------------- 1 | addRoutingMiddleware(); 4 | 5 | $errorMiddleware = $app->addErrorMiddleware(true, true, true); 6 | 7 | // Set the Not Found Handler 8 | $errorMiddleware->setErrorHandler( 9 | HttpNotFoundException::class, 10 | function (ServerRequestInterface $request, Throwable $exception, bool $displayErrorDetails) { 11 | 12 | $response = new Response(); 13 | $response->getBody()->write('404 Not Found'); 14 | 15 | return $response->withStatus(404); 16 | 17 | } 18 | ); 19 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.el.js: -------------------------------------------------------------------------------- 1 | // Greek 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: "πριν", 4 | prefixFromNow: "σε", 5 | suffixAgo: "", 6 | suffixFromNow: "", 7 | seconds: "λιγότερο από ένα λεπτό", 8 | minute: "περίπου ένα λεπτό", 9 | minutes: "%d λεπτά", 10 | hour: "περίπου μία ώρα", 11 | hours: "περίπου %d ώρες", 12 | day: "μία μέρα", 13 | days: "%d μέρες", 14 | month: "περίπου ένα μήνα", 15 | months: "%d μήνες", 16 | year: "περίπου ένα χρόνο", 17 | years: "%d χρόνια" 18 | }; -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.is.js: -------------------------------------------------------------------------------- 1 | jQuery.timeago.settings.strings = { 2 | prefixAgo: "fyrir", 3 | prefixFromNow: "eftir", 4 | suffixAgo: "síðan", 5 | suffixFromNow: null, 6 | seconds: "minna en mínútu", 7 | minute: "mínútu", 8 | minutes: "%d mínútum", 9 | hour: "klukkutíma", 10 | hours: "um %d klukkutímum", 11 | day: "degi", 12 | days: "%d dögum", 13 | month: "mánuði", 14 | months: "%d mánuðum", 15 | year: "ári", 16 | years: "%d árum", 17 | wordSeparator: " ", 18 | numbers: [] 19 | }; 20 | -------------------------------------------------------------------------------- /sql/0001.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE posts 2 | DROP COLUMN parent_id, 3 | CHANGE COLUMN domain post_author VARCHAR(255) DEFAULT NULL, 4 | ADD COLUMN in_reply_to VARCHAR(512) DEFAULT NULL, 5 | DROP COLUMN points, 6 | DROP COLUMN score, 7 | ADD COLUMN source_url VARCHAR(512) DEFAULT NULL; 8 | 9 | UPDATE posts 10 | SET source_url = href; 11 | 12 | UPDATE posts 13 | SET post_author = CONCAT("http://", post_author); 14 | 15 | DROP TABLE last_computed; 16 | 17 | ALTER TABLE users 18 | CHANGE COLUMN domain url VARCHAR(255) DEFAULT NULL; 19 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.cy.js: -------------------------------------------------------------------------------- 1 | // Welsh 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: null, 5 | suffixAgo: "yn ôl", 6 | suffixFromNow: "o hyn", 7 | seconds: "llai na munud", 8 | minute: "am funud", 9 | minutes: "%d munud", 10 | hour: "tua awr", 11 | hours: "am %d awr", 12 | day: "y dydd", 13 | days: "%d diwrnod", 14 | month: "tua mis", 15 | months: "%d mis", 16 | year: "am y flwyddyn", 17 | years: "%d blynedd", 18 | wordSeparator: " ", 19 | numbers: [] 20 | }; 21 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.id.js: -------------------------------------------------------------------------------- 1 | // Indonesian 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: null, 5 | suffixAgo: "yang lalu", 6 | suffixFromNow: "dari sekarang", 7 | seconds: "kurang dari semenit", 8 | minute: "sekitar satu menit", 9 | minutes: "%d menit", 10 | hour: "sekitar sejam", 11 | hours: "sekitar %d jam", 12 | day: "sehari", 13 | days: "%d hari", 14 | month: "sekitar sebulan", 15 | months: "%d bulan", 16 | year: "sekitar setahun", 17 | years: "%d tahun" 18 | }; 19 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.rw.js: -------------------------------------------------------------------------------- 1 | // Kinyarwanda 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: "hashize", 4 | prefixFromNow: "mu", 5 | suffixAgo: null, 6 | suffixFromNow: null, 7 | seconds: "amasegonda macye", 8 | minute: "umunota", 9 | minutes: "iminota %d", 10 | hour: "isaha", 11 | hours: "amasaha %d", 12 | day: "umunsi", 13 | days: "iminsi %d", 14 | month: "ukwezi", 15 | months: "amezi %d", 16 | year: "umwaka", 17 | years: "imyaka %d", 18 | wordSeparator: " ", 19 | numbers: [] 20 | }; 21 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.mk.js: -------------------------------------------------------------------------------- 1 | // Macedonian 2 | (function() { 3 | jQuery.timeago.settings.strings={ 4 | prefixAgo: "пред", 5 | prefixFromNow: "за", 6 | suffixAgo: null, 7 | suffixFromNow: null, 8 | seconds: "%d секунди", 9 | minute: "%d минута", 10 | minutes: "%d минути", 11 | hour: "%d час", 12 | hours: "%d часа", 13 | day: "%d ден", 14 | days: "%d денови" , 15 | month: "%d месец", 16 | months: "%d месеци", 17 | year: "%d година", 18 | years: "%d години" 19 | }; 20 | })(); 21 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.fr.js: -------------------------------------------------------------------------------- 1 | // French 2 | jQuery.timeago.settings.strings = { 3 | // environ ~= about, it's optional 4 | prefixAgo: "il y a", 5 | prefixFromNow: "d'ici", 6 | seconds: "moins d'une minute", 7 | minute: "environ une minute", 8 | minutes: "environ %d minutes", 9 | hour: "environ une heure", 10 | hours: "environ %d heures", 11 | day: "environ un jour", 12 | days: "environ %d jours", 13 | month: "environ un mois", 14 | months: "environ %d mois", 15 | year: "un an", 16 | years: "%d ans" 17 | }; -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.hu.js: -------------------------------------------------------------------------------- 1 | // Hungarian 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: null, 5 | suffixAgo: null, 6 | suffixFromNow: null, 7 | seconds: "kevesebb mint egy perce", 8 | minute: "körülbelül egy perce", 9 | minutes: "%d perce", 10 | hour: "körülbelül egy órája", 11 | hours: "körülbelül %d órája", 12 | day: "körülbelül egy napja", 13 | days: "%d napja", 14 | month: "körülbelül egy hónapja", 15 | months: "%d hónapja", 16 | year: "körülbelül egy éve", 17 | years: "%d éve" 18 | }; -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.jv.js: -------------------------------------------------------------------------------- 1 | // Javanesse (Boso Jowo) 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: null, 5 | suffixAgo: "kepungkur", 6 | suffixFromNow: "seko saiki", 7 | seconds: "kurang seko sakmenit", 8 | minute: "kurang luwih sakmenit", 9 | minutes: "%d menit", 10 | hour: "kurang luwih sakjam", 11 | hours: "kurang luwih %d jam", 12 | day: "sedina", 13 | days: "%d dina", 14 | month: "kurang luwih sewulan", 15 | months: "%d wulan", 16 | year: "kurang luwih setahun", 17 | years: "%d tahun" 18 | }; 19 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.th.js: -------------------------------------------------------------------------------- 1 | // Thai 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: null, 5 | suffixAgo: "ที่แล้ว", 6 | suffixFromNow: "จากตอนนี้", 7 | seconds: "น้อยกว่าหนึ่งนาที", 8 | minute: "ประมาณหนึ่งนาที", 9 | minutes: "%d นาที", 10 | hour: "ประมาณหนึ่งชั่วโมง", 11 | hours: "ประมาณ %d ชั่วโมง", 12 | day: "หนึ่งวัน", 13 | days: "%d วัน", 14 | month: "ประมาณหนึ่งเดือน", 15 | months: "%d เดือน", 16 | year: "ประมาณหนึ่งปี", 17 | years: "%d ปี", 18 | wordSeparator: "", 19 | numbers: [] 20 | }; 21 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.en.js: -------------------------------------------------------------------------------- 1 | // English (Template) 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: null, 5 | suffixAgo: "ago", 6 | suffixFromNow: "from now", 7 | seconds: "less than a minute", 8 | minute: "about a minute", 9 | minutes: "%d minutes", 10 | hour: "about an hour", 11 | hours: "about %d hours", 12 | day: "a day", 13 | days: "%d days", 14 | month: "about a month", 15 | months: "%d months", 16 | year: "about a year", 17 | years: "%d years", 18 | wordSeparator: " ", 19 | numbers: [] 20 | }; 21 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.vi.js: -------------------------------------------------------------------------------- 1 | // Vietnamese 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: 'cách đây', 4 | prefixFromNow: null, 5 | suffixAgo: null, 6 | suffixFromNow: "trước", 7 | seconds: "chưa đến một phút", 8 | minute: "khoảng một phút", 9 | minutes: "%d phút", 10 | hour: "khoảng một tiếng", 11 | hours: "khoảng %d tiếng", 12 | day: "một ngày", 13 | days: "%d ngày", 14 | month: "khoảng một tháng", 15 | months: "%d tháng", 16 | year: "khoảng một năm", 17 | years: "%d năm", 18 | wordSeparator: " ", 19 | numbers: [] 20 | }; 21 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.nl.js: -------------------------------------------------------------------------------- 1 | // Dutch 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: "over", 5 | suffixAgo: "geleden", 6 | suffixFromNow: null, 7 | seconds: "minder dan een minuut", 8 | minute: "ongeveer een minuut", 9 | minutes: "%d minuten", 10 | hour: "ongeveer een uur", 11 | hours: "ongeveer %d uur", 12 | day: "een dag", 13 | days: "%d dagen", 14 | month: "ongeveer een maand", 15 | months: "%d maanden", 16 | year: "ongeveer een jaar", 17 | years: "%d jaar", 18 | wordSeparator: " ", 19 | numbers: [] 20 | }; 21 | -------------------------------------------------------------------------------- /lib/config.template.php: -------------------------------------------------------------------------------- 1 | get('/', function($request, $response) { 3 | return render($response, 'index', array( 4 | 'title' => 'IndieNews homepage', 5 | 'meta' => '', 6 | 'lang' => 'en' 7 | )); 8 | }); 9 | 10 | $app->get('/how-to-submit-a-post', function($request, $response) use($app) { 11 | return render($response, 'submit-full', array( 12 | 'title' => 'IndieNews - How to submit a post', 13 | 'meta' => '', 14 | 'lang' => 'en' 15 | )); 16 | }); 17 | 18 | $app->redirect('/how', '/how-to-submit-a-post', 301); 19 | $app->redirect('/how-to-comment', '/how-to-submit-a-post', 301); 20 | $app->redirect('/constructing-post-urls', '/how-to-submit-a-post', 301); 21 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 'SET NAMES utf8mb4')); 13 | 14 | $app = AppFactory::create(); 15 | 16 | require 'controllers/middleware.php'; 17 | require 'controllers/static.php'; 18 | require 'controllers/webmention.php'; 19 | require 'controllers/controllers.php'; 20 | 21 | session_start(); 22 | 23 | $app->run(); 24 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.pl.js: -------------------------------------------------------------------------------- 1 | // Polish 2 | (function() { 3 | function numpf(n, s, t) { 4 | // s - 2-4, 22-24, 32-34 ... 5 | // t - 5-21, 25-31, ... 6 | var n10 = n % 10; 7 | if ( (n10 > 1) && (n10 < 5) && ( (n > 20) || (n < 10) ) ) { 8 | return s; 9 | } else { 10 | return t; 11 | } 12 | } 13 | 14 | jQuery.timeago.settings.strings = { 15 | prefixAgo: null, 16 | prefixFromNow: "za", 17 | suffixAgo: "temu", 18 | suffixFromNow: null, 19 | seconds: "mniej niż minutę", 20 | minute: "minutę", 21 | minutes: function(value) { return numpf(value, "%d minuty", "%d minut"); }, 22 | hour: "godzinę", 23 | hours: function(value) { return numpf(value, "%d godziny", "%d godzin"); }, 24 | day: "dzień", 25 | days: "%d dni", 26 | month: "miesiąc", 27 | months: function(value) { return numpf(value, "%d miesiące", "%d miesięcy"); }, 28 | year: "rok", 29 | years: function(value) { return numpf(value, "%d lata", "%d lat"); } 30 | }; 31 | })(); -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.fi.js: -------------------------------------------------------------------------------- 1 | // Finnish 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: null, 5 | suffixAgo: "sitten", 6 | suffixFromNow: "tulevaisuudessa", 7 | seconds: "alle minuutti", 8 | minute: "minuutti", 9 | minutes: "%d minuuttia", 10 | hour: "tunti", 11 | hours: "%d tuntia", 12 | day: "päivä", 13 | days: "%d päivää", 14 | month: "kuukausi", 15 | months: "%d kuukautta", 16 | year: "vuosi", 17 | years: "%d vuotta" 18 | }; 19 | 20 | // The above is not a great localization because one would usually 21 | // write "2 days ago" in Finnish as "2 päivää sitten", however 22 | // one would write "2 days into the future" as "2:n päivän päästä" 23 | // which cannot be achieved with localization support this simple. 24 | // This is because Finnish has word suffixes (attached directly 25 | // to the end of the word). The word "day" is "päivä" in Finnish. 26 | // As workaround, the above localizations will say 27 | // "2 päivää tulevaisuudessa" which is understandable but 28 | // not as fluent. -------------------------------------------------------------------------------- /public/js/timeago/README.md: -------------------------------------------------------------------------------- 1 | # Locale override examples for timeago 2 | 3 | You can represent time statements in most western languages where 4 | a prefix and/or suffix is used. 5 | 6 | The default case is to use suffix only (as in English), which you 7 | do by providing the `suffixAgo` and `suffixFromNow` settings in 8 | the strings hash (earlier versions of timeago used the deprecated 9 | `ago` and `fromNow` options). If present, they are used. 10 | 11 | 2 minutes [suffixAgo] 12 | 2 minutes [suffixFromNow] 13 | 14 | In case you want to use prefix only instead of 15 | suffix (e.g. Greek), you provide the `prefixAgo` and 16 | `prefixFromNow` options in the strings hash and leave `suffixAgo` 17 | and `suffixFromNow` empty or null. 18 | 19 | [prefixAgo] 2 minutes 20 | [prefixFromNow] 2 minutes 21 | 22 | For languages where you want to use a prefix only for future 23 | tense and prefix/suffix for past tense (for example swedish), you 24 | can combine the prefix and suffixes as needed. 25 | 26 | [prefixAgo] 2 minutes [suffixAgo] 27 | [prefixFromNow] 2 minutes 28 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.et.js: -------------------------------------------------------------------------------- 1 | // Estonian 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: null, 5 | suffixAgo: "tagasi", 6 | suffixFromNow: "pärast", 7 | seconds: function(n, d) { return d < 0 ? "vähem kui minuti aja" : "vähem kui minut aega"; }, 8 | minute: function(n, d) { return d < 0 ? "umbes minuti aja" : "umbes minut aega"; }, 9 | minutes: function(n, d) { return d < 0 ? "%d minuti" : "%d minutit"; }, 10 | hour: function(n, d) { return d < 0 ? "umbes tunni aja" : "umbes tund aega"; }, 11 | hours: function(n, d) { return d < 0 ? "%d tunni" : "%d tundi"; }, 12 | day: function(n, d) { return d < 0 ? "umbes päeva" : "umbes päev"; }, 13 | days: function(n, d) { return d < 0 ? "%d päeva" : "%d päeva"; }, 14 | month: function(n, d) { return d < 0 ? "umbes kuu aja" : "umbes kuu aega"; }, 15 | months: function(n, d) { return d < 0 ? "%d kuu" : "%d kuud"; }, 16 | year: function(n, d) { return d < 0 ? "umbes aasta aja" : "umbes aasta aega"; }, 17 | years: function(n, d) { return d < 0 ? "%d aasta" : "%d aastat"; } 18 | }; 19 | -------------------------------------------------------------------------------- /views/posts.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

5 |
6 | 7 | 8 | $post): ?> 9 | fetch('_post-row.php', [ 10 | 'post' => $post, 11 | 'position' => $i+1, 12 | 'view' => 'list']) 13 | ?> 14 | 15 |
16 |
17 | 18 | 19 |
20 |

21 |

','']) ?>

22 |
23 | 24 | 25 | 26 |
27 | 28 |
29 | 30 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.cs.js: -------------------------------------------------------------------------------- 1 | // Czech 2 | (function() { 3 | function f(n, d, a) { 4 | return a[d>=0 ? 0 : a.length==2 || n<5 ? 1 : 2]; 5 | } 6 | 7 | jQuery.timeago.settings.strings = { 8 | prefixAgo: 'před', 9 | prefixFromNow: 'za', 10 | suffixAgo: null, 11 | suffixFromNow: null, 12 | seconds: function(n, d) {return f(n, d, ['méně než minutou', 'méně než minutu']);}, 13 | minute: function(n, d) {return f(n, d, ['minutou', 'minutu']);}, 14 | minutes: function(n, d) {return f(n, d, ['%d minutami', '%d minuty', '%d minut']);}, 15 | hour: function(n, d) {return f(n, d, ['hodinou', 'hodinu']);}, 16 | hours: function(n, d) {return f(n, d, ['%d hodinami', '%d hodiny', '%d hodin']);}, 17 | day: function(n, d) {return f(n, d, ['%d dnem', '%d den']);}, 18 | days: function(n, d) {return f(n, d, ['%d dny', '%d dny', '%d dní']);}, 19 | month: function(n, d) {return f(n, d, ['%d měsícem', '%d měsíc']);}, 20 | months: function(n, d) {return f(n, d, ['%d měsíci', '%d měsíce', '%d měsíců']);}, 21 | year: function(n, d) {return f(n, d, ['%d rokem', '%d rok']);}, 22 | years: function(n, d) {return f(n, d, ['%d lety', '%d roky', '%d let']);} 23 | }; 24 | })(); 25 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.ky.js: -------------------------------------------------------------------------------- 1 | // Russian 2 | (function() { 3 | function numpf(n, f, s, t) { 4 | // f - 1, 21, 31, ... 5 | // s - 2-4, 22-24, 32-34 ... 6 | // t - 5-20, 25-30, ... 7 | var n10 = n % 10; 8 | if ( (n10 == 1) && ( (n == 1) || (n > 20) ) ) { 9 | return f; 10 | } else if ( (n10 > 1) && (n10 < 5) && ( (n > 20) || (n < 10) ) ) { 11 | return s; 12 | } else { 13 | return t; 14 | } 15 | } 16 | 17 | jQuery.timeago.settings.strings = { 18 | prefixAgo: null, 19 | prefixFromNow: "через", 20 | suffixAgo: "мурун", 21 | suffixFromNow: null, 22 | seconds: "1 минуттан аз", 23 | minute: "минута", 24 | minutes: function(value) { return numpf(value, "%d минута", "%d минута", "%d минут"); }, 25 | hour: "саат", 26 | hours: function(value) { return numpf(value, "%d саат", "%d саат", "%d саат"); }, 27 | day: "күн", 28 | days: function(value) { return numpf(value, "%d күн", "%d күн", "%d күн"); }, 29 | month: "ай", 30 | months: function(value) { return numpf(value, "%d ай", "%d ай", "%d ай"); }, 31 | year: "жыл", 32 | years: function(value) { return numpf(value, "%d жыл", "%d жыл", "%d жыл"); } 33 | }; 34 | })(); -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.ru.js: -------------------------------------------------------------------------------- 1 | // Russian 2 | (function() { 3 | function numpf(n, f, s, t) { 4 | // f - 1, 21, 31, ... 5 | // s - 2-4, 22-24, 32-34 ... 6 | // t - 5-20, 25-30, ... 7 | var n10 = n % 10; 8 | if ( (n10 == 1) && ( (n == 1) || (n > 20) ) ) { 9 | return f; 10 | } else if ( (n10 > 1) && (n10 < 5) && ( (n > 20) || (n < 10) ) ) { 11 | return s; 12 | } else { 13 | return t; 14 | } 15 | } 16 | 17 | jQuery.timeago.settings.strings = { 18 | prefixAgo: null, 19 | prefixFromNow: "через", 20 | suffixAgo: "назад", 21 | suffixFromNow: null, 22 | seconds: "меньше минуты", 23 | minute: "минуту", 24 | minutes: function(value) { return numpf(value, "%d минута", "%d минуты", "%d минут"); }, 25 | hour: "час", 26 | hours: function(value) { return numpf(value, "%d час", "%d часа", "%d часов"); }, 27 | day: "день", 28 | days: function(value) { return numpf(value, "%d день", "%d дня", "%d дней"); }, 29 | month: "месяц", 30 | months: function(value) { return numpf(value, "%d месяц", "%d месяца", "%d месяцев"); }, 31 | year: "год", 32 | years: function(value) { return numpf(value, "%d год", "%d года", "%d лет"); } 33 | }; 34 | })(); -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.uk.js: -------------------------------------------------------------------------------- 1 | // Ukrainian 2 | (function() { 3 | function numpf(n, f, s, t) { 4 | // f - 1, 21, 31, ... 5 | // s - 2-4, 22-24, 32-34 ... 6 | // t - 5-20, 25-30, ... 7 | var n10 = n % 10; 8 | if ( (n10 == 1) && ( (n == 1) || (n > 20) ) ) { 9 | return f; 10 | } else if ( (n10 > 1) && (n10 < 5) && ( (n > 20) || (n < 10) ) ) { 11 | return s; 12 | } else { 13 | return t; 14 | } 15 | } 16 | 17 | jQuery.timeago.settings.strings = { 18 | prefixAgo: null, 19 | prefixFromNow: "через", 20 | suffixAgo: "тому", 21 | suffixFromNow: null, 22 | seconds: "менше хвилини", 23 | minute: "хвилина", 24 | minutes: function(value) { return numpf(value, "%d хвилина", "%d хвилини", "%d хвилин"); }, 25 | hour: "година", 26 | hours: function(value) { return numpf(value, "%d година", "%d години", "%d годин"); }, 27 | day: "день", 28 | days: function(value) { return numpf(value, "%d день", "%d дні", "%d днів"); }, 29 | month: "місяць", 30 | months: function(value) { return numpf(value, "%d місяць", "%d місяці", "%d місяців"); }, 31 | year: "рік", 32 | years: function(value) { return numpf(value, "%d рік", "%d роки", "%d років"); } 33 | }; 34 | })(); -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.bs.js: -------------------------------------------------------------------------------- 1 | // Bosnian 2 | (function() { 3 | var numpf; 4 | 5 | numpf = function(n, f, s, t) { 6 | var n10; 7 | n10 = n % 10; 8 | if (n10 === 1 && (n === 1 || n > 20)) { 9 | return f; 10 | } else if (n10 > 1 && n10 < 5 && (n > 20 || n < 10)) { 11 | return s; 12 | } else { 13 | return t; 14 | } 15 | }; 16 | 17 | jQuery.timeago.settings.strings = { 18 | prefixAgo: "prije", 19 | prefixFromNow: "za", 20 | suffixAgo: null, 21 | suffixFromNow: null, 22 | second: "sekund", 23 | seconds: function(value) { 24 | return numpf(value, "%d sekund", "%d sekunde", "%d sekundi"); 25 | }, 26 | minute: "oko minut", 27 | minutes: function(value) { 28 | return numpf(value, "%d minut", "%d minute", "%d minuta"); 29 | }, 30 | hour: "oko sat", 31 | hours: function(value) { 32 | return numpf(value, "%d sat", "%d sata", "%d sati"); 33 | }, 34 | day: "oko jednog dana", 35 | days: function(value) { 36 | return numpf(value, "%d dan", "%d dana", "%d dana"); 37 | }, 38 | month: "mjesec dana", 39 | months: function(value) { 40 | return numpf(value, "%d mjesec", "%d mjeseca", "%d mjeseci"); 41 | }, 42 | year: "prije godinu dana ", 43 | years: function(value) { 44 | return numpf(value, "%d godinu", "%d godine", "%d godina"); 45 | }, 46 | wordSeparator: " " 47 | }; 48 | 49 | }).call(this); -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.sl.js: -------------------------------------------------------------------------------- 1 | // Slovenian with support for dual 2 | (function () { 3 | var numpf; 4 | numpf = function (n, a) { 5 | return a[n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0]; 6 | }; 7 | 8 | jQuery.timeago.settings.strings = { 9 | prefixAgo: null, 10 | prefixFromNow: "čez", 11 | suffixAgo: "nazaj", 12 | suffixFromNow: null, 13 | second: "sekundo", 14 | seconds: function (value) { 15 | return numpf(value, ["%d sekund", "%d sekundo", "%d sekundi", "%d sekunde"]); 16 | }, 17 | minute: "minuto", 18 | minutes: function (value) { 19 | return numpf(value, ["%d minut", "%d minuto", "%d minuti", "%d minute"]); 20 | }, 21 | hour: "eno uro", 22 | hours: function (value) { 23 | return numpf(value, ["%d ur", "%d uro", "%d uri", "%d ure"]); 24 | }, 25 | day: "en dan", 26 | days: function (value) { 27 | return numpf(value, ["%d dni", "%d dan", "%d dneva", "%d dni"]); 28 | }, 29 | month: "en mesec", 30 | months: function (value) { 31 | return numpf(value, ["%d mescov", "%d mesec", "%d mesca", "%d mesce"]); 32 | }, 33 | year: "eno leto", 34 | years: function (value) { 35 | return numpf(value, ["%d let", "%d leto", "%d leti", "%d leta"]); 36 | }, 37 | wordSeparator: " " 38 | }; 39 | 40 | }).call(this); 41 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.rs.js: -------------------------------------------------------------------------------- 1 | // Serbian 2 | (function () { 3 | var numpf; 4 | 5 | numpf = function (n, f, s, t) { 6 | var n10; 7 | n10 = n % 10; 8 | if (n10 === 1 && (n === 1 || n > 20)) { 9 | return f; 10 | } else if (n10 > 1 && n10 < 5 && (n > 20 || n < 10)) { 11 | return s; 12 | } else { 13 | return t; 14 | } 15 | }; 16 | 17 | jQuery.timeago.settings.strings = { 18 | prefixAgo: "pre", 19 | prefixFromNow: "za", 20 | suffixAgo: null, 21 | suffixFromNow: null, 22 | second: "sekund", 23 | seconds: function (value) { 24 | return numpf(value, "%d sekund", "%d sekunde", "%d sekundi"); 25 | }, 26 | minute: "oko minut", 27 | minutes: function (value) { 28 | return numpf(value, "%d minut", "%d minuta", "%d minuta"); 29 | }, 30 | hour: "oko jedan sat", 31 | hours: function (value) { 32 | return numpf(value, "%d sat", "%d sata", "%d sati"); 33 | }, 34 | day: "jedan dan", 35 | days: function (value) { 36 | return numpf(value, "%d dan", "%d dana", "%d dana"); 37 | }, 38 | month: "mesec dana", 39 | months: function (value) { 40 | return numpf(value, "%d mesec", "%d meseca", "%d meseci"); 41 | }, 42 | year: "godinu dana", 43 | years: function (value) { 44 | return numpf(value, "%d godinu", "%d godine", "%d godina"); 45 | }, 46 | wordSeparator: " " 47 | }; 48 | 49 | }).call(this); 50 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.hr.js: -------------------------------------------------------------------------------- 1 | // Croatian 2 | (function () { 3 | var numpf; 4 | 5 | numpf = function (n, f, s, t) { 6 | var n10; 7 | n10 = n % 10; 8 | if (n10 === 1 && (n === 1 || n > 20)) { 9 | return f; 10 | } else if (n10 > 1 && n10 < 5 && (n > 20 || n < 10)) { 11 | return s; 12 | } else { 13 | return t; 14 | } 15 | }; 16 | 17 | jQuery.timeago.settings.strings = { 18 | prefixAgo: "prije", 19 | prefixFromNow: "za", 20 | suffixAgo: null, 21 | suffixFromNow: null, 22 | second: "sekundu", 23 | seconds: function (value) { 24 | return numpf(value, "%d sekundu", "%d sekunde", "%d sekundi"); 25 | }, 26 | minute: "oko minutu", 27 | minutes: function (value) { 28 | return numpf(value, "%d minutu", "%d minute", "%d minuta"); 29 | }, 30 | hour: "oko jedan sat", 31 | hours: function (value) { 32 | return numpf(value, "%d sat", "%d sata", "%d sati"); 33 | }, 34 | day: "jedan dan", 35 | days: function (value) { 36 | return numpf(value, "%d dan", "%d dana", "%d dana"); 37 | }, 38 | month: "mjesec dana", 39 | months: function (value) { 40 | return numpf(value, "%d mjesec", "%d mjeseca", "%d mjeseci"); 41 | }, 42 | year: "prije godinu dana", 43 | years: function (value) { 44 | return numpf(value, "%d godinu", "%d godine", "%d godina"); 45 | }, 46 | wordSeparator: " " 47 | }; 48 | 49 | }).call(this); -------------------------------------------------------------------------------- /sql/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `users` ( 2 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 3 | `url` varchar(255) DEFAULT NULL, 4 | `date_created` datetime DEFAULT NULL, 5 | `points` int(11) NOT NULL DEFAULT 0, 6 | PRIMARY KEY (`id`) 7 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 8 | 9 | CREATE TABLE `posts` ( 10 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 11 | `lang` char(2) COLLATE utf8mb4_unicode_ci DEFAULT 'en', 12 | `user_id` bigint(20) unsigned DEFAULT NULL, 13 | `date_submitted` datetime NOT NULL, 14 | `post_date` datetime DEFAULT NULL, 15 | `tzoffset` int(11) NOT NULL DEFAULT 0, 16 | `post_author` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 17 | `title` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 18 | `href` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 19 | `body` text COLLATE utf8mb4_unicode_ci DEFAULT NULL, 20 | `comments` int(11) NOT NULL DEFAULT 0, 21 | `in_reply_to` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 22 | `source_url` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL, 23 | `deleted` tinyint(4) NOT NULL DEFAULT 0, 24 | PRIMARY KEY (`id`) 25 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 26 | 27 | CREATE TABLE `blocks` ( 28 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 29 | `domain` varchar(512) DEFAULT NULL, 30 | `source_url` varchar(512) DEFAULT NULL, 31 | `date_created` datetime DEFAULT NULL, 32 | `note` longtext DEFAULT NULL, 33 | PRIMARY KEY (`id`), 34 | KEY `domain` (`domain`), 35 | KEY `source_url` (`source_url`) 36 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 37 | 38 | -------------------------------------------------------------------------------- /views/index.php: -------------------------------------------------------------------------------- 1 |
2 |

IndieNews

3 |

', '']) ?>

4 |
5 | 6 |
7 | $lang): ?> 8 |
9 | 10 |
11 | 12 |
13 | 14 |
15 |
16 |

Want to see a new language? You can add a translation as a pull request! Copy the Locale file to a new folder, fill it out for your language, and submit a pull request!

17 |
18 |
19 | 20 | 60 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD License 2 | ----------- 3 | 4 | Copyright (c) 2013-2017 by Aaron Parecki. All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of Django nor the names of its contributors may be used 17 | to endorse or promote products derived from this software without 18 | specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /views/webmention.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

5 | 6 |
7 |

written a post with the proper markup, you will need to send a Webmention to submit it to IndieNews. Your software should do that automatically if it supports {0}, but you can also use the form below.', ['Webmention']) ?>

8 |
9 | 10 |
11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 |
20 | 25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 | 33 |
34 | 35 |
36 |
37 | -------------------------------------------------------------------------------- /views/_post-row.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | title)): ?> 4 |
title) ? $post->title : display_url($post->href)) ?>
5 | 6 |
title ?: $post->body,0,600)) ?>
7 | 8 |
9 | 10 | 11 | post_author) ?> 12 | 13 | | 14 | 15 | in_reply_to): ?> 16 | | 17 | 18 | 19 | 20 | post_date): ?> 21 | 24 | 25 | 26 | 27 | | 28 | 29 | 30 | 31 | 33 | 34 | source_url != $post->href ? 35 | ' ' . __('submitted ... from') 36 | . ' ' 37 | . (parse_url($post->source_url, PHP_URL_HOST)) 38 | . '' : '' ?> 39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | padding-left: 20px; 4 | padding-right: 20px; 5 | } 6 | .navbar-fixed-top, .navbar-fixed-bottom, .navbar-static-top { 7 | margin-left: -20px; 8 | margin-right: -20px; 9 | } 10 | 11 | 12 | .header, .header h2 { 13 | font-size: 13px; 14 | line-height: 18px; 15 | } 16 | .header h2 { 17 | font-weight: bold; 18 | } 19 | 20 | .header .main-nav { 21 | float: left; 22 | } 23 | 24 | 25 | .page { 26 | padding-top: 16px; 27 | } 28 | 29 | .post .title { 30 | overflow: hidden; 31 | text-overflow: ellipsis; 32 | display: block; 33 | } 34 | 35 | .post .content { 36 | white-space: pre-wrap; 37 | font-size: 13px; 38 | line-height: 15px; 39 | margin-top: 0.5em; 40 | } 41 | 42 | .single .title { 43 | font-size: 17px; 44 | } 45 | 46 | abbr.timeago { 47 | cursor: auto; 48 | border-bottom: none; 49 | } 50 | 51 | /* Table Styling since we're not using bootstrap tables */ 52 | .single td { 53 | border-top: 1px solid #ddd; 54 | border-bottom: 1px solid #ddd; 55 | } 56 | 57 | .threaded { 58 | width: 100%; 59 | border-collapse: collapse; 60 | border-spacing: 0; 61 | } 62 | 63 | .threaded .post.single { 64 | background-color: #f9f9f9; 65 | } 66 | 67 | .threaded td { 68 | padding: 8px; 69 | line-height: 20px; 70 | text-align: left; 71 | } 72 | .threaded td.nested-container { 73 | border-top: 1px solid #ddd; 74 | padding: 0; 75 | } 76 | 77 | .threaded td td { 78 | padding-bottom: 12px; 79 | } 80 | 81 | .threaded table.nested { 82 | width: 100%; 83 | } 84 | 85 | 86 | .post .details { 87 | font-size: 11px; 88 | color: #777; 89 | } 90 | .post .details .p-author a.u-url { 91 | color: #777; 92 | } 93 | 94 | 95 | 96 | .footer { 97 | margin-top: 30px; 98 | border-top: 1px #e9e9e9 solid; 99 | font-size: 8pt; 100 | } 101 | .footer p { 102 | margin-bottom: 0; 103 | clear: both; 104 | margin-left: 4px; 105 | } 106 | 107 | .footer ul { 108 | list-style-type: none; 109 | margin: 0; 110 | padding: 0; 111 | } 112 | .footer ul li { 113 | float: left; 114 | margin-right: 20px; 115 | padding: 4px 4px; 116 | } 117 | 118 | 119 | -------------------------------------------------------------------------------- /views/submit.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | 5 |

6 | 7 |

h-entry') ?>

8 | 9 |

bookmark') ?>

10 | 11 |

12 | 13 |

u-syndication', 'u-category', '', '']) ?>

14 | 15 |

16 | 17 |

Webmentions']) ?> 18 | 19 |

Webmention', 'h-entry']) ?>

20 | 21 |
22 |
23 |
24 | 25 | 26 |
27 | 28 | 29 | 30 |
31 |
32 | 33 |

34 | 35 |

36 | 37 |
38 |
39 | -------------------------------------------------------------------------------- /views/layout.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <?= $title ?> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 |
41 | 42 | 43 | 44 | 54 |
55 | 56 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /public/js/timeago/jquery.timeago.ar.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function numpf(n, a) { 3 | return a[plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5]; 4 | } 5 | 6 | jQuery.timeago.settings.strings = { 7 | prefixAgo: "منذ", 8 | prefixFromNow: "بعد", 9 | suffixAgo: null, 10 | suffixFromNow: null, // null OR "من الآن" 11 | second: function(value) { return numpf(value, [ 12 | 'أقل من ثانية', 13 | 'ثانية واحدة', 14 | 'ثانيتين', 15 | '%d ثوانٍ', 16 | '%d ثانية', 17 | '%d ثانية']); }, 18 | seconds: function(value) { return numpf(value, [ 19 | 'أقل من ثانية', 20 | 'ثانية واحدة', 21 | 'ثانيتين', 22 | '%d ثوانٍ', 23 | '%d ثانية', 24 | '%d ثانية']); }, 25 | minute: function(value) { return numpf(value, [ 26 | 'أقل من دقيقة', 27 | 'دقيقة واحدة', 28 | 'دقيقتين', 29 | '%d دقائق', 30 | '%d دقيقة', 31 | 'دقيقة']); }, 32 | minutes: function(value) { return numpf(value, [ 33 | 'أقل من دقيقة', 34 | 'دقيقة واحدة', 35 | 'دقيقتين', 36 | '%d دقائق', 37 | '%d دقيقة', 38 | 'دقيقة']); }, 39 | hour: function(value) { return numpf(value, [ 40 | 'أقل من ساعة', 41 | 'ساعة واحدة', 42 | 'ساعتين', 43 | '%d ساعات', 44 | '%d ساعة', 45 | '%d ساعة']); }, 46 | hours: function(value) { return numpf(value, [ 47 | 'أقل من ساعة', 48 | 'ساعة واحدة', 49 | 'ساعتين', 50 | '%d ساعات', 51 | '%d ساعة', 52 | '%d ساعة']); }, 53 | day: function(value) { return numpf(value, [ 54 | 'أقل من يوم', 55 | 'يوم واحد', 56 | 'يومين', 57 | '%d أيام', 58 | '%d يومًا', 59 | '%d يوم']); }, 60 | days: function(value) { return numpf(value, [ 61 | 'أقل من يوم', 62 | 'يوم واحد', 63 | 'يومين', 64 | '%d أيام', 65 | '%d يومًا', 66 | '%d يوم']); }, 67 | month: function(value) { return numpf(value, [ 68 | 'أقل من شهر', 69 | 'شهر واحد', 70 | 'شهرين', 71 | '%d أشهر', 72 | '%d شهرًا', 73 | '%d شهر']); }, 74 | months: function(value) { return numpf(value, [ 75 | 'أقل من شهر', 76 | 'شهر واحد', 77 | 'شهرين', 78 | '%d أشهر', 79 | '%d شهرًا', 80 | '%d شهر']); }, 81 | year: function(value) { return numpf(value, [ 82 | 'أقل من عام', 83 | 'عام واحد', 84 | '%d عامين', 85 | '%d أعوام', 86 | '%d عامًا']); 87 | }, 88 | years: function(value) { return numpf(value, [ 89 | 'أقل من عام', 90 | 'عام واحد', 91 | 'عامين', 92 | '%d أعوام', 93 | '%d عامًا', 94 | '%d عام']);} 95 | }; 96 | })(); 97 | -------------------------------------------------------------------------------- /resources/locales/default.pot: -------------------------------------------------------------------------------- 1 | # LANGUAGE translation of CakePHP Application 2 | # Copyright YEAR NAME 3 | # 4 | #, fuzzy 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: PROJECT VERSION\n" 8 | "POT-Creation-Date: 2015-09-08 19:59+0000\n" 9 | "PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n" 10 | "Last-Translator: NAME \n" 11 | "Language-Team: LANGUAGE \n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=utf-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" 16 | 17 | #: /controllers/controllers.php:88 18 | #: /views/layout.php:63 19 | msgid "About IndieNews" 20 | msgstr "" 21 | 22 | #: /views/_post-row.php:7 23 | msgid "in reply to" 24 | msgstr "" 25 | 26 | #: /views/_post-row.php:10 27 | msgid "submitted" 28 | msgstr "" 29 | 30 | #: /views/_post-row.php:11 31 | msgid "submitted ... from" 32 | msgstr "" 33 | 34 | #: /views/_post-row.php:12 35 | msgid "permalink" 36 | msgstr "" 37 | 38 | #: /views/index.php:3 39 | #: /views/submit.php:3 40 | msgid "IndieNews is a community-curated list of articles relevant to the {0}Indie Web{1}." 41 | msgstr "" 42 | 43 | #: /views/layout.php:34 44 | msgid "Home" 45 | msgstr "" 46 | 47 | #: /views/layout.php:35 48 | #: /views/submit.php:8 49 | msgid "Submit" 50 | msgstr "" 51 | 52 | #: /views/layout.php:64 53 | msgid "How to Submit a Post" 54 | msgstr "" 55 | 56 | #: /views/layout.php:67 57 | msgid "is part of" 58 | msgstr "" 59 | 60 | #: /views/layout.php:68 61 | msgid "This code is {0}open source{1}." 62 | msgstr "" 63 | 64 | #: /views/layout.php:69 65 | msgid "Feel free to send a pull request, or {0}file an issue{1}." 66 | msgstr "" 67 | 68 | #: /views/posts.php:14 69 | msgid "No posts yet!" 70 | msgstr "" 71 | 72 | #: /views/posts.php:15 73 | msgid "There are no posts submitted to the English feed yet! As soon as you {0}submit a post{1} it will show up here." 74 | msgstr "" 75 | 76 | #: /views/posts.php:22 77 | msgid "Older" 78 | msgstr "" 79 | 80 | #: /views/submit.php:10 81 | msgid "You won't find a \"submit\" form on this site. You don't even need to be logged in to this site to submit a post!" 82 | msgstr "" 83 | 84 | #: /views/submit.php:12 85 | msgid "Write a Post" 86 | msgstr "" 87 | 88 | #: /views/submit.php:14 89 | msgid "Write a post on your own site, and mark it up with the Microformats markup for an {0}." 90 | msgstr "" 91 | 92 | #: /views/submit.php:16 93 | msgid "Link to IndieNews" 94 | msgstr "" 95 | 96 | #: /views/submit.php:18 97 | msgid "Somewhere on the page you are submitting, add a {0} or {1} link to {2}the IndieNews home page{3} for your language." 98 | msgstr "" 99 | 100 | #: /views/submit.php:20 101 | msgid "Send a Webmention" 102 | msgstr "" 103 | 104 | #: /views/submit.php:22 105 | msgid "Send a {0} notification to let IndieNews know about your post. When the mention is received, IndieNews will fetch the page from your site and look for the {1} markup to find the post title and author." 106 | msgstr "" 107 | 108 | #: /views/submit.php:25 109 | msgid "Detailed instructions on how to submit a post" 110 | msgstr "" 111 | 112 | -------------------------------------------------------------------------------- /views/calendar.php: -------------------------------------------------------------------------------- 1 |

format('U')); ?>

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | format($start==1?'N':'w'); 14 | $lastDayOfWeek = date($start==1?'N':'w', strtotime($date->format('Y-m-t'))); 15 | 16 | if($firstDayOfWeek > $start) { 17 | echo ''."\n"; 18 | for($i = $start; $i < $firstDayOfWeek; $i++) { 19 | echo "\t".''."\n"; 20 | } 21 | } 22 | for($i = 1; $i <= $date->format('t'); $i++) { 23 | $thisDay = mktime(0,0,0,$month,$i,$year); 24 | if(strftime('%w',$thisDay) == $start) 25 | echo ''."\n"; 26 | 27 | echo ''."\n"; 51 | if( strftime(($start==1?'%u':'%w'),$thisDay) == ($start==1?7:6) ) 52 | echo ''."\n"; 53 | } 54 | if( $lastDayOfWeek < ($start==1?7:6) ) 55 | { 56 | for( $i=$lastDayOfWeek; $i<($start==1?7:6); $i++ ) 57 | { 58 | echo ''."\n"; 59 | } 60 | echo ''."\n"; 61 | } 62 | ?> 63 |
'."\n"; 31 | 32 | echo '
' . $i . '
' . "\n"; 33 | 34 | if(array_key_exists($i, $calendar)) { 35 | foreach($calendar[$i] as $post) { 36 | echo '
'; 37 | echo ''; 38 | if(shouldDisplayPostName($post->title)) { 39 | echo htmlspecialchars($post->title); 40 | } else { 41 | echo display_url($post->href); 42 | } 43 | echo ''."\n"; 44 | if(shouldDisplayPostName($post->title)) 45 | echo ' ' . display_url($post->post_author) . ''; 46 | echo '
'; 47 | } 48 | } 49 | 50 | echo '
64 | 65 | 80 | 81 | 102 | -------------------------------------------------------------------------------- /resources/locales/ru/default.po: -------------------------------------------------------------------------------- 1 | # LANGUAGE translation of CakePHP Application 2 | # Copyright YEAR NAME 3 | # 4 | msgid "" 5 | msgstr "" 6 | "Project-Id-Version: PROJECT VERSION\n" 7 | "POT-Creation-Date: 2015-09-06 03:40+0000\n" 8 | "PO-Revision-Date: 2015-09-11 09:00+0200\n" 9 | "Last-Translator: Tim Marinin \n" 10 | "Language-Team: LANGUAGE \n" 11 | "MIME-Version: 1.0\n" 12 | "Content-Type: text/plain; charset=utf-8\n" 13 | "Content-Transfer-Encoding: 8bit\n" 14 | "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" 15 | "Language: ru\n" 16 | "X-Generator: Poedit 1.8.4\n" 17 | 18 | #: /controllers/controllers.php:88 19 | #: /views/layout.php:63 20 | msgid "About IndieNews" 21 | msgstr "Про Индиновости" 22 | 23 | #: /views/_post-row.php:7 24 | msgid "in reply to" 25 | msgstr "в ответ на" 26 | 27 | #: /views/_post-row.php:10 28 | msgid "submitted" 29 | msgstr "добавлено" 30 | 31 | #: /views/_post-row.php:11 32 | msgid "submitted ... from" 33 | msgstr "через" 34 | 35 | #: /views/_post-row.php:12 36 | msgid "permalink" 37 | msgstr "пермалинк" 38 | 39 | #: /views/index.php:3 40 | #: /views/submit.php:3 41 | msgid "IndieNews is a community-curated list of articles relevant to the {0}Indie Web{1}." 42 | msgstr "Индиновости это пополняемый сообществом список статей, относящихся к {0}Индивебу{1}." 43 | 44 | #: /views/layout.php:34 45 | msgid "Home" 46 | msgstr "Дом" 47 | 48 | #: /views/layout.php:35 49 | #: /views/submit.php:8 50 | msgid "Submit" 51 | msgstr "Добавить" 52 | 53 | #: /views/layout.php:64 54 | msgid "How to Submit a Post" 55 | msgstr "Как добавить пост" 56 | 57 | #: /views/layout.php:67 58 | msgid "is part of" 59 | msgstr "часть" 60 | 61 | #: /views/layout.php:68 62 | msgid "This code is {0}open source{1}." 63 | msgstr "Это {0}открытый{1} код." 64 | 65 | #: /views/layout.php:69 66 | msgid "Feel free to send a pull request, or {0}file an issue{1}." 67 | msgstr "Присылайте пулреквесты или {0}заводите ишью{1}." 68 | 69 | #: /views/posts.php:4 70 | msgid "IndieNews English" 71 | msgstr "Индиновости на русском" 72 | 73 | #: /views/posts.php:20 74 | msgid "No posts yet!" 75 | msgstr "Пока постов нет!" 76 | 77 | #: /views/posts.php:21 78 | msgid "There are no posts submitted to the English feed yet! As soon as you {0}submit a post{1} it will show up here." 79 | msgstr "Пока нет добавленных постов. Как только вы {0}добавите пост{1}, он появится здесь." 80 | 81 | #: /views/posts.php:22 82 | msgid "Older" 83 | msgstr "Раньше" 84 | 85 | #: /views/submit.php:10 86 | msgid "You won't find a \"submit\" form on this site. You don't even need to be logged in to this site to submit a post!" 87 | msgstr "На этом сайте нет формы для добавления поста. Здесь даже логиниться не надо, чтобы добавить пост!" 88 | 89 | #: /views/submit.php:12 90 | msgid "Write a Post" 91 | msgstr "Напишите пост" 92 | 93 | #: /views/submit.php:14 94 | msgid "Write a post on your own site, and mark it up with the Microformats markup for an {0}." 95 | msgstr "Напишите пост на своём сайте, и разметьте его микроформатом {0}." 96 | 97 | #: /views/submit.php:16 98 | msgid "Link to IndieNews" 99 | msgstr "Сошлитесь на Индиновости" 100 | 101 | #: /views/submit.php:18 102 | msgid "Somewhere on the page you are submitting, add a {0} or {1} link to {2}the IndieNews home page{3} for your language." 103 | msgstr "Где-нибудь на странице добавьте ссылку с классом {0} или {1} на {2}главную страницу Индиновостей{3} на своём языке." 104 | 105 | #: /views/submit.php:20 106 | msgid "Send a Webmention" 107 | msgstr "Отправьте вебменшен" 108 | 109 | #: /views/submit.php:22 110 | msgid "IndieNews will fetch the page from your site and look for the {1} markup to find the post title and author." 111 | msgstr "Индиновости загрузят страницу с вашего сайта и будут искать разметку {1}, чтобы найти заголовок поста и автора." 112 | 113 | #: /views/submit.php:25 114 | msgid "Detailed instructions on how to submit a post" 115 | msgstr "Подробная инструкция, как добавить пост" 116 | 117 | msgid "posts" 118 | msgstr "посты" 119 | 120 | msgid "post" 121 | msgstr "пост" 122 | -------------------------------------------------------------------------------- /resources/locales/sv/default.po: -------------------------------------------------------------------------------- 1 | # LANGUAGE translation of CakePHP Application 2 | # Copyright YEAR NAME 3 | # 4 | msgid "" 5 | msgstr "" 6 | "Project-Id-Version: PROJECT VERSION\n" 7 | "POT-Creation-Date: 2015-09-06 17:42+0200\n" 8 | "PO-Revision-Date: 2015-09-09 17:43+0200\n" 9 | "Language-Team: EMAIL@ADDRESS\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 14 | "X-Generator: Poedit 1.8.4\n" 15 | "Last-Translator: \n" 16 | "Language: sv\n" 17 | 18 | #: /controllers/controllers.php:88 /views/layout.php:63 19 | msgid "About IndieNews" 20 | msgstr "Om IndieNews" 21 | 22 | #: /views/_post-row.php:7 23 | msgid "in reply to" 24 | msgstr "som svar på" 25 | 26 | #: /views/_post-row.php:10 27 | msgid "submitted" 28 | msgstr "inskickad" 29 | 30 | #: /views/_post-row.php:11 31 | msgid "submitted ... from" 32 | msgstr "från" 33 | 34 | #: /views/_post-row.php:12 35 | msgid "permalink" 36 | msgstr "permalänk" 37 | 38 | #: /views/index.php:3 /views/submit.php:3 39 | msgid "IndieNews is a community-curated list of articles relevant to the {0}Indie Web{1}." 40 | msgstr "IndieNews är en gemenskapskuraterad lista av artiklar relevanta för {0}Indiewebben{1}." 41 | 42 | #: /views/layout.php:34 43 | msgid "Home" 44 | msgstr "Hem" 45 | 46 | #: /views/layout.php:35 /views/submit.php:8 47 | msgid "Submit" 48 | msgstr "Skicka" 49 | 50 | #: /views/layout.php:64 51 | msgid "How to Submit a Post" 52 | msgstr "Hur du skickar in ett inlägg" 53 | 54 | #: /views/layout.php:67 55 | msgid "is part of" 56 | msgstr "är del av" 57 | 58 | #: /views/layout.php:68 59 | msgid "This code is {0}open source{1}." 60 | msgstr "Denna kod är {0}öppen källkod{1}." 61 | 62 | #: /views/layout.php:69 63 | msgid "Feel free to send a pull request, or {0}file an issue{1}." 64 | msgstr "Skicka gärna en pull request, eller {0}rapportera ett problem{1}." 65 | 66 | #: /views/posts.php:4 67 | msgid "IndieNews English" 68 | msgstr "IndieNews Svenska" 69 | 70 | #: /views/posts.php:20 71 | msgid "No posts yet!" 72 | msgstr "Inga inlägg ännu!" 73 | 74 | #: /views/posts.php:21 75 | msgid "There are no posts submitted to the English feed yet! As soon as you {0}submit a post{1} it will show up here." 76 | msgstr "Där är inga inlägg inskickade till det svenska flödet ännu! Så snart du {0}skickar in ett inlägg{1} så dyker det upp här." 77 | 78 | #: /views/posts.php:22 79 | msgid "Older" 80 | msgstr "Äldre" 81 | 82 | #: /views/submit.php:10 83 | msgid "You won't find a \"submit\" form on this site. You don't even need to be logged in to this site to submit a post!" 84 | msgstr "Du kommer inte hitta ett \"skicka in\"-formulär på denna sida. Du behöver inte ens vara inloggad för att skicka in ett inlägg!" 85 | 86 | #: /views/submit.php:12 87 | msgid "Write a Post" 88 | msgstr "Skriv ett inlägg" 89 | 90 | #: /views/submit.php:14 91 | msgid "Write a post on your own site, and mark it up with the Microformats markup for an {0}." 92 | msgstr "Skriv ett inlägg på din egna sida, och märk upp den med mikroformatsformattering för en {0}." 93 | 94 | #: /views/submit.php:16 95 | msgid "Link to IndieNews" 96 | msgstr "Länka till IndieNews" 97 | 98 | #: /views/submit.php:18 99 | msgid "Somewhere on the page you are submitting, add a {0} or {1} link to {2}the IndieNews home page{3} for your language." 100 | msgstr "Någonstans på sidan du skickar in, lägg till en {0} eller {1} länk till {2}IndieNews hemsidan{3} för ditt språk." 101 | 102 | #: /views/submit.php:20 103 | msgid "Send a Webmention" 104 | msgstr "Skicka en WebMention" 105 | 106 | #: /views/submit.php:22 107 | msgid "IndieNews will fetch the page from your site and look for the {1} markup to find the post title and author." 108 | msgstr "När omnämnandet är mottaget hämtar IndieNews sidan från din sajt och letar efter {1} formatering för att hitta inläggstitel och författare." 109 | 110 | #: /views/submit.php:25 111 | msgid "Detailed instructions on how to submit a post" 112 | msgstr "Detaljerade instruktioner kring att skicka in ett inlägg" 113 | -------------------------------------------------------------------------------- /resources/locales/es/default.po: -------------------------------------------------------------------------------- 1 | # LANGUAGE translation of CakePHP Application 2 | # Copyright YEAR NAME 3 | # 4 | msgid "" 5 | msgstr "" 6 | "Project-Id-Version: PROJECT VERSION\n" 7 | "POT-Creation-Date: 2015-09-06 03:40+0000\n" 8 | "PO-Revision-Date: 2022-02-15 09:00+0200\n" 9 | "Last-Translator: kinduff \n" 10 | "Language-Team: LANGUAGE \n" 11 | "MIME-Version: 1.0\n" 12 | "Content-Type: text/plain; charset=utf-8\n" 13 | "Content-Transfer-Encoding: 8bit\n" 14 | "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" 15 | "Language: es\n" 16 | 17 | #: /controllers/controllers.php:88 /views/layout.php:63 18 | msgid "About IndieNews" 19 | msgstr "Acerca de IndieNews" 20 | 21 | #: /views/_post-row.php:7 22 | msgid "in reply to" 23 | msgstr "en respuesta a" 24 | 25 | #: /views/_post-row.php:10 26 | msgid "submitted" 27 | msgstr "enviado" 28 | 29 | #: /views/_post-row.php:11 30 | msgid "submitted ... from" 31 | msgstr "enviado ... de" 32 | 33 | #: /views/_post-row.php:12 34 | msgid "permalink" 35 | msgstr "enlace permanente" 36 | 37 | #: /views/index.php:3 /views/submit.php:3 38 | msgid "IndieNews is a community-curated list of articles relevant to the {0}Indie Web{1}." 39 | msgstr "IndieNews es una lista de artículos relevantes para la {0}Indie Web{1}." 40 | 41 | #: /views/layout.php:34 42 | msgid "Home" 43 | msgstr "Inicio" 44 | 45 | #: /views/layout.php:35 /views/submit.php:8 46 | msgid "Submit" 47 | msgstr "Enviar" 48 | 49 | #: /views/layout.php:64 50 | msgid "How to Submit a Post" 51 | msgstr "Cómo enviar una publicación" 52 | 53 | #: /views/layout.php:67 54 | msgid "is part of" 55 | msgstr "fait partie de " 56 | 57 | #: /views/layout.php:68 58 | msgid "This code is {0}open source{1}." 59 | msgstr "Este código es de {0}código abierto{1}." 60 | 61 | #: /views/layout.php:69 62 | msgid "Feel free to send a pull request, or {0}file an issue{1}." 63 | msgstr "Siéntase libre de enviar un pull request, o {0}abrir un issue{1}." 64 | 65 | #: /views/posts.php:4 66 | msgid "IndieNews English" 67 | msgstr "IndieNews Español" 68 | 69 | #: /views/posts.php:20 70 | msgid "No posts yet!" 71 | msgstr "Aún no hay publicaciones!" 72 | 73 | #: /views/posts.php:21 74 | msgid "There are no posts submitted to the English feed yet! As soon as you {0}submit a post{1} it will show up here." 75 | msgstr "¡No hay publicaciones enviadas al feed de Español aún! Tan pronto como envíes una publicación, se mostrará aquí." 76 | 77 | #: /views/posts.php:22 78 | msgid "Older" 79 | msgstr "Anteriores" 80 | 81 | #: /views/submit.php:10 82 | msgid "You won't find a \"submit\" form on this site. You don't even need to be logged in to this site to submit a post!" 83 | msgstr "No encontrarás un formulario de \"enviar\" en este sitio. ¡Tampoco necesitas estar conectado a este sitio para enviar una publicación!" 84 | 85 | #: /views/submit.php:12 86 | msgid "Write a Post" 87 | msgstr "Escribir una publicación" 88 | 89 | #: /views/submit.php:14 90 | msgid "Write a post on your own site, and mark it up with the Microformats markup for an {0}." 91 | msgstr "Escribe una publicación en tu propio sitio, y marca la publicación con elementos de Microformats para un {0}." 92 | 93 | #: /views/submit.php:16 94 | msgid "Link to IndieNews" 95 | msgstr "Enlace a IndieNews" 96 | 97 | #: /views/submit.php:18 98 | msgid "Somewhere on the page you are submitting, add a {0} or {1} link to {2}the IndieNews home page{3} for your language." 99 | msgstr "Alguna parte de la página que estás enviando, añade un {0} o {1} enlace a {2}la página de inicio de IndieNews{3} para tu idioma." 100 | 101 | #: /views/submit.php:20 102 | msgid "Send a Webmention" 103 | msgstr "Enviar un Webmention" 104 | 105 | #: /views/submit.php:22 106 | msgid "IndieNews will fetch the page from your site and look for the {1} markup to find the post title and author." 107 | msgstr "IndieNews buscará la página en tu sitio y buscará el {1} marcado para encontrar el título de la publicación y el autor." 108 | 109 | #: /views/submit.php:25 110 | msgid "Detailed instructions on how to submit a post" 111 | msgstr "Instrucciones detalladas para enviar una publicación" 112 | -------------------------------------------------------------------------------- /resources/locales/nl/default.po: -------------------------------------------------------------------------------- 1 | # LANGUAGE translation of CakePHP Application 2 | # Copyright YEAR NAME 3 | # 4 | msgid "" 5 | msgstr "" 6 | "Project-Id-Version: PROJECT VERSION\n" 7 | "POT-Creation-Date: 2015-09-06 17:42+0200\n" 8 | "PO-Revision-Date: 2017-01-12 02:31+0100\n" 9 | "Language-Team: EMAIL@ADDRESS\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 14 | "X-Generator: Poedit 1.8.4\n" 15 | "Last-Translator: \n" 16 | "Language: nl\n" 17 | 18 | #: /controllers/controllers.php:88 /views/layout.php:63 19 | msgid "About IndieNews" 20 | msgstr "Over IndieNews" 21 | 22 | #: /views/_post-row.php:7 23 | msgid "in reply to" 24 | msgstr "in antwoord op" 25 | 26 | #: /views/_post-row.php:10 27 | msgid "submitted" 28 | msgstr "ingestuurd" 29 | 30 | #: /views/_post-row.php:11 31 | msgid "submitted ... from" 32 | msgstr "ingestuurd ... door" 33 | 34 | #: /views/_post-row.php:12 35 | msgid "permalink" 36 | msgstr "permalink" 37 | 38 | #: /views/index.php:3 /views/submit.php:3 39 | msgid "IndieNews is a community-curated list of articles relevant to the {0}Indie Web{1}." 40 | msgstr "IndieNews is een door de community gecureerde lijst van voor {0}IndieWeb{1} relevante artikelen." 41 | 42 | #: /views/layout.php:34 43 | msgid "Home" 44 | msgstr "Home" 45 | 46 | #: /views/layout.php:35 /views/submit.php:8 47 | msgid "Submit" 48 | msgstr "Insturen" 49 | 50 | #: /views/layout.php:64 51 | msgid "How to Submit a Post" 52 | msgstr "Hoe kan een artikel insturen?" 53 | 54 | #: /views/layout.php:67 55 | msgid "is part of" 56 | msgstr "is een onderdeel van" 57 | 58 | #: /views/layout.php:68 59 | msgid "This code is {0}open source{1}." 60 | msgstr "Deze code is {0}open source{1}." 61 | 62 | #: /views/layout.php:69 63 | msgid "Feel free to send a pull request, or {0}file an issue{1}." 64 | msgstr "Stuur een pull request of {0}meld een probleem{1}." 65 | 66 | #: /views/posts.php:4 67 | msgid "IndieNews English" 68 | msgstr "IndieNews Nederlands" 69 | 70 | #: /views/posts.php:20 71 | msgid "No posts yet!" 72 | msgstr "Nog geen posts!" 73 | 74 | #: /views/posts.php:21 75 | msgid "There are no posts submitted to the English feed yet! As soon as you {0}submit a post{1} it will show up here." 76 | msgstr "Er zijn nog geen artikelen aan de Nederlandse feed toegevoegd! Zodra je een {0}artikel instuurt{1} komt die hier te staan." 77 | 78 | #: /views/posts.php:22 79 | msgid "Older" 80 | msgstr "Ouder" 81 | 82 | #: /views/submit.php:10 83 | msgid "You won't find a \"submit\" form on this site. You don't even need to be logged in to this site to submit a post!" 84 | msgstr "Je vindt op deze site geen formulier om nieuwe artikelen in te sturen. Om een artikel te plaatsen hoeft je niet eens in te loggen op deze site!" 85 | 86 | #: /views/submit.php:12 87 | msgid "Write a Post" 88 | msgstr "Schrijf een artikel" 89 | 90 | #: /views/submit.php:14 91 | msgid "Write a post on your own site, and mark it up with the Microformats markup for an {0}." 92 | msgstr "Schrijf een artikel op je eigen site en maak het op met de Microformats mark-up voor een {0}." 93 | 94 | #: /views/submit.php:16 95 | msgid "Link to IndieNews" 96 | msgstr "Link naar IndieNews" 97 | 98 | #: /views/submit.php:18 99 | msgid "Somewhere on the page you are submitting, add a {0} or {1} link to {2}the IndieNews home page{3} for your language." 100 | msgstr "Zet ergens op de pagina die je wilt inzenden een link met {0} of {1} naar {2}de homepagina van IndieNews{3} voor jouw taal." 101 | 102 | #: /views/submit.php:20 103 | msgid "Send a Webmention" 104 | msgstr "Stuur een Webmention" 105 | 106 | #: /views/submit.php:22 107 | msgid "IndieNews will fetch the page from your site and look for the {1} markup to find the post title and author." 108 | msgstr "Als de Webmention ontvangen is zal IndieNews de ingestuurde pagina opvragen en naar de {1} mark-up kijken om de titel en auteur van het artikel te vinden." 109 | 110 | #: /views/submit.php:25 111 | msgid "Detailed instructions on how to submit a post" 112 | msgstr "Uitgebreide Engelse instructies over hoe je moet insturen" 113 | -------------------------------------------------------------------------------- /resources/locales/fr/default.po: -------------------------------------------------------------------------------- 1 | # LANGUAGE translation of CakePHP Application 2 | # Copyright YEAR NAME 3 | # 4 | msgid "" 5 | msgstr "" 6 | "Project-Id-Version: PROJECT VERSION\n" 7 | "POT-Creation-Date: 2015-09-06 17:42+0200\n" 8 | "PO-Revision-Date: 2015-09-10 09:39+0200\n" 9 | "Language-Team: EMAIL@ADDRESS\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 14 | "X-Generator: Poedit 1.8.4\n" 15 | "Last-Translator: \n" 16 | "Language: sv\n" 17 | 18 | #: /controllers/controllers.php:88 /views/layout.php:63 19 | msgid "About IndieNews" 20 | msgstr "À Propos d’IndieNews" 21 | 22 | #: /views/_post-row.php:7 23 | msgid "in reply to" 24 | msgstr "en réponse à" 25 | 26 | #: /views/_post-row.php:10 27 | msgid "submitted" 28 | msgstr "proposé" 29 | 30 | #: /views/_post-row.php:11 31 | msgid "submitted ... from" 32 | msgstr "proposé… par" 33 | 34 | #: /views/_post-row.php:12 35 | msgid "permalink" 36 | msgstr "permalien" 37 | 38 | #: /views/index.php:3 /views/submit.php:3 39 | msgid "IndieNews is a community-curated list of articles relevant to the {0}Indie Web{1}." 40 | msgstr "IndieNews est une liste d’articles sélectionnés par la communauté en rapport avec l’{0}Indie Web{1}." 41 | 42 | #: /views/layout.php:34 43 | msgid "Home" 44 | msgstr "Accueil" 45 | 46 | #: /views/layout.php:35 /views/submit.php:8 47 | msgid "Submit" 48 | msgstr "Proposer" 49 | 50 | #: /views/layout.php:64 51 | msgid "How to Submit a Post" 52 | msgstr "Comment Proposer un Article" 53 | 54 | #: /views/layout.php:67 55 | msgid "is part of" 56 | msgstr "fait partie de " 57 | 58 | #: /views/layout.php:68 59 | msgid "This code is {0}open source{1}." 60 | msgstr "Ce code est {0}open source{1}." 61 | 62 | #: /views/layout.php:69 63 | msgid "Feel free to send a pull request, or {0}file an issue{1}." 64 | msgstr "Sentez-vous à l’aise pour envoyer un « pull request », ou pour {0}rapporter un problème{1}." 65 | 66 | #: /views/posts.php:4 67 | msgid "IndieNews English" 68 | msgstr "IndieNews Français" 69 | 70 | #: /views/posts.php:20 71 | msgid "No posts yet!" 72 | msgstr "Aucun article à cette heure !" 73 | 74 | #: /views/posts.php:21 75 | msgid "There are no posts submitted to the English feed yet! As soon as you {0}submit a post{1} it will show up here." 76 | msgstr "Aucun article proposé sur le flux Français à cette heure ! Dès que vous {0}proposerez un article{1}, il s’affichera ici." 77 | 78 | #: /views/posts.php:22 79 | msgid "Older" 80 | msgstr "Plus ancien" 81 | 82 | #: /views/submit.php:10 83 | msgid "You won't find a \"submit\" form on this site. You don't even need to be logged in to this site to submit a post!" 84 | msgstr "Vous ne trouverez pas de formulaire « proposer » sur ce site. Vous n’avez même pas besoin d’être connecté pour proposer un article !" 85 | 86 | #: /views/submit.php:12 87 | msgid "Write a Post" 88 | msgstr "Écrire un Article" 89 | 90 | #: /views/submit.php:14 91 | msgid "Write a post on your own site, and mark it up with the Microformats markup for an {0}." 92 | msgstr "Écrivez un article sur votre propre site et marquez-le avec la syntaxe Microformats pour un {0}." 93 | 94 | #: /views/submit.php:16 95 | msgid "Link to IndieNews" 96 | msgstr "Lien vers IndieNews" 97 | 98 | #: /views/submit.php:18 99 | msgid "Somewhere on the page you are submitting, add a {0} or {1} link to {2}the IndieNews home page{3} for your language." 100 | msgstr "Quelque part sur la page que vous proposez, ajoutez un {0} ou {1} lien vers la {2}page d’accueil IndieNews{3} pour votre langue." 101 | 102 | #: /views/submit.php:20 103 | msgid "Send a Webmention" 104 | msgstr "Envoyer une WebMention" 105 | 106 | #: /views/submit.php:22 107 | msgid "IndieNews will fetch the page from your site and look for the {1} markup to find the post title and author." 108 | msgstr "IndieNews récupérera la page à partir de votre site et regardera le {1} marquage pour trouver le titre de l’article et l’auteur." 109 | 110 | #: /views/submit.php:25 111 | msgid "Detailed instructions on how to submit a post" 112 | msgstr "Instructions détaillées pour savoir comment proposer un article." 113 | -------------------------------------------------------------------------------- /resources/locales/de/default.po: -------------------------------------------------------------------------------- 1 | # LANGUAGE translation of CakePHP Application 2 | # Copyright YEAR NAME 3 | # 4 | msgid "" 5 | msgstr "" 6 | "Project-Id-Version: PROJECT VERSION\n" 7 | "POT-Creation-Date: 2015-09-06 03:40+0000\n" 8 | "PO-Revision-Date: 2015-09-11 09:00+0200\n" 9 | "Last-Translator: Felix Schwenzel \n" 10 | "Language-Team: LANGUAGE \n" 11 | "MIME-Version: 1.0\n" 12 | "Content-Type: text/plain; charset=utf-8\n" 13 | "Content-Transfer-Encoding: 8bit\n" 14 | "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" 15 | "Language: de\n" 16 | "X-Generator: Poedit 1.8.4\n" 17 | 18 | #: /controllers/controllers.php:88 19 | #: /views/layout.php:63 20 | msgid "About IndieNews" 21 | msgstr "Über IndieNews" 22 | 23 | #: /views/_post-row.php:7 24 | msgid "in reply to" 25 | msgstr "als Antwort auf" 26 | 27 | #: /views/_post-row.php:10 28 | msgid "submitted" 29 | msgstr "eingereicht" 30 | 31 | #: /views/_post-row.php:11 32 | msgid "submitted ... from" 33 | msgstr "von" 34 | 35 | #: /views/_post-row.php:12 36 | msgid "permalink" 37 | msgstr "Permalink" 38 | 39 | #: /views/index.php:3 40 | #: /views/submit.php:3 41 | msgid "IndieNews is a community-curated list of articles relevant to the {0}Indie Web{1}." 42 | msgstr "IndieNews ist eine gemeinschaftlich kuratierte Liste von Artikeln zum Thema {0}Indie Web{1}." 43 | 44 | #: /views/layout.php:34 45 | msgid "Home" 46 | msgstr "Startseite" 47 | 48 | #: /views/layout.php:35 49 | #: /views/submit.php:8 50 | msgid "Submit" 51 | msgstr "Einreichen" 52 | 53 | #: /views/layout.php:64 54 | msgid "How to Submit a Post" 55 | msgstr "Wie man einen Artikel einreicht" 56 | 57 | #: /views/layout.php:67 58 | msgid "is part of" 59 | msgstr "ist ein Teil der" 60 | 61 | #: /views/layout.php:68 62 | msgid "This code is {0}open source{1}." 63 | msgstr "Dieser Code ist {0}Open-Source{1}." 64 | 65 | #: /views/layout.php:69 66 | msgid "Feel free to send a pull request, or {0}file an issue{1}." 67 | msgstr "Zögere nicht, einen Pull-Reuest zu senden, oder {0}einen Fehler zu melden{1}." 68 | 69 | #: /views/posts.php:4 70 | msgid "IndieNews English" 71 | msgstr "IndieNews Deutsch" 72 | 73 | #: /views/posts.php:20 74 | msgid "No posts yet!" 75 | msgstr "Bisher keine Artikel!" 76 | 77 | #: /views/posts.php:21 78 | msgid "There are no posts submitted to the English feed yet! As soon as you {0}submit a post{1} it will show up here." 79 | msgstr "Es gibt hier keinen Artikel. Wenn man {0}einen Artikel einreicht,{1} wird er hier erscheinen." 80 | 81 | #: /views/posts.php:22 82 | msgid "Older" 83 | msgstr "Ältere" 84 | 85 | #: /views/submit.php:10 86 | msgid "You won't find a \"submit\" form on this site. You don't even need to be logged in to this site to submit a post!" 87 | msgstr "Es gibt keine Einreichungsformular auf dieser Site. Du musst dich noch nicht einmal einloggen, um hier einen Artikel einzureichen!" 88 | 89 | #: /views/submit.php:12 90 | msgid "Write a Post" 91 | msgstr "Schreibe einen Artikel" 92 | 93 | #: /views/submit.php:14 94 | msgid "Write a post on your own site, and mark it up with the Microformats markup for an {0}." 95 | msgstr "Schreibe einen Artikel auf deiner eigenen Site und füge Microformat-Markup für eine {0} hinzu." 96 | 97 | #: /views/submit.php:16 98 | msgid "Link to IndieNews" 99 | msgstr "Verlinke IndieNews" 100 | 101 | #: /views/submit.php:18 102 | msgid "Somewhere on the page you are submitting, add a {0} or {1} link to {2}the IndieNews home page{3} for your language." 103 | msgstr "Füge irgendwo in dem Artikel, den du einreichen möchtest, einen {0}- oder {1}-Link auf {2}die IndieNews Startseite{3} deiner Sprache hinzu." 104 | 105 | #: /views/submit.php:20 106 | msgid "Send a Webmention" 107 | msgstr "Sende einen Webmention" 108 | 109 | #: /views/submit.php:22 110 | msgid "IndieNews will fetch the page from your site and look for the {1} markup to find the post title and author." 111 | msgstr "IndieNews wird den Artikel von deiner Seite laden und nach dem {1}-Markup suchen, um den Titel und Autoren-Namen zu finden." 112 | 113 | #: /views/submit.php:25 114 | msgid "Detailed instructions on how to submit a post" 115 | msgstr "Ausführliche Anleitung wie man einen Artikel einreicht" 116 | 117 | msgid "posts" 118 | msgstr "Artikel" 119 | 120 | msgid "post" 121 | msgstr "Artikel" 122 | -------------------------------------------------------------------------------- /lib/mf2.php: -------------------------------------------------------------------------------- 1 | _data = json_decode(json_encode($data)); 9 | else 10 | $this->_data = $data; 11 | $this->_parent = $parent; 12 | } 13 | 14 | public function __get($key) { 15 | $cacheKey = '_'.$key; 16 | 17 | if(method_exists($this, $key) && property_exists($this, $cacheKey)) { 18 | if($this->{$cacheKey} !== null) 19 | return $this->{$cacheKey}; 20 | $val = $this->$key(); 21 | $this->{$cacheKey} = $val; 22 | return $val; 23 | } 24 | 25 | return null; 26 | } 27 | 28 | protected function _is($obj, $type) { 29 | return in_array($type, $obj->type); 30 | } 31 | 32 | protected function _findFirstItem($type) { 33 | foreach($this->_data->items as $i) { 34 | if($this->_is($i, $type)) 35 | return $i; 36 | } 37 | return null; 38 | } 39 | 40 | // Find the property in the 'properties' object and return the values 41 | // If $first is true, returns only the first value, otherwise returns an array 42 | public function property($key, $first=false) { 43 | if($this->_data && property_exists($this->_data, 'properties')) { 44 | if(property_exists($this->_data->properties, $key)) { 45 | if($first) 46 | return $this->_data->properties->{$key}[0]; 47 | else 48 | return $this->_data->properties->{$key}; 49 | } 50 | } 51 | if($first) 52 | return null; 53 | else 54 | return array(); 55 | } 56 | 57 | protected function _stringForProperty($key, $single=false) { 58 | if($content=$this->property($key)) { 59 | if($single) { 60 | return $content[0]; 61 | } else{ 62 | return implode(' ', $content); 63 | } 64 | } 65 | return ''; 66 | } 67 | } 68 | 69 | class MF2Page extends MF2Object { 70 | protected $_hentry = null; 71 | protected $_hevent = null; 72 | protected $_author = null; 73 | 74 | protected function hentry() { 75 | $item = $this->_findFirstItem('h-entry'); 76 | if($item) 77 | return new HEntry($item, $this); 78 | else 79 | return null; 80 | } 81 | 82 | protected function hevent() { 83 | $item = $this->_findFirstItem('h-event'); 84 | if($item) 85 | return new HEvent($item, $this); 86 | else 87 | return null; 88 | } 89 | 90 | protected function author() { 91 | $author = null; 92 | $entry = $this->_findFirstItem('h-entry'); 93 | if($entry) { 94 | if(property_exists($entry->properties, 'author')) { 95 | $author = $entry->properties->author; 96 | } else { 97 | 98 | } 99 | } 100 | } 101 | 102 | } 103 | 104 | class HEntry extends MF2Object { 105 | protected $_content = null; 106 | protected $_published = null; 107 | protected $_author = null; 108 | 109 | protected function content() { 110 | if($content=$this->property('name')) 111 | return implode(' ', $content); 112 | if($content=$this->property('content')) 113 | return implode(' ', $content); 114 | return ''; 115 | } 116 | 117 | protected function published() { 118 | if($time=$this->property('published', true)) 119 | return new DateTime($time); 120 | return null; 121 | } 122 | 123 | // Search the h-entry for an author. If none is found, fall back to the parent's h-card. 124 | protected function author() { 125 | if($author=$this->property('author', true)) 126 | return new HCard($author); 127 | if($author=$this->_parent->_findFirstItem('h-card')) 128 | return new HCard($author); 129 | return null; 130 | } 131 | } 132 | 133 | class HEvent extends MF2Object { 134 | protected $_location = null; 135 | protected $_published = null; 136 | protected $_author = null; 137 | 138 | protected function location() { 139 | $locations = array(); 140 | if($location=$this->property('location')) { 141 | foreach($location as $l) 142 | $locations[] = new HCard($l); 143 | return $locations; 144 | } else { 145 | return null; 146 | } 147 | } 148 | 149 | protected function published() { 150 | if($time=$this->property('published', true)) 151 | return new DateTime($time); 152 | return null; 153 | } 154 | 155 | // Search the h-entry for an author. If none is found, fall back to the parent's h-card. 156 | protected function author() { 157 | if($author=$this->property('author', true)) 158 | return new HCard($author); 159 | if($author=$this->_parent->_findFirstItem('h-card')) 160 | return new HCard($author); 161 | return null; 162 | } 163 | } 164 | 165 | class HCard extends MF2Object { 166 | protected $_name; 167 | protected $_nickname; 168 | protected $_given_name; 169 | protected $_family_name; 170 | protected $_adr; 171 | protected $_note; 172 | protected $_photo; 173 | protected $_url; 174 | 175 | protected function name() { 176 | return $this->_stringForProperty('name', true); 177 | } 178 | 179 | protected function nickname() { 180 | return $this->_stringForProperty('nickname', true); 181 | } 182 | 183 | protected function given_name() { 184 | return $this->_stringForProperty('given-name', true); 185 | } 186 | 187 | protected function family_name() { 188 | return $this->_stringForProperty('family-name', true); 189 | } 190 | 191 | protected function adr() { 192 | return $this->_stringForProperty('adr'. true); 193 | } 194 | 195 | protected function note() { 196 | return $this->_stringForProperty('note', true); 197 | } 198 | 199 | protected function photo() { 200 | return $this->_stringForProperty('photo', true); 201 | } 202 | 203 | protected function url() { 204 | return $this->_stringForProperty('url', true); 205 | } 206 | 207 | } 208 | 209 | class ParserPlus extends mf2\Parser { 210 | public function xpath($query) { 211 | return $this->xpath->query($query); 212 | } 213 | } 214 | 215 | -------------------------------------------------------------------------------- /lib/helpers.php: -------------------------------------------------------------------------------- 1 | 'English', 15 | 'sv' => 'Svenska', 16 | 'de' => 'Deutsch', 17 | 'fr' => 'Français', 18 | 'nl' => 'Nederlands', 19 | 'ru' => 'русский', 20 | 'es' => 'Español', 21 | ]; 22 | } 23 | 24 | function localeFromLangCode($code) { 25 | switch($code) { 26 | case 'en': 27 | return 'en_US'; 28 | case 'sv': 29 | return 'sv_SE.UTF-8'; 30 | case 'de': 31 | return 'de_DE.UTF-8'; 32 | case 'fr': 33 | return 'fr_FR.UTF-8'; 34 | case 'nl': 35 | return 'nl_NL.UTF-8'; 36 | case 'ru': 37 | return 'ru_RU.UTF-8'; 38 | case 'es': 39 | return 'es_MX.UTF-8'; 40 | } 41 | } 42 | 43 | function getPostsForParentID($parentID) { 44 | return ORM::for_table('posts')->raw_query(' 45 | SELECT *, GREATEST(1, TIMESTAMPDIFF(HOUR, date_submitted, NOW())) AS age 46 | FROM posts 47 | ORDER BY date_submitted DESC 48 | ')->find_many(); 49 | } 50 | 51 | function respondWithFormat($response, $html, $format) { 52 | if($format == 'json') { 53 | $parser = new mf2\Parser($html); 54 | $output = $parser->parse(); 55 | $output['note'][] = "This JSON is automatically generated by parsing the microformats from the HTML representation of this page using the php-mf2 library."; 56 | $output['note'][] = "You can find the php-mf2 library at github.com/microformats/php-mf2"; 57 | $output['note'][] = "If you see a problem with the output, please let me know! (github.com/aaronpk/IndieNews/issues)"; 58 | $response->getBody()->write(json_encode($output)); 59 | return $response->withHeader('Content-Type', 'application/json'); 60 | } elseif($format == 'jf2') { 61 | $xray = new p3k\XRay(); 62 | $parsed = $xray->parse(Config::$baseURL, $html); 63 | $response->getBody()->write(json_encode($parsed)); 64 | return $response->withHeader('Content-Type', 'application/json'); 65 | } else { 66 | $response->getBody()->write($html); 67 | return $response; 68 | } 69 | } 70 | 71 | // $format - one of the php.net/date format strings 72 | // $date - a string that will be passed to DateTime() 73 | // $offset - numeric timezone offset 74 | function printLocalDate($format, $date, $offset) { 75 | if($offset != 0) 76 | $tz = new DateTimeZone(($offset < 0 ? '-' : '+') . sprintf('%02d:%02d', abs(floor($offset / 60 / 60)), (($offset / 60) % 60))); 77 | else 78 | $tz = new DateTimeZone('UTC'); 79 | $d = new DateTime($date); 80 | $d->setTimeZone($tz); 81 | return $d->format($format); 82 | } 83 | 84 | function formatFromRouteArgs($args) { 85 | return empty($args['format']) ? 'html' : trim($args['format'], '.'); 86 | } 87 | 88 | // Strip the http:// prefix 89 | function slugForURL($url) { 90 | return preg_replace('/https?:\/\//', '', $url); 91 | } 92 | 93 | function permalinkForURL($lang, $url) { 94 | return Config::$baseURL . '/' . $lang . '/' . slugForURL($url); 95 | } 96 | 97 | function shouldDisplayPostName($name) { 98 | if(!$name) return false; 99 | $name = str_replace('http://','https://',$name); 100 | return strlen($name) < 200 # must be less than 200 chars 101 | && substr_count($name, "\n") <= 1 # must be less than 2 lines 102 | && substr_count($name, "https://") <= 1; # must have at most 1 URL 103 | } 104 | 105 | function render($response, $page, $data) { 106 | $renderer = new PhpRenderer(__DIR__.'/../views/'); 107 | $renderer->setLayout('layout.php'); 108 | return $renderer->render($response, "$page.php", $data); 109 | }; 110 | 111 | function session($key) { 112 | if(array_key_exists($key, $_SESSION)) 113 | return $_SESSION[$key]; 114 | else 115 | return null; 116 | } 117 | 118 | function getLoggedInUser() { 119 | if(session('user')) { 120 | return ORM::for_table('users')->where('url', session('user'))->find_one(); 121 | } else { 122 | return false; 123 | } 124 | } 125 | 126 | function display_url($url) { 127 | return preg_replace(['/https?:\/\//','/\/$/'],'',$url); 128 | } 129 | 130 | function pa($a) { 131 | echo '
'; print_r($a); echo '
'; 132 | } 133 | 134 | function irc_notice($msg) { 135 | if(isset(Config::$ircURL)) { 136 | $ch = curl_init(Config::$ircURL); 137 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 138 | curl_setopt($ch, CURLOPT_POST, true); 139 | curl_setopt($ch, CURLOPT_HTTPHEADER, [ 140 | 'Authorization: Bearer ' . Config::$ircToken 141 | ]); 142 | curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array( 143 | 'content' => $msg, 144 | 'channel' => Config::$ircChannel 145 | ))); 146 | curl_exec($ch); 147 | } 148 | } 149 | 150 | /** 151 | * Converts base 10 to base 60. 152 | * http://tantek.pbworks.com/NewBase60 153 | * @param int $n 154 | * @return string 155 | */ 156 | function b10to60($n) 157 | { 158 | $s = ""; 159 | $m = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz"; 160 | if ($n==0) 161 | return 0; 162 | 163 | while ($n>0) 164 | { 165 | $d = $n % 60; 166 | $s = $m[$d] . $s; 167 | $n = ($n-$d)/60; 168 | } 169 | return $s; 170 | } 171 | 172 | /** 173 | * Converts base 60 to base 10, with error checking 174 | * http://tantek.pbworks.com/NewBase60 175 | * @param string $s 176 | * @return int 177 | */ 178 | function b60to10($s) 179 | { 180 | $n = 0; 181 | for($i = 0; $i < strlen($s); $i++) // iterate from first to last char of $s 182 | { 183 | $c = ord($s[$i]); // put current ASCII of char into $c 184 | if ($c>=48 && $c<=57) { $c=$c-48; } 185 | else if ($c>=65 && $c<=72) { $c-=55; } 186 | else if ($c==73 || $c==108) { $c=1; } // typo capital I, lowercase l to 1 187 | else if ($c>=74 && $c<=78) { $c-=56; } 188 | else if ($c==79) { $c=0; } // error correct typo capital O to 0 189 | else if ($c>=80 && $c<=90) { $c-=57; } 190 | else if ($c==95) { $c=34; } // underscore 191 | else if ($c>=97 && $c<=107) { $c-=62; } 192 | else if ($c>=109 && $c<=122) { $c-=63; } 193 | else { $c = 0; } // treat all other noise as 0 194 | $n = (60 * $n) + $c; 195 | } 196 | return $n; 197 | } 198 | -------------------------------------------------------------------------------- /controllers/controllers.php: -------------------------------------------------------------------------------- 1 | redirect('/home.json', '/en.json', 301); 7 | $app->redirect('/newest.json', '/en.json', 301); 8 | 9 | // Redirect post IDs to the post URL version 10 | $app->get('/post/{id:[0-9]+}{format:|\.json}', function($request, $response, $args) { 11 | $post = ORM::for_table('posts') 12 | ->where('id', $args['id']) 13 | ->where('deleted', 0) 14 | ->find_one(); 15 | 16 | if(!$post) 17 | return $response->withStatus(404); 18 | 19 | $url = Config::$baseURL . '/post/'. slugForURL($post->href) . ($args['format']); 20 | 21 | return $response 22 | ->withHeader('Location', $url) 23 | ->withStatus(302); 24 | }); 25 | 26 | // Language-specific feeds 27 | $app->get('/{lang:'.LANG_REGEX.'}{format:|\.json|\.jf2}', function($request, $response, $args) { 28 | 29 | $params = $request->getQueryParams(); 30 | 31 | $format = formatFromRouteArgs($args); 32 | 33 | I18n::setLocale($args['lang']); 34 | 35 | // Get posts ordered by date submitted 36 | $posts = ORM::for_table('posts') 37 | ->where('lang', $args['lang']) 38 | ->where('deleted', 0) 39 | ->order_by_desc('date_submitted'); 40 | 41 | if(array_key_exists('before', $params)) { 42 | $before = date('Y-m-d H:i:s', b60to10($params['before'])); 43 | $posts = $posts->where_lt('date_submitted', $before); 44 | } 45 | 46 | $posts = $posts->limit(20)->find_many(); 47 | 48 | $atomFeed = ''; 49 | $webSubTags = '' . "\n" . ''; 50 | 51 | $renderer = new PhpRenderer(__DIR__.'/../views/'); 52 | $renderer->setLayout('layout.php'); 53 | 54 | $temp = $renderer->render(new \Slim\Psr7\Response(), "posts.php", [ 55 | 'title' => 'IndieNews ' . $args['lang'], 56 | 'posts' => $posts, 57 | 'lang' => $args['lang'], 58 | 'meta' => $atomFeed.$webSubTags, 59 | ]); 60 | 61 | $html = $temp->getBody()->__toString(); 62 | 63 | $response = $response->withAddedHeader('Link', '<' . Config::$baseURL . '/'.$args['lang'].'/webmention>; rel="webmention"'); 64 | $response = $response->withAddedHeader('Link', '<' . Config::$baseURL . '/'.$args['lang'].'>; rel="self"'); 65 | $response = $response->withAddedHeader('Link', '<' . Config::$hubURL . '>; rel="hub"'); 66 | 67 | return respondWithFormat($response, $html, $format); 68 | }); 69 | 70 | 71 | $app->get('/{lang:'.LANG_REGEX.'}/{year:\d{4}}/{month:\d{2}}', function($request, $response, $args) { 72 | 73 | $year = $args['year']; 74 | $month = $args['month']; 75 | $lang = $args['lang']; 76 | 77 | I18n::setLocale($args['lang']); 78 | setlocale(LC_ALL, localeFromLangCode($args['lang'])); 79 | 80 | $date = new DateTime($year.'-'.$month.'-01'); 81 | 82 | $posts = ORM::for_table('posts') 83 | ->where('lang', $lang) 84 | ->where('deleted', 0) 85 | ->where_lte('date_submitted', $date->format('Y-m-t').' 23:59:59') 86 | ->where_gte('date_submitted', $date->format('Y-m-01')) 87 | ->order_by_asc('date_submitted') 88 | ->find_many(); 89 | 90 | $prev = false; 91 | $next = false; 92 | 93 | $prevPost = ORM::for_table('posts') 94 | ->where('lang', $lang) 95 | ->where('deleted', 0) 96 | ->where_lt('date_submitted', $date->format('Y-m-01')) 97 | ->order_by_desc('date_submitted') 98 | ->find_one(); 99 | if($prevPost) { 100 | $prev = new DateTime($prevPost->date_submitted); 101 | } 102 | 103 | $nextPost = ORM::for_table('posts') 104 | ->where('lang', $lang) 105 | ->where('deleted', 0) 106 | ->where_gt('date_submitted', $date->format('Y-m-t').' 23:59:59') 107 | ->order_by_asc('date_submitted') 108 | ->find_one(); 109 | if($nextPost) { 110 | $next = new DateTime($nextPost->date_submitted); 111 | } 112 | 113 | $calendar = []; 114 | foreach($posts as $post) { 115 | $postDate = $post->post_date ?: $post->date_submitted; 116 | $day = printLocalDate('j', $postDate, $post->tzoffset); 117 | if(!array_key_exists($day, $calendar)) 118 | $calendar[(int)$day] = []; 119 | $calendar[(int)$day][] = $post; 120 | } 121 | ksort($calendar); 122 | 123 | return render($response, 'calendar', [ 124 | 'title' => 'IndieNews ' . $lang, 125 | 'date' => $date, 126 | 'year' => $year, 127 | 'month' => $month, 128 | 'calendar' => $calendar, 129 | 'meta' => '', 130 | 'lang' => $lang, 131 | 'next' => $next, 132 | 'prev' => $prev 133 | ]); 134 | }); 135 | 136 | 137 | 138 | 139 | // Language-specific submit instructions 140 | $app->get('/{lang:'.LANG_REGEX.'}/submit', function($request, $response, $args) { 141 | I18n::setLocale($args['lang']); 142 | 143 | return render($response, 'submit', array( 144 | 'title' => __('About IndieNews'), 145 | 'meta' => '', 146 | 'lang' => $args['lang'] 147 | )); 148 | }); 149 | 150 | 151 | $app->get('/{lang:'.LANG_REGEX.'}/members', function($request, $response, $args) { 152 | I18n::setLocale($args['lang']); 153 | 154 | $users = ORM::for_table('users') 155 | ->select('users.*') 156 | ->select_expr('COUNT(posts.id) AS num_posts') 157 | ->join('posts', ['posts.user_id', '=', 'users.id']) 158 | ->where('posts.lang', $args['lang']) 159 | ->where('posts.deleted', 0) 160 | ->where_gt('posts.date_submitted', date('Y-m-d H:i:s', strtotime('1 year ago'))) 161 | ->group_by('users.id') 162 | ->order_by_desc('num_posts') 163 | ->find_many(); 164 | 165 | return render($response, 'members', array( 166 | 'title' => __('IndieNews Members'), 167 | 'meta' => '', 168 | 'lang' => $args['lang'], 169 | 'users' => $users 170 | )); 171 | }); 172 | 173 | 174 | // Language-specific permalinks 175 | $app->get('/{lang:'.LANG_REGEX.'}/{slug:.*?}{format:|\.json|\.jf2}', function($request, $response, $args) { 176 | $format = formatFromRouteArgs($args); 177 | $slug = $args['slug']; 178 | 179 | I18n::setLocale($args['lang']); 180 | 181 | $post = ORM::for_table('posts') 182 | ->where_in('href', array('http://'.$slug,'https://'.$slug)) 183 | ->where('deleted', 0) 184 | ->find_one(); 185 | $posts = array($post); 186 | 187 | $response = $response->withAddedHeader('Link', '<' . Config::$baseURL . '/'.$args['lang'].'/webmention>; rel="webmention"'); 188 | 189 | if(!$post) { 190 | return $response->withStatus(404); 191 | } 192 | 193 | $renderer = new PhpRenderer(__DIR__.'/../views/'); 194 | $renderer->setLayout('layout.php'); 195 | 196 | $temp = $renderer->render(new \Slim\Psr7\Response(), "post.php", [ 197 | 'title' => $post->title, 198 | 'post' => $post, 199 | 'view' => 'single', 200 | 'meta' => '', 201 | 'lang' => $args['lang'] 202 | ]); 203 | 204 | $html = $temp->getBody()->__toString(); 205 | 206 | return respondWithFormat($response, $html, $format); 207 | }); 208 | -------------------------------------------------------------------------------- /views/comment-full.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

How to Comment on an IndieNews Post

5 | 6 |

In order to comment on a post, you do not need an IndieNews account. Instead, you 7 | can only submit comments as posts from your own site, by linking to the IndieNews page 8 | you wish to comment on and sending a notification using the 9 | webmention protocol!

10 | 11 | 12 |

1. Write a comment as a post on your own site

13 | 14 |

Create a new post on your site, and mark it up with the Microformats markup for 15 | an h-entry.

16 | 17 | 18 | 19 |

2. Add an "in-reply-to" link to the original post

20 | 21 |

Somewhere in the h-entry, add a link to the original post you are commenting on 22 | (not the IndieNews URL) with the class "u-in-reply-to". 23 | This usually looks something like the following:

24 | 25 |

<a href="http://aaronparecki.com/notes/2013/04/25/1" class="u-in-reply-to" rel="in-reply-to">
 26 |   In Reply To @aaronpk
 27 | </a>

28 | 29 | 30 | 31 |

3. Add a "u-syndication" link to IndieNews

32 | 33 |

Inside the h-entry, add a link to the IndieNews URL for the post with the class 34 | u-syndication. This usually 35 | looks something like the following:

36 | 37 |

<a href="https://news.indieweb.org/post/aaronparecki.com/notes/2013/04/25/1" class="u-syndication" rel="syndication">Also posted on IndieNews</a>

38 | 39 |

You can construct the IndieNews URL before it's posted to IndieNews by following the 40 | convention IndieNews uses for building its permalinks. Follow the example above, or 41 | read the full instructions on constructing post URLs. 42 | 43 | 44 | 45 |

4. Send a WebMention

46 | 47 |

Example Request

48 | 49 |

Make a POST request to news.indieweb.org/webmention with two parameters, 50 | source and target, where target is 51 | https://news.indieweb.org/post/example.com/100 and source is 52 | http://example.com/100 assuming you are submitting a page on your site with 53 | the url http://example.com/100.

54 | 55 |
POST /webmention HTTP/1.1
 56 | Host: news.indieweb.org
 57 | 
 58 | target=https://news.indieweb.org/post/aaronparecki.com/notes/2013/04/25/1/original-post-discovery
 59 | &source=http://aaronparecki.com/notes/2013/04/25/1/original-post-discovery
 60 | 
61 | 62 |

Example Response

63 | 64 |
{
 65 |  "result": "success",
 66 |  "notices": [
 67 |  ],
 68 |  "data": {
 69 |    "title": "A demonstration of Original Post Discovery #indieweb",
 70 |    "author": "aaronparecki.com",
 71 |    "date": "2013-04-26T03:22:39+00:00"
 72 |  },
 73 |  "source": "http://aaronparecki.com/notes/2013/04/25/1/original-post-discovery",
 74 |  "target": "https://news.indieweb.org/post/aaronparecki.com/notes/2013/04/25/1/original-post-discovery",
 75 |  "href": "https://news.indieweb.org/post/aaronparecki.com/notes/2013/04/25/1/original-post-discovery"
 76 | }
 77 | 
78 | 79 |

This webmention endpoint returns more data than is technically required for a WebMention to succeed. It will 80 | return data that is useful for debugging purposes while you're initially trying it out.

81 | 82 |
    83 |
  • result - Will be equal to "success" if the submission was accepted
  • 84 |
  • notices - An array of string messages if there was anything that needs attention in your submission. These are not errors, but will indicate if microformat markup was not present or invalid.
  • 85 |
  • data - This object shows the values extracted from the page, including title, author and date.
  • 86 |
  • source - The source URL sent in the initial request.
  • 87 |
  • target - The target URL sent in the initial request.
  • 88 |
  • href - The permalink to this submission on news.indieweb.org.
  • 89 |
  • canonical - If you accidentally linked your "in-reply-to" to the IndieNews URL, this field will tell you the canonical URL of the post you were actually replying to.
  • 90 |
91 | 92 |

Sample Code

93 | 94 |
Curl
95 |
curl https://news.indieweb.org/webmention -i \
 96 |   -d target=https://news.indieweb.org/post/aaronparecki.com/notes/2013/04/25/1/original-post-discovery \
 97 |   -d source=http://aaronparecki.com/notes/2013/04/25/1/original-post-discovery
 98 | 
99 | 100 |
PHP
101 |
<?php
102 | $ch = curl_init("https://news.indieweb.org/webmention");
103 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
104 | curl_setopt($ch, CURLOPT_POST, true);
105 | curl_setopt($ch, CURLOPT_POSTFIELDS, array(
106 |   'target' => 'https://news.indieweb.org/post/aaronparecki.com/notes/2013/04/25/1/original-post-discovery',
107 |   'source' => 'http://aaronparecki.com/notes/2013/04/25/1/original-post-discovery'
108 | ));
109 | echo curl_exec($ch);
110 | ?>
111 | 112 |
Ruby
113 |
require 'rest-client'
114 | require 'json'
115 | 
116 | data = JSON.parse RestClient.post "https://news.indieweb.org/webmention", {
117 |   'target' => 'https://news.indieweb.org/post/aaronparecki.com/notes/2013/04/25/1/original-post-discovery',
118 |   'source' => 'http://aaronparecki.com/notes/2013/04/25/1/original-post-discovery'
119 | }
120 | jj data
121 | 
122 | 123 |

Re-Submitting a Post

124 | 125 |

If you update the post (for example trying to debug the microformats markup, or changing the post 126 | title), you can re-send the webmention. The existing post will be updated with the new information found.

127 | 128 | 129 |

Microformats Support

130 | 131 |

Your page must be marked up with an h-entry 132 | or an h-event, IndieNews will 133 | use the name in the entry as the title of the submission.

134 | 135 |

If an h-card is present, 136 | author information will be pulled from there, otherwise it will fall back to using the domain name as the author.

137 | 138 | 139 |

Pingback Support

140 | 141 |

If you use a client which automatically sends pingbacks to 142 | any links found in the post, then you can use the same flow as the WebMention flow but send a Pingback instead! 143 | You can find the pingback endpoint using the normal pingback discovery mechanism.

144 | 145 |

Note that the rich debugging response will not be present in the pingback reply.

146 | 147 |
148 |
149 | -------------------------------------------------------------------------------- /views/submit-full.php: -------------------------------------------------------------------------------- 1 |
2 |

IndieNews

3 |

IndieNews is a community-curated list of articles relevant to the Indie Web.

4 |
5 | 6 |
7 |
8 | 9 |

How to Submit a Post to IndieNews

10 | 11 |

In order to submit a post, you do not need an IndieNews account. Instead, you can only submit 12 | posts from your own site by linking to the IndieNews site and sending a notification 13 | using the webmention protocol!

14 | 15 | 16 |

1. Write a post on your own site

17 | 18 |

Create a new post on your site, and mark it up with the Microformats markup for 19 | an h-entry.

20 | 21 |

If you are submitting your own post, that's all you have to do.

22 | 23 |

To submit someone else's post, you can post a bookmark on your site and submit that 24 | URL to IndieNews! Post an h-entry as normal, then include a 25 | u-bookmark-of property 26 | linking to the actual URL you want to submit.

27 | 28 | 29 |

2. Add a "u-syndication" or "u-category" link to IndieNews

30 | 31 |

Inside the h-entry, add a link to the IndieNews home page for your language with the class 32 | u-syndication or 33 | u-category. This usually 34 | looks something like the following:

35 | 36 |

<a href="https://news.indieweb.org/en" class="u-syndication">
 37 |   Also posted on IndieNews
 38 | </a>

39 | 40 |

<a href="https://news.indieweb.org/en" class="u-category">#indienews</a>

41 | 42 | 43 |

3. Send a Webmention

44 | 45 |

If your website sends Webmentions automatically, then you don't need to worry about this step. Otherwise, follow the instructions below to send a Webmention, or just use the Webmention form available at the IndieNews Webmention endpoint.

46 | 47 |

Example Request

48 | 49 |

Make a POST request to https://news.indieweb.org/en/webmention with two parameters, 50 | source and target, where target is 51 | https://news.indieweb.org/en and source is 52 | http://example.com/100 assuming you are submitting a page on your site with 53 | the url http://example.com/100.

54 | 55 |

Note that each language's home page has a unique Webmention endpoint, so you should 56 | do the Webmention endpoint discovery as normal to find it.

57 | 58 |
POST /en/webmention HTTP/1.1
 59 | Host: news.indieweb.org
 60 | 
 61 | target=https://news.indieweb.org/en
 62 | &source=https://aaronparecki.com/2013/04/25/4/original-post-discovery
 63 | 
64 | 65 | 66 |

Example Response

67 | 68 |
HTTP/1.1 201 Created
 69 | Location: https://news.indieweb.org/en/aaronparecki.com/2013/04/25/4/original-post-discovery
 70 | 
 71 | {
 72 |  "result": "success",
 73 |  "notices": [
 74 |  ],
 75 |  "data": {
 76 |    "title": "A demonstration of Original Post Discovery #indieweb",
 77 |    "author": "aaronparecki.com",
 78 |    "date": "2013-04-26T03:22:39+00:00"
 79 |  },
 80 |  "source": "https://aaronparecki.com/2013/04/25/4/original-post-discovery",
 81 |  "url": "https://news.indieweb.org/en/aaronparecki.com/2013/04/25/4/original-post-discovery"
 82 | }
 83 | 
84 | 85 |

You can find the permalink of your syndication by looking for the Location header in the response. You can then update your post with that URL so that your post always links to the IndieNews permalink instead of the IndieNews home page.

86 | 87 |

This webmention endpoint also returns more data that is useful for debugging purposes while you're initially trying it out.

88 | 89 |
    90 |
  • result - Will be equal to "success" if the submission was accepted
  • 91 |
  • notices - An array of string messages if there was anything that needs attention in your submission. These are not errors, but will indicate if microformat markup was not present or invalid.
  • 92 |
  • data - This object shows the values extracted from the page, including title, author and date.
  • 93 |
  • source - The source URL sent in the initial request
  • 94 |
  • url - The permalink to this submission on news.indieweb.org.
  • 95 |
96 | 97 |

Sample Code

98 | 99 |
Curl
100 |
curl https://news.indieweb.org/en/webmention -i \
101 |   -d target=https://news.indieweb.org/en \
102 |   -d source=https://aaronparecki.com/2013/04/25/4/original-post-discovery
103 | 
104 | 105 |
PHP
106 |
<?php
107 | $ch = curl_init("https://news.indieweb.org/en/webmention");
108 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
109 | curl_setopt($ch, CURLOPT_POST, true);
110 | curl_setopt($ch, CURLOPT_POSTFIELDS, array(
111 |   'target' => 'https://news.indieweb.org/en',
112 |   'source' => 'https://aaronparecki.com/2013/04/25/4/original-post-discovery'
113 | ));
114 | echo curl_exec($ch);
115 | ?>
116 | 117 |
Ruby
118 |
require 'rest-client'
119 | require 'json'
120 | 
121 | data = JSON.parse RestClient.post "https://news.indieweb.org/en/webmention", {
122 |   'target' => 'https://news.indieweb.org/en',
123 |   'source' => 'https://aaronparecki.com/2013/04/25/4/original-post-discovery'
124 | }
125 | jj data
126 | 
127 | 128 |

Re-Submitting a Post

129 | 130 |

If you update the post (for example trying to debug the microformats markup, or changing the post 131 | title), you can re-send the webmention. The existing IndieNews post will be updated with the new information found.

132 | 133 | 134 |

Microformats Support

135 | 136 |

Your page must be marked up with an h-entry 137 | or an h-event, IndieNews will 138 | use the name in the entry as the title of the submission.

139 | 140 |

If an h-card is present, 141 | author information will be pulled from there, otherwise it will fall back to using the domain name as the author.

142 | 143 | 144 |

Pingback Support

145 | 146 |

If you use a client which automatically sends pingbacks to 147 | any links found in the post, then you can use the same flow as the WebMention flow but send a Pingback instead! 148 | You can find the pingback endpoint using the normal pingback discovery mechanism.

149 | 150 |

Note that the rich debugging response will not be present in the pingback reply.

151 | 152 |
153 |
154 | -------------------------------------------------------------------------------- /public/js/jquery.timeago.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Timeago is a jQuery plugin that makes it easy to support automatically 3 | * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago"). 4 | * 5 | * @name timeago 6 | * @version 1.4.2 7 | * @requires jQuery v1.2.3+ 8 | * @author Ryan McGeary 9 | * @license MIT License - http://www.opensource.org/licenses/mit-license.php 10 | * 11 | * For usage and examples, visit: 12 | * http://timeago.yarp.com/ 13 | * 14 | * Copyright (c) 2008-2015, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org) 15 | */ 16 | 17 | (function (factory) { 18 | if (typeof define === 'function' && define.amd) { 19 | // AMD. Register as an anonymous module. 20 | define(['jquery'], factory); 21 | } if (typeof module === 'object' && typeof module.exports === 'object') { 22 | factory(require('jquery')); 23 | } else { 24 | // Browser globals 25 | factory(jQuery); 26 | } 27 | }(function ($) { 28 | $.timeago = function(timestamp) { 29 | if (timestamp instanceof Date) { 30 | return inWords(timestamp); 31 | } else if (typeof timestamp === "string") { 32 | return inWords($.timeago.parse(timestamp)); 33 | } else if (typeof timestamp === "number") { 34 | return inWords(new Date(timestamp)); 35 | } else { 36 | return inWords($.timeago.datetime(timestamp)); 37 | } 38 | }; 39 | var $t = $.timeago; 40 | 41 | $.extend($.timeago, { 42 | settings: { 43 | refreshMillis: 60000, 44 | allowPast: true, 45 | allowFuture: false, 46 | localeTitle: false, 47 | cutoff: 0, 48 | strings: { 49 | prefixAgo: null, 50 | prefixFromNow: null, 51 | suffixAgo: "ago", 52 | suffixFromNow: "from now", 53 | inPast: 'any moment now', 54 | seconds: "less than a minute", 55 | minute: "about a minute", 56 | minutes: "%d minutes", 57 | hour: "about an hour", 58 | hours: "about %d hours", 59 | day: "a day", 60 | days: "%d days", 61 | month: "about a month", 62 | months: "%d months", 63 | year: "about a year", 64 | years: "%d years", 65 | wordSeparator: " ", 66 | numbers: [] 67 | } 68 | }, 69 | 70 | inWords: function(distanceMillis) { 71 | if(!this.settings.allowPast && ! this.settings.allowFuture) { 72 | throw 'timeago allowPast and allowFuture settings can not both be set to false.'; 73 | } 74 | 75 | var $l = this.settings.strings; 76 | var prefix = $l.prefixAgo; 77 | var suffix = $l.suffixAgo; 78 | if (this.settings.allowFuture) { 79 | if (distanceMillis < 0) { 80 | prefix = $l.prefixFromNow; 81 | suffix = $l.suffixFromNow; 82 | } 83 | } 84 | 85 | if(!this.settings.allowPast && distanceMillis >= 0) { 86 | return this.settings.strings.inPast; 87 | } 88 | 89 | var seconds = Math.abs(distanceMillis) / 1000; 90 | var minutes = seconds / 60; 91 | var hours = minutes / 60; 92 | var days = hours / 24; 93 | var years = days / 365; 94 | 95 | function substitute(stringOrFunction, number) { 96 | var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction; 97 | var value = ($l.numbers && $l.numbers[number]) || number; 98 | return string.replace(/%d/i, value); 99 | } 100 | 101 | var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) || 102 | seconds < 90 && substitute($l.minute, 1) || 103 | minutes < 45 && substitute($l.minutes, Math.round(minutes)) || 104 | minutes < 90 && substitute($l.hour, 1) || 105 | hours < 24 && substitute($l.hours, Math.round(hours)) || 106 | hours < 42 && substitute($l.day, 1) || 107 | days < 30 && substitute($l.days, Math.round(days)) || 108 | days < 45 && substitute($l.month, 1) || 109 | days < 365 && substitute($l.months, Math.round(days / 30)) || 110 | years < 1.5 && substitute($l.year, 1) || 111 | substitute($l.years, Math.round(years)); 112 | 113 | var separator = $l.wordSeparator || ""; 114 | if ($l.wordSeparator === undefined) { separator = " "; } 115 | return $.trim([prefix, words, suffix].join(separator)); 116 | }, 117 | 118 | parse: function(iso8601) { 119 | var s = $.trim(iso8601); 120 | s = s.replace(/\.\d+/,""); // remove milliseconds 121 | s = s.replace(/-/,"/").replace(/-/,"/"); 122 | s = s.replace(/T/," ").replace(/Z/," UTC"); 123 | s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400 124 | s = s.replace(/([\+\-]\d\d)$/," $100"); // +09 -> +0900 125 | return new Date(s); 126 | }, 127 | datetime: function(elem) { 128 | var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title"); 129 | return $t.parse(iso8601); 130 | }, 131 | isTime: function(elem) { 132 | // jQuery's `is()` doesn't play well with HTML5 in IE 133 | return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time"); 134 | } 135 | }); 136 | 137 | // functions that can be called via $(el).timeago('action') 138 | // init is default when no action is given 139 | // functions are called with context of a single element 140 | var functions = { 141 | init: function(){ 142 | var refresh_el = $.proxy(refresh, this); 143 | refresh_el(); 144 | var $s = $t.settings; 145 | if ($s.refreshMillis > 0) { 146 | this._timeagoInterval = setInterval(refresh_el, $s.refreshMillis); 147 | } 148 | }, 149 | update: function(time){ 150 | var parsedTime = $t.parse(time); 151 | $(this).data('timeago', { datetime: parsedTime }); 152 | if($t.settings.localeTitle) $(this).attr("title", parsedTime.toLocaleString()); 153 | refresh.apply(this); 154 | }, 155 | updateFromDOM: function(){ 156 | $(this).data('timeago', { datetime: $t.parse( $t.isTime(this) ? $(this).attr("datetime") : $(this).attr("title") ) }); 157 | refresh.apply(this); 158 | }, 159 | dispose: function () { 160 | if (this._timeagoInterval) { 161 | window.clearInterval(this._timeagoInterval); 162 | this._timeagoInterval = null; 163 | } 164 | } 165 | }; 166 | 167 | $.fn.timeago = function(action, options) { 168 | var fn = action ? functions[action] : functions.init; 169 | if(!fn){ 170 | throw new Error("Unknown function name '"+ action +"' for timeago"); 171 | } 172 | // each over objects here and call the requested function 173 | this.each(function(){ 174 | fn.call(this, options); 175 | }); 176 | return this; 177 | }; 178 | 179 | function refresh() { 180 | //check if it's still visible 181 | if(!$.contains(document.documentElement,this)){ 182 | //stop if it has been removed 183 | $(this).timeago("dispose"); 184 | return this; 185 | } 186 | 187 | var data = prepareData(this); 188 | var $s = $t.settings; 189 | 190 | if (!isNaN(data.datetime)) { 191 | if ( $s.cutoff == 0 || Math.abs(distance(data.datetime)) < $s.cutoff) { 192 | $(this).text(inWords(data.datetime)); 193 | } 194 | } 195 | return this; 196 | } 197 | 198 | function prepareData(element) { 199 | element = $(element); 200 | if (!element.data("timeago")) { 201 | element.data("timeago", { datetime: $t.datetime(element) }); 202 | var text = $.trim(element.text()); 203 | if ($t.settings.localeTitle) { 204 | element.attr("title", element.data('timeago').datetime.toLocaleString()); 205 | } else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) { 206 | element.attr("title", text); 207 | } 208 | } 209 | return element.data("timeago"); 210 | } 211 | 212 | function inWords(date) { 213 | return $t.inWords(distance(date)); 214 | } 215 | 216 | function distance(date) { 217 | return (new Date().getTime() - date.getTime()); 218 | } 219 | 220 | // fix for IE6 suckage 221 | document.createElement("abbr"); 222 | document.createElement("time"); 223 | })); 224 | -------------------------------------------------------------------------------- /controllers/webmention.php: -------------------------------------------------------------------------------- 1 | get('/{lang:'.LANG_REGEX.'}/webmention', function($request, $response, $args) { 5 | I18n::setLocale($args['lang']); 6 | 7 | return render($response, 'webmention', array( 8 | 'title' => 'IndieNews Webmention Endpoint', 9 | 'meta' => '', 10 | 'lang' => $args['lang'] 11 | )); 12 | }); 13 | 14 | $app->post('/{lang:'.LANG_REGEX.'}/webmention', function($request, $response, $args) { 15 | 16 | $params = (array)$request->getParsedBody(); 17 | $is_html = $params['html'] ?? false; 18 | 19 | $sourceURL = $params['source'] ?? null; 20 | $targetURL = $params['target'] ?? null; 21 | 22 | $error = function($err, $description=false) use($is_html, $response, $args) { 23 | if($is_html) { 24 | $response = $response->withStatus(400); 25 | return render($response, 'webmention-error', [ 26 | 'title' => 'Webmention Error', 27 | 'error' => $err, 28 | 'description' => $description, 29 | 'meta' => '', 30 | 'lang' => $args['lang'] 31 | ]); 32 | } else { 33 | $error = array( 34 | 'error' => $err 35 | ); 36 | if($description) 37 | $error['error_description'] = $description; 38 | 39 | $response->getBody()->write(json_encode($error)); 40 | return $response->withHeader('Content-Type', 'application/json')->withStatus(400); 41 | } 42 | }; 43 | 44 | if($sourceURL == null) { 45 | return $error('missing_source_url', 'No source URL was provided in the request.'); 46 | } 47 | 48 | $source = parse_url($sourceURL); 49 | 50 | # Verify $source is valid 51 | if($source == null 52 | || !array_key_exists('scheme', $source) 53 | || !in_array($source['scheme'], array('http','https')) 54 | || !array_key_exists('host', $source) 55 | ) { 56 | return $error('invalid_source_url', 'The source URL was not valid. Ensure the URL is an http or https URL.'); 57 | } 58 | 59 | 60 | # Check if the source URL or domain is blocked 61 | $blocked = ORM::for_table('blocks') 62 | ->where_raw('domain = ? OR source_url = ?', [$source['host'], $sourceURL]) 63 | ->find_one(); 64 | if($blocked) { 65 | return $error('blocked', 'The source URL has been blocked from this website.'); 66 | } 67 | 68 | 69 | if($targetURL == null) { 70 | return $error('missing_target_url', 'No target URL was provided in the request.'); 71 | } 72 | 73 | # Verify $target is actually a resource under our control (home page, individual post) 74 | $target = parse_url($targetURL); 75 | 76 | # Verify $source is valid 77 | if($target == null 78 | || !array_key_exists('scheme', $target) 79 | || !in_array($target['scheme'], array('http','https')) 80 | || !array_key_exists('host', $target) 81 | || $target['host'] != parse_url(Config::$baseURL, PHP_URL_HOST) 82 | ) { 83 | return $error('target_not_supported', 'The target URL provided is not supported. Only '.Config::$baseURL.' URLs are accepted.'); 84 | } 85 | 86 | if(!preg_match('/^' . str_replace('/', '\/', Config::$baseURL) . '(?:\/('.LANG_REGEX.'))\/?$/', $targetURL, $match)) { 87 | return $error('target_not_supported', 'The target you specified does not match a supported URL on this site.'); 88 | } 89 | 90 | // Parse the language from the target URL, so that the story ends up on the specified 91 | // feed regardless of which endpoint it was sent to. 92 | // If no lang was sent in the target param (like if they just linked to the indienews home page), 93 | // then use the language specified by the webmention endpoint. 94 | if(array_key_exists(1, $match)) 95 | $lang = $match[1]; 96 | 97 | $record = array( 98 | 'post_author' => $source['scheme'].'://'.$source['host'], 99 | 'title' => false, 100 | 'body' => false, 101 | 'date' => false 102 | ); 103 | $notices = array(); 104 | 105 | # Now fetch and parse the page looking for Microformats 106 | $xray = new p3k\XRay(); 107 | $xray->http = new p3k\HTTP('IndieNews/1.0.0 (https://news.indieweb.org/)'); 108 | $xrayresponse = $xray->parse($sourceURL, ['ignore-as2' => true]); 109 | 110 | if(isset($xrayresponse['error'])) { 111 | return $error($xrayresponse['error'], 'An error occurred while attempting to fetch the source URL: ' . $xrayresponse['error_description']); 112 | } 113 | 114 | $post = $xrayresponse['data']; 115 | if(isset($xrayresponse['refs'])) 116 | $refs = $xrayresponse['refs']; 117 | else 118 | $refs = []; 119 | 120 | if(!isset($post['type']) || !in_array($post['type'], ['entry','event'])) { 121 | return $error('no_link_found', 'No h-entry or h-event was found on the page, so we were unable to find a u-syndication or u-category URL. If you have multiple top-level h-* objects, ensure that one of them has a u-url property set to the URL of the page.'); 122 | } 123 | 124 | $authorURL = false; 125 | 126 | if($post['type'] == 'entry') { 127 | 128 | if(isset($post['name'])) { 129 | $record['title'] = ellipsize_to_word($post['name'], 200, '...', 10); 130 | } else { 131 | $notices[] = 'No "name" property found on the h-entry.'; 132 | } 133 | 134 | if(isset($post['content'])) { 135 | $record['body'] = ellipsize_to_word($post['content']['text'], 500, '...', 10); 136 | } 137 | 138 | if(array_key_exists('published', $post)) { 139 | try { 140 | $published = new DateTime($post['published']); 141 | } catch(Exception $e) { 142 | $notices[] = 'Failed to parse published date'; 143 | } 144 | if(isset($published)) { 145 | $record['date'] = $published; 146 | $utcdate = clone $published; 147 | $utcdate->setTimeZone(new DateTimeZone('UTC')); 148 | } 149 | } else { 150 | $notices[] = 'No published date found'; 151 | } 152 | 153 | 154 | } elseif($post['type'] == 'event') { 155 | if(isset($post['name'])) { 156 | $record['title'] = ellipsize_to_word($post['name'], 200, '...', 10); 157 | } else { 158 | $notices[] = 'No "name" was found for this h-event'; 159 | } 160 | 161 | if(isset($post['start'])) { 162 | try { 163 | $start = new DateTime($post['start']); 164 | } catch(Exception $e) { 165 | $notices[] = 'Failed to parse start date'; 166 | } 167 | if(isset($start) && $start) { 168 | $record['date'] = $start; 169 | $utcdate = clone $start; 170 | $utcdate->setTimeZone(new DateTimeZone('UTC')); 171 | } 172 | } 173 | 174 | if($locations=$post['location']) { 175 | $locationURL = $locations[0]; 176 | if(array_key_exists($locationURL, $refs)) { 177 | $location = $refs[$locationURL]; 178 | if(array_key_exists('name', $location)) { 179 | $record['title'] .= ' at ' . ellipsize_to_word($location['name'], 200, '...', 10); 180 | } 181 | } 182 | } 183 | } 184 | 185 | if(isset($post['author']) && $post['author']['url']) { 186 | $authorURL = parse_url($post['author']['url']); 187 | if($authorURL && array_key_exists('host', $authorURL)) { 188 | $record['post_author'] = $post['author']['url']; 189 | } else { 190 | $notices[] = 'No host was found on the author URL (' . $post['author']['url'] . ')'; 191 | } 192 | } else { 193 | $notices[] = 'No author URL was found for the h-entry. Using the domain name instead.'; 194 | } 195 | 196 | $synURL = false; 197 | if(array_key_exists('syndication', $post)) { 198 | foreach($post['syndication'] as $syn) { 199 | if(strpos($syn, Config::$baseURL) === 0) { 200 | $synURL = $syn; 201 | } 202 | } 203 | } 204 | if(array_key_exists('category', $post)) { 205 | foreach($post['category'] as $cat) { 206 | if(strpos($cat, Config::$baseURL) === 0) { 207 | $synURL = $cat; 208 | } 209 | } 210 | } 211 | if(!$synURL) { 212 | // Check if this post was previously submitted, and delete it if so 213 | $post = ORM::for_table('posts')->where('source_url', $sourceURL)->find_one(); 214 | if($post) { 215 | $post->deleted = 1; 216 | $post->save(); 217 | 218 | $data = array( 219 | 'result' => 'deleted', 220 | ); 221 | 222 | $response->getBody()->write(json_encode($data, JSON_PRETTY_PRINT+JSON_UNESCAPED_SLASHES)); 223 | return $response; 224 | } 225 | 226 | return $error('no_link_found', 'Could not find a syndication or category link for this entry to news.indieweb.org. Please see https://news.indieweb.org/how for more information.'); 227 | } 228 | if($synURL != $targetURL) { 229 | return $error('target_mismatch', 'The URL on the page did not match the target URL of the Webmention. Make sure your post links to ' . $targetURL); 230 | } 231 | 232 | if(array_key_exists('in-reply-to', $post)) { 233 | // We can only use the first in-reply-to. Not sure what the correct behavior would be for multiple. 234 | $inReplyTo = $post['in-reply-to'][0]; 235 | } else { 236 | $inReplyTo = false; 237 | } 238 | 239 | # Get the domain of $source and find or create a user account 240 | $user = ORM::for_table('users')->where('url', $record['post_author'])->find_one(); 241 | 242 | if($user == FALSE) { 243 | $user = ORM::for_table('users')->create(); 244 | $user->url = $record['post_author']; 245 | $user->date_created = date('Y-m-d H:i:s'); 246 | $user->save(); 247 | } 248 | 249 | $href = $sourceURL; 250 | 251 | if(array_key_exists('bookmark-of', $post)) { 252 | // Strip utm tracking params 253 | $href = p3k\url\strip_tracking_params($post['bookmark-of'][0]); 254 | if(array_key_exists($href, $refs)) { 255 | if(array_key_exists('name', $refs[$href])) { 256 | $record['title'] = $refs[$href]['name']; 257 | } else { 258 | // TODO: Parse the bookmark URL and find the canonical post title 259 | } 260 | } 261 | // If this is a submission of a bookmark, set the post author to the bookmark website. 262 | // For now, just set it to the domain of the bookmark. Later we could parse the bookmark for an h-card. 263 | if($href != $sourceURL) { 264 | $record['post_author'] = parse_url($href, PHP_URL_SCHEME) . '://' . parse_url($href, PHP_URL_HOST); 265 | } 266 | } 267 | 268 | $indieNewsPermalink = permalinkForURL($lang, $href); 269 | 270 | # If there is no existing post for $sourceURL, update the properties 271 | $post = ORM::for_table('posts')->where('lang', $lang)->where('source_url', $sourceURL)->find_one(); 272 | if($post != FALSE) { 273 | if($record['date']) { 274 | $post->post_date = $utcdate->format('Y-m-d H:i:s'); 275 | $post->tzoffset = $record['date']->format('Z'); 276 | } 277 | $post->post_author = $record['post_author']; 278 | $post->title = $record['title']; 279 | if($inReplyTo) 280 | $post->in_reply_to = $inReplyTo; 281 | if($record['body']) 282 | $post->body = $record['body']; 283 | $post->save(); 284 | $notices[] = 'Already registered, updating properties of the post.'; 285 | $update = true; 286 | } else { 287 | # Record a new post 288 | $post = ORM::for_table('posts')->create(); 289 | $post->lang = $lang; 290 | $post->user_id = $user->id; 291 | $post->date_submitted = date('Y-m-d H:i:s'); 292 | if($record['date']) { 293 | $post->post_date = $utcdate->format('Y-m-d H:i:s'); 294 | $post->tzoffset = $record['date']->format('Z'); 295 | } 296 | $post->post_author = $record['post_author']; 297 | $post->title = $record['title']; 298 | if($inReplyTo) 299 | $post->in_reply_to = $inReplyTo; 300 | if($record['body']) 301 | $post->body = $record['body']; 302 | $post->href = $href; 303 | $post->source_url = $sourceURL; 304 | $post->save(); 305 | $update = false; 306 | 307 | irc_notice('[indienews' . ($lang == 'en' ? '' : '/'.strtolower($lang)) . '] New post: ' . ($post->title ? '"'.$post->title.'" ' : '') . $post->href . ($sourceURL == $href ? '' : ' (from ' . $sourceURL . ')')); 308 | 309 | # Ping the hub 310 | if(Config::$hubURL) { 311 | $ch = curl_init(Config::$hubURL); 312 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 313 | curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ 314 | 'hub.mode' => 'publish', 315 | 'hub.topic' => Config::$baseURL . '/' . $lang 316 | ])); 317 | curl_setopt($ch, CURLOPT_TIMEOUT, 4); 318 | curl_exec($ch); 319 | } 320 | } 321 | 322 | if($is_html) { 323 | return $response 324 | ->withHeader('Location', $indieNewsPermalink) 325 | ->withStatus(302); 326 | } else { 327 | $response = $response 328 | ->withHeader('Content-Type', 'application/json') 329 | ->withStatus(201); 330 | 331 | $responseData = array( 332 | 'title' => $record['title'], 333 | 'body' => $record['body'] ? true : false, 334 | 'author' => $record['post_author'], 335 | 'date' => ($record['date'] ? $record['date']->format('c') : false) 336 | ); 337 | if($inReplyTo) 338 | $responseData['in-reply-to'] = $inReplyTo; 339 | 340 | $data = array( 341 | 'result' => 'success', 342 | 'notices' => $notices, 343 | 'data' => $responseData, 344 | 'source' => $sourceURL, 345 | 'url' => $indieNewsPermalink 346 | ); 347 | 348 | $response->getBody()->write(json_encode($data, JSON_PRETTY_PRINT+JSON_UNESCAPED_SLASHES)); 349 | 350 | return $response->withHeader('Location', $indieNewsPermalink); 351 | } 352 | }); 353 | 354 | -------------------------------------------------------------------------------- /public/bootstrap/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.2.2 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */@-ms-viewport{width:device-width}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /public/bootstrap/css/bootstrap-responsive.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.2.2 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */ 10 | 11 | @-ms-viewport { 12 | width: device-width; 13 | } 14 | 15 | .clearfix { 16 | *zoom: 1; 17 | } 18 | 19 | .clearfix:before, 20 | .clearfix:after { 21 | display: table; 22 | line-height: 0; 23 | content: ""; 24 | } 25 | 26 | .clearfix:after { 27 | clear: both; 28 | } 29 | 30 | .hide-text { 31 | font: 0/0 a; 32 | color: transparent; 33 | text-shadow: none; 34 | background-color: transparent; 35 | border: 0; 36 | } 37 | 38 | .input-block-level { 39 | display: block; 40 | width: 100%; 41 | min-height: 30px; 42 | -webkit-box-sizing: border-box; 43 | -moz-box-sizing: border-box; 44 | box-sizing: border-box; 45 | } 46 | 47 | .hidden { 48 | display: none; 49 | visibility: hidden; 50 | } 51 | 52 | .visible-phone { 53 | display: none !important; 54 | } 55 | 56 | .visible-tablet { 57 | display: none !important; 58 | } 59 | 60 | .hidden-desktop { 61 | display: none !important; 62 | } 63 | 64 | .visible-desktop { 65 | display: inherit !important; 66 | } 67 | 68 | @media (min-width: 768px) and (max-width: 979px) { 69 | .hidden-desktop { 70 | display: inherit !important; 71 | } 72 | .visible-desktop { 73 | display: none !important ; 74 | } 75 | .visible-tablet { 76 | display: inherit !important; 77 | } 78 | .hidden-tablet { 79 | display: none !important; 80 | } 81 | } 82 | 83 | @media (max-width: 767px) { 84 | .hidden-desktop { 85 | display: inherit !important; 86 | } 87 | .visible-desktop { 88 | display: none !important; 89 | } 90 | .visible-phone { 91 | display: inherit !important; 92 | } 93 | .hidden-phone { 94 | display: none !important; 95 | } 96 | } 97 | 98 | @media (min-width: 1200px) { 99 | .row { 100 | margin-left: -30px; 101 | *zoom: 1; 102 | } 103 | .row:before, 104 | .row:after { 105 | display: table; 106 | line-height: 0; 107 | content: ""; 108 | } 109 | .row:after { 110 | clear: both; 111 | } 112 | [class*="span"] { 113 | float: left; 114 | min-height: 1px; 115 | margin-left: 30px; 116 | } 117 | .container, 118 | .navbar-static-top .container, 119 | .navbar-fixed-top .container, 120 | .navbar-fixed-bottom .container { 121 | width: 1170px; 122 | } 123 | .span12 { 124 | width: 1170px; 125 | } 126 | .span11 { 127 | width: 1070px; 128 | } 129 | .span10 { 130 | width: 970px; 131 | } 132 | .span9 { 133 | width: 870px; 134 | } 135 | .span8 { 136 | width: 770px; 137 | } 138 | .span7 { 139 | width: 670px; 140 | } 141 | .span6 { 142 | width: 570px; 143 | } 144 | .span5 { 145 | width: 470px; 146 | } 147 | .span4 { 148 | width: 370px; 149 | } 150 | .span3 { 151 | width: 270px; 152 | } 153 | .span2 { 154 | width: 170px; 155 | } 156 | .span1 { 157 | width: 70px; 158 | } 159 | .offset12 { 160 | margin-left: 1230px; 161 | } 162 | .offset11 { 163 | margin-left: 1130px; 164 | } 165 | .offset10 { 166 | margin-left: 1030px; 167 | } 168 | .offset9 { 169 | margin-left: 930px; 170 | } 171 | .offset8 { 172 | margin-left: 830px; 173 | } 174 | .offset7 { 175 | margin-left: 730px; 176 | } 177 | .offset6 { 178 | margin-left: 630px; 179 | } 180 | .offset5 { 181 | margin-left: 530px; 182 | } 183 | .offset4 { 184 | margin-left: 430px; 185 | } 186 | .offset3 { 187 | margin-left: 330px; 188 | } 189 | .offset2 { 190 | margin-left: 230px; 191 | } 192 | .offset1 { 193 | margin-left: 130px; 194 | } 195 | .row-fluid { 196 | width: 100%; 197 | *zoom: 1; 198 | } 199 | .row-fluid:before, 200 | .row-fluid:after { 201 | display: table; 202 | line-height: 0; 203 | content: ""; 204 | } 205 | .row-fluid:after { 206 | clear: both; 207 | } 208 | .row-fluid [class*="span"] { 209 | display: block; 210 | float: left; 211 | width: 100%; 212 | min-height: 30px; 213 | margin-left: 2.564102564102564%; 214 | *margin-left: 2.5109110747408616%; 215 | -webkit-box-sizing: border-box; 216 | -moz-box-sizing: border-box; 217 | box-sizing: border-box; 218 | } 219 | .row-fluid [class*="span"]:first-child { 220 | margin-left: 0; 221 | } 222 | .row-fluid .controls-row [class*="span"] + [class*="span"] { 223 | margin-left: 2.564102564102564%; 224 | } 225 | .row-fluid .span12 { 226 | width: 100%; 227 | *width: 99.94680851063829%; 228 | } 229 | .row-fluid .span11 { 230 | width: 91.45299145299145%; 231 | *width: 91.39979996362975%; 232 | } 233 | .row-fluid .span10 { 234 | width: 82.90598290598291%; 235 | *width: 82.8527914166212%; 236 | } 237 | .row-fluid .span9 { 238 | width: 74.35897435897436%; 239 | *width: 74.30578286961266%; 240 | } 241 | .row-fluid .span8 { 242 | width: 65.81196581196582%; 243 | *width: 65.75877432260411%; 244 | } 245 | .row-fluid .span7 { 246 | width: 57.26495726495726%; 247 | *width: 57.21176577559556%; 248 | } 249 | .row-fluid .span6 { 250 | width: 48.717948717948715%; 251 | *width: 48.664757228587014%; 252 | } 253 | .row-fluid .span5 { 254 | width: 40.17094017094017%; 255 | *width: 40.11774868157847%; 256 | } 257 | .row-fluid .span4 { 258 | width: 31.623931623931625%; 259 | *width: 31.570740134569924%; 260 | } 261 | .row-fluid .span3 { 262 | width: 23.076923076923077%; 263 | *width: 23.023731587561375%; 264 | } 265 | .row-fluid .span2 { 266 | width: 14.52991452991453%; 267 | *width: 14.476723040552828%; 268 | } 269 | .row-fluid .span1 { 270 | width: 5.982905982905983%; 271 | *width: 5.929714493544281%; 272 | } 273 | .row-fluid .offset12 { 274 | margin-left: 105.12820512820512%; 275 | *margin-left: 105.02182214948171%; 276 | } 277 | .row-fluid .offset12:first-child { 278 | margin-left: 102.56410256410257%; 279 | *margin-left: 102.45771958537915%; 280 | } 281 | .row-fluid .offset11 { 282 | margin-left: 96.58119658119658%; 283 | *margin-left: 96.47481360247316%; 284 | } 285 | .row-fluid .offset11:first-child { 286 | margin-left: 94.01709401709402%; 287 | *margin-left: 93.91071103837061%; 288 | } 289 | .row-fluid .offset10 { 290 | margin-left: 88.03418803418803%; 291 | *margin-left: 87.92780505546462%; 292 | } 293 | .row-fluid .offset10:first-child { 294 | margin-left: 85.47008547008548%; 295 | *margin-left: 85.36370249136206%; 296 | } 297 | .row-fluid .offset9 { 298 | margin-left: 79.48717948717949%; 299 | *margin-left: 79.38079650845607%; 300 | } 301 | .row-fluid .offset9:first-child { 302 | margin-left: 76.92307692307693%; 303 | *margin-left: 76.81669394435352%; 304 | } 305 | .row-fluid .offset8 { 306 | margin-left: 70.94017094017094%; 307 | *margin-left: 70.83378796144753%; 308 | } 309 | .row-fluid .offset8:first-child { 310 | margin-left: 68.37606837606839%; 311 | *margin-left: 68.26968539734497%; 312 | } 313 | .row-fluid .offset7 { 314 | margin-left: 62.393162393162385%; 315 | *margin-left: 62.28677941443899%; 316 | } 317 | .row-fluid .offset7:first-child { 318 | margin-left: 59.82905982905982%; 319 | *margin-left: 59.72267685033642%; 320 | } 321 | .row-fluid .offset6 { 322 | margin-left: 53.84615384615384%; 323 | *margin-left: 53.739770867430444%; 324 | } 325 | .row-fluid .offset6:first-child { 326 | margin-left: 51.28205128205128%; 327 | *margin-left: 51.175668303327875%; 328 | } 329 | .row-fluid .offset5 { 330 | margin-left: 45.299145299145295%; 331 | *margin-left: 45.1927623204219%; 332 | } 333 | .row-fluid .offset5:first-child { 334 | margin-left: 42.73504273504273%; 335 | *margin-left: 42.62865975631933%; 336 | } 337 | .row-fluid .offset4 { 338 | margin-left: 36.75213675213675%; 339 | *margin-left: 36.645753773413354%; 340 | } 341 | .row-fluid .offset4:first-child { 342 | margin-left: 34.18803418803419%; 343 | *margin-left: 34.081651209310785%; 344 | } 345 | .row-fluid .offset3 { 346 | margin-left: 28.205128205128204%; 347 | *margin-left: 28.0987452264048%; 348 | } 349 | .row-fluid .offset3:first-child { 350 | margin-left: 25.641025641025642%; 351 | *margin-left: 25.53464266230224%; 352 | } 353 | .row-fluid .offset2 { 354 | margin-left: 19.65811965811966%; 355 | *margin-left: 19.551736679396257%; 356 | } 357 | .row-fluid .offset2:first-child { 358 | margin-left: 17.094017094017094%; 359 | *margin-left: 16.98763411529369%; 360 | } 361 | .row-fluid .offset1 { 362 | margin-left: 11.11111111111111%; 363 | *margin-left: 11.004728132387708%; 364 | } 365 | .row-fluid .offset1:first-child { 366 | margin-left: 8.547008547008547%; 367 | *margin-left: 8.440625568285142%; 368 | } 369 | input, 370 | textarea, 371 | .uneditable-input { 372 | margin-left: 0; 373 | } 374 | .controls-row [class*="span"] + [class*="span"] { 375 | margin-left: 30px; 376 | } 377 | input.span12, 378 | textarea.span12, 379 | .uneditable-input.span12 { 380 | width: 1156px; 381 | } 382 | input.span11, 383 | textarea.span11, 384 | .uneditable-input.span11 { 385 | width: 1056px; 386 | } 387 | input.span10, 388 | textarea.span10, 389 | .uneditable-input.span10 { 390 | width: 956px; 391 | } 392 | input.span9, 393 | textarea.span9, 394 | .uneditable-input.span9 { 395 | width: 856px; 396 | } 397 | input.span8, 398 | textarea.span8, 399 | .uneditable-input.span8 { 400 | width: 756px; 401 | } 402 | input.span7, 403 | textarea.span7, 404 | .uneditable-input.span7 { 405 | width: 656px; 406 | } 407 | input.span6, 408 | textarea.span6, 409 | .uneditable-input.span6 { 410 | width: 556px; 411 | } 412 | input.span5, 413 | textarea.span5, 414 | .uneditable-input.span5 { 415 | width: 456px; 416 | } 417 | input.span4, 418 | textarea.span4, 419 | .uneditable-input.span4 { 420 | width: 356px; 421 | } 422 | input.span3, 423 | textarea.span3, 424 | .uneditable-input.span3 { 425 | width: 256px; 426 | } 427 | input.span2, 428 | textarea.span2, 429 | .uneditable-input.span2 { 430 | width: 156px; 431 | } 432 | input.span1, 433 | textarea.span1, 434 | .uneditable-input.span1 { 435 | width: 56px; 436 | } 437 | .thumbnails { 438 | margin-left: -30px; 439 | } 440 | .thumbnails > li { 441 | margin-left: 30px; 442 | } 443 | .row-fluid .thumbnails { 444 | margin-left: 0; 445 | } 446 | } 447 | 448 | @media (min-width: 768px) and (max-width: 979px) { 449 | .row { 450 | margin-left: -20px; 451 | *zoom: 1; 452 | } 453 | .row:before, 454 | .row:after { 455 | display: table; 456 | line-height: 0; 457 | content: ""; 458 | } 459 | .row:after { 460 | clear: both; 461 | } 462 | [class*="span"] { 463 | float: left; 464 | min-height: 1px; 465 | margin-left: 20px; 466 | } 467 | .container, 468 | .navbar-static-top .container, 469 | .navbar-fixed-top .container, 470 | .navbar-fixed-bottom .container { 471 | width: 724px; 472 | } 473 | .span12 { 474 | width: 724px; 475 | } 476 | .span11 { 477 | width: 662px; 478 | } 479 | .span10 { 480 | width: 600px; 481 | } 482 | .span9 { 483 | width: 538px; 484 | } 485 | .span8 { 486 | width: 476px; 487 | } 488 | .span7 { 489 | width: 414px; 490 | } 491 | .span6 { 492 | width: 352px; 493 | } 494 | .span5 { 495 | width: 290px; 496 | } 497 | .span4 { 498 | width: 228px; 499 | } 500 | .span3 { 501 | width: 166px; 502 | } 503 | .span2 { 504 | width: 104px; 505 | } 506 | .span1 { 507 | width: 42px; 508 | } 509 | .offset12 { 510 | margin-left: 764px; 511 | } 512 | .offset11 { 513 | margin-left: 702px; 514 | } 515 | .offset10 { 516 | margin-left: 640px; 517 | } 518 | .offset9 { 519 | margin-left: 578px; 520 | } 521 | .offset8 { 522 | margin-left: 516px; 523 | } 524 | .offset7 { 525 | margin-left: 454px; 526 | } 527 | .offset6 { 528 | margin-left: 392px; 529 | } 530 | .offset5 { 531 | margin-left: 330px; 532 | } 533 | .offset4 { 534 | margin-left: 268px; 535 | } 536 | .offset3 { 537 | margin-left: 206px; 538 | } 539 | .offset2 { 540 | margin-left: 144px; 541 | } 542 | .offset1 { 543 | margin-left: 82px; 544 | } 545 | .row-fluid { 546 | width: 100%; 547 | *zoom: 1; 548 | } 549 | .row-fluid:before, 550 | .row-fluid:after { 551 | display: table; 552 | line-height: 0; 553 | content: ""; 554 | } 555 | .row-fluid:after { 556 | clear: both; 557 | } 558 | .row-fluid [class*="span"] { 559 | display: block; 560 | float: left; 561 | width: 100%; 562 | min-height: 30px; 563 | margin-left: 2.7624309392265194%; 564 | *margin-left: 2.709239449864817%; 565 | -webkit-box-sizing: border-box; 566 | -moz-box-sizing: border-box; 567 | box-sizing: border-box; 568 | } 569 | .row-fluid [class*="span"]:first-child { 570 | margin-left: 0; 571 | } 572 | .row-fluid .controls-row [class*="span"] + [class*="span"] { 573 | margin-left: 2.7624309392265194%; 574 | } 575 | .row-fluid .span12 { 576 | width: 100%; 577 | *width: 99.94680851063829%; 578 | } 579 | .row-fluid .span11 { 580 | width: 91.43646408839778%; 581 | *width: 91.38327259903608%; 582 | } 583 | .row-fluid .span10 { 584 | width: 82.87292817679558%; 585 | *width: 82.81973668743387%; 586 | } 587 | .row-fluid .span9 { 588 | width: 74.30939226519337%; 589 | *width: 74.25620077583166%; 590 | } 591 | .row-fluid .span8 { 592 | width: 65.74585635359117%; 593 | *width: 65.69266486422946%; 594 | } 595 | .row-fluid .span7 { 596 | width: 57.18232044198895%; 597 | *width: 57.12912895262725%; 598 | } 599 | .row-fluid .span6 { 600 | width: 48.61878453038674%; 601 | *width: 48.56559304102504%; 602 | } 603 | .row-fluid .span5 { 604 | width: 40.05524861878453%; 605 | *width: 40.00205712942283%; 606 | } 607 | .row-fluid .span4 { 608 | width: 31.491712707182323%; 609 | *width: 31.43852121782062%; 610 | } 611 | .row-fluid .span3 { 612 | width: 22.92817679558011%; 613 | *width: 22.87498530621841%; 614 | } 615 | .row-fluid .span2 { 616 | width: 14.3646408839779%; 617 | *width: 14.311449394616199%; 618 | } 619 | .row-fluid .span1 { 620 | width: 5.801104972375691%; 621 | *width: 5.747913483013988%; 622 | } 623 | .row-fluid .offset12 { 624 | margin-left: 105.52486187845304%; 625 | *margin-left: 105.41847889972962%; 626 | } 627 | .row-fluid .offset12:first-child { 628 | margin-left: 102.76243093922652%; 629 | *margin-left: 102.6560479605031%; 630 | } 631 | .row-fluid .offset11 { 632 | margin-left: 96.96132596685082%; 633 | *margin-left: 96.8549429881274%; 634 | } 635 | .row-fluid .offset11:first-child { 636 | margin-left: 94.1988950276243%; 637 | *margin-left: 94.09251204890089%; 638 | } 639 | .row-fluid .offset10 { 640 | margin-left: 88.39779005524862%; 641 | *margin-left: 88.2914070765252%; 642 | } 643 | .row-fluid .offset10:first-child { 644 | margin-left: 85.6353591160221%; 645 | *margin-left: 85.52897613729868%; 646 | } 647 | .row-fluid .offset9 { 648 | margin-left: 79.8342541436464%; 649 | *margin-left: 79.72787116492299%; 650 | } 651 | .row-fluid .offset9:first-child { 652 | margin-left: 77.07182320441989%; 653 | *margin-left: 76.96544022569647%; 654 | } 655 | .row-fluid .offset8 { 656 | margin-left: 71.2707182320442%; 657 | *margin-left: 71.16433525332079%; 658 | } 659 | .row-fluid .offset8:first-child { 660 | margin-left: 68.50828729281768%; 661 | *margin-left: 68.40190431409427%; 662 | } 663 | .row-fluid .offset7 { 664 | margin-left: 62.70718232044199%; 665 | *margin-left: 62.600799341718584%; 666 | } 667 | .row-fluid .offset7:first-child { 668 | margin-left: 59.94475138121547%; 669 | *margin-left: 59.838368402492065%; 670 | } 671 | .row-fluid .offset6 { 672 | margin-left: 54.14364640883978%; 673 | *margin-left: 54.037263430116376%; 674 | } 675 | .row-fluid .offset6:first-child { 676 | margin-left: 51.38121546961326%; 677 | *margin-left: 51.27483249088986%; 678 | } 679 | .row-fluid .offset5 { 680 | margin-left: 45.58011049723757%; 681 | *margin-left: 45.47372751851417%; 682 | } 683 | .row-fluid .offset5:first-child { 684 | margin-left: 42.81767955801105%; 685 | *margin-left: 42.71129657928765%; 686 | } 687 | .row-fluid .offset4 { 688 | margin-left: 37.01657458563536%; 689 | *margin-left: 36.91019160691196%; 690 | } 691 | .row-fluid .offset4:first-child { 692 | margin-left: 34.25414364640884%; 693 | *margin-left: 34.14776066768544%; 694 | } 695 | .row-fluid .offset3 { 696 | margin-left: 28.45303867403315%; 697 | *margin-left: 28.346655695309746%; 698 | } 699 | .row-fluid .offset3:first-child { 700 | margin-left: 25.69060773480663%; 701 | *margin-left: 25.584224756083227%; 702 | } 703 | .row-fluid .offset2 { 704 | margin-left: 19.88950276243094%; 705 | *margin-left: 19.783119783707537%; 706 | } 707 | .row-fluid .offset2:first-child { 708 | margin-left: 17.12707182320442%; 709 | *margin-left: 17.02068884448102%; 710 | } 711 | .row-fluid .offset1 { 712 | margin-left: 11.32596685082873%; 713 | *margin-left: 11.219583872105325%; 714 | } 715 | .row-fluid .offset1:first-child { 716 | margin-left: 8.56353591160221%; 717 | *margin-left: 8.457152932878806%; 718 | } 719 | input, 720 | textarea, 721 | .uneditable-input { 722 | margin-left: 0; 723 | } 724 | .controls-row [class*="span"] + [class*="span"] { 725 | margin-left: 20px; 726 | } 727 | input.span12, 728 | textarea.span12, 729 | .uneditable-input.span12 { 730 | width: 710px; 731 | } 732 | input.span11, 733 | textarea.span11, 734 | .uneditable-input.span11 { 735 | width: 648px; 736 | } 737 | input.span10, 738 | textarea.span10, 739 | .uneditable-input.span10 { 740 | width: 586px; 741 | } 742 | input.span9, 743 | textarea.span9, 744 | .uneditable-input.span9 { 745 | width: 524px; 746 | } 747 | input.span8, 748 | textarea.span8, 749 | .uneditable-input.span8 { 750 | width: 462px; 751 | } 752 | input.span7, 753 | textarea.span7, 754 | .uneditable-input.span7 { 755 | width: 400px; 756 | } 757 | input.span6, 758 | textarea.span6, 759 | .uneditable-input.span6 { 760 | width: 338px; 761 | } 762 | input.span5, 763 | textarea.span5, 764 | .uneditable-input.span5 { 765 | width: 276px; 766 | } 767 | input.span4, 768 | textarea.span4, 769 | .uneditable-input.span4 { 770 | width: 214px; 771 | } 772 | input.span3, 773 | textarea.span3, 774 | .uneditable-input.span3 { 775 | width: 152px; 776 | } 777 | input.span2, 778 | textarea.span2, 779 | .uneditable-input.span2 { 780 | width: 90px; 781 | } 782 | input.span1, 783 | textarea.span1, 784 | .uneditable-input.span1 { 785 | width: 28px; 786 | } 787 | } 788 | 789 | @media (max-width: 767px) { 790 | body { 791 | padding-right: 20px; 792 | padding-left: 20px; 793 | } 794 | .navbar-fixed-top, 795 | .navbar-fixed-bottom, 796 | .navbar-static-top { 797 | margin-right: -20px; 798 | margin-left: -20px; 799 | } 800 | .container-fluid { 801 | padding: 0; 802 | } 803 | .dl-horizontal dt { 804 | float: none; 805 | width: auto; 806 | clear: none; 807 | text-align: left; 808 | } 809 | .dl-horizontal dd { 810 | margin-left: 0; 811 | } 812 | .container { 813 | width: auto; 814 | } 815 | .row-fluid { 816 | width: 100%; 817 | } 818 | .row, 819 | .thumbnails { 820 | margin-left: 0; 821 | } 822 | .thumbnails > li { 823 | float: none; 824 | margin-left: 0; 825 | } 826 | [class*="span"], 827 | .uneditable-input[class*="span"], 828 | .row-fluid [class*="span"] { 829 | display: block; 830 | float: none; 831 | width: 100%; 832 | margin-left: 0; 833 | -webkit-box-sizing: border-box; 834 | -moz-box-sizing: border-box; 835 | box-sizing: border-box; 836 | } 837 | .span12, 838 | .row-fluid .span12 { 839 | width: 100%; 840 | -webkit-box-sizing: border-box; 841 | -moz-box-sizing: border-box; 842 | box-sizing: border-box; 843 | } 844 | .row-fluid [class*="offset"]:first-child { 845 | margin-left: 0; 846 | } 847 | .input-large, 848 | .input-xlarge, 849 | .input-xxlarge, 850 | input[class*="span"], 851 | select[class*="span"], 852 | textarea[class*="span"], 853 | .uneditable-input { 854 | display: block; 855 | width: 100%; 856 | min-height: 30px; 857 | -webkit-box-sizing: border-box; 858 | -moz-box-sizing: border-box; 859 | box-sizing: border-box; 860 | } 861 | .input-prepend input, 862 | .input-append input, 863 | .input-prepend input[class*="span"], 864 | .input-append input[class*="span"] { 865 | display: inline-block; 866 | width: auto; 867 | } 868 | .controls-row [class*="span"] + [class*="span"] { 869 | margin-left: 0; 870 | } 871 | .modal { 872 | position: fixed; 873 | top: 20px; 874 | right: 20px; 875 | left: 20px; 876 | width: auto; 877 | margin: 0; 878 | } 879 | .modal.fade { 880 | top: -100px; 881 | } 882 | .modal.fade.in { 883 | top: 20px; 884 | } 885 | } 886 | 887 | @media (max-width: 480px) { 888 | .nav-collapse { 889 | -webkit-transform: translate3d(0, 0, 0); 890 | } 891 | .page-header h1 small { 892 | display: block; 893 | line-height: 20px; 894 | } 895 | input[type="checkbox"], 896 | input[type="radio"] { 897 | border: 1px solid #ccc; 898 | } 899 | .form-horizontal .control-label { 900 | float: none; 901 | width: auto; 902 | padding-top: 0; 903 | text-align: left; 904 | } 905 | .form-horizontal .controls { 906 | margin-left: 0; 907 | } 908 | .form-horizontal .control-list { 909 | padding-top: 0; 910 | } 911 | .form-horizontal .form-actions { 912 | padding-right: 10px; 913 | padding-left: 10px; 914 | } 915 | .media .pull-left, 916 | .media .pull-right { 917 | display: block; 918 | float: none; 919 | margin-bottom: 10px; 920 | } 921 | .media-object { 922 | margin-right: 0; 923 | margin-left: 0; 924 | } 925 | .modal { 926 | top: 10px; 927 | right: 10px; 928 | left: 10px; 929 | } 930 | .modal-header .close { 931 | padding: 10px; 932 | margin: -10px; 933 | } 934 | .carousel-caption { 935 | position: static; 936 | } 937 | } 938 | 939 | @media (max-width: 979px) { 940 | body { 941 | padding-top: 0; 942 | } 943 | .navbar-fixed-top, 944 | .navbar-fixed-bottom { 945 | position: static; 946 | } 947 | .navbar-fixed-top { 948 | margin-bottom: 20px; 949 | } 950 | .navbar-fixed-bottom { 951 | margin-top: 20px; 952 | } 953 | .navbar-fixed-top .navbar-inner, 954 | .navbar-fixed-bottom .navbar-inner { 955 | padding: 5px; 956 | } 957 | .navbar .container { 958 | width: auto; 959 | padding: 0; 960 | } 961 | .navbar .brand { 962 | padding-right: 10px; 963 | padding-left: 10px; 964 | margin: 0 0 0 -5px; 965 | } 966 | .nav-collapse { 967 | clear: both; 968 | } 969 | .nav-collapse .nav { 970 | float: none; 971 | margin: 0 0 10px; 972 | } 973 | .nav-collapse .nav > li { 974 | float: none; 975 | } 976 | .nav-collapse .nav > li > a { 977 | margin-bottom: 2px; 978 | } 979 | .nav-collapse .nav > .divider-vertical { 980 | display: none; 981 | } 982 | .nav-collapse .nav .nav-header { 983 | color: #777777; 984 | text-shadow: none; 985 | } 986 | .nav-collapse .nav > li > a, 987 | .nav-collapse .dropdown-menu a { 988 | padding: 9px 15px; 989 | font-weight: bold; 990 | color: #777777; 991 | -webkit-border-radius: 3px; 992 | -moz-border-radius: 3px; 993 | border-radius: 3px; 994 | } 995 | .nav-collapse .btn { 996 | padding: 4px 10px 4px; 997 | font-weight: normal; 998 | -webkit-border-radius: 4px; 999 | -moz-border-radius: 4px; 1000 | border-radius: 4px; 1001 | } 1002 | .nav-collapse .dropdown-menu li + li a { 1003 | margin-bottom: 2px; 1004 | } 1005 | .nav-collapse .nav > li > a:hover, 1006 | .nav-collapse .dropdown-menu a:hover { 1007 | background-color: #f2f2f2; 1008 | } 1009 | .navbar-inverse .nav-collapse .nav > li > a, 1010 | .navbar-inverse .nav-collapse .dropdown-menu a { 1011 | color: #999999; 1012 | } 1013 | .navbar-inverse .nav-collapse .nav > li > a:hover, 1014 | .navbar-inverse .nav-collapse .dropdown-menu a:hover { 1015 | background-color: #111111; 1016 | } 1017 | .nav-collapse.in .btn-group { 1018 | padding: 0; 1019 | margin-top: 5px; 1020 | } 1021 | .nav-collapse .dropdown-menu { 1022 | position: static; 1023 | top: auto; 1024 | left: auto; 1025 | display: none; 1026 | float: none; 1027 | max-width: none; 1028 | padding: 0; 1029 | margin: 0 15px; 1030 | background-color: transparent; 1031 | border: none; 1032 | -webkit-border-radius: 0; 1033 | -moz-border-radius: 0; 1034 | border-radius: 0; 1035 | -webkit-box-shadow: none; 1036 | -moz-box-shadow: none; 1037 | box-shadow: none; 1038 | } 1039 | .nav-collapse .open > .dropdown-menu { 1040 | display: block; 1041 | } 1042 | .nav-collapse .dropdown-menu:before, 1043 | .nav-collapse .dropdown-menu:after { 1044 | display: none; 1045 | } 1046 | .nav-collapse .dropdown-menu .divider { 1047 | display: none; 1048 | } 1049 | .nav-collapse .nav > li > .dropdown-menu:before, 1050 | .nav-collapse .nav > li > .dropdown-menu:after { 1051 | display: none; 1052 | } 1053 | .nav-collapse .navbar-form, 1054 | .nav-collapse .navbar-search { 1055 | float: none; 1056 | padding: 10px 15px; 1057 | margin: 10px 0; 1058 | border-top: 1px solid #f2f2f2; 1059 | border-bottom: 1px solid #f2f2f2; 1060 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1061 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1062 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1063 | } 1064 | .navbar-inverse .nav-collapse .navbar-form, 1065 | .navbar-inverse .nav-collapse .navbar-search { 1066 | border-top-color: #111111; 1067 | border-bottom-color: #111111; 1068 | } 1069 | .navbar .nav-collapse .nav.pull-right { 1070 | float: none; 1071 | margin-left: 0; 1072 | } 1073 | .nav-collapse, 1074 | .nav-collapse.collapse { 1075 | height: 0; 1076 | overflow: hidden; 1077 | } 1078 | .navbar .btn-navbar { 1079 | display: block; 1080 | } 1081 | .navbar-static .navbar-inner { 1082 | padding-right: 10px; 1083 | padding-left: 10px; 1084 | } 1085 | } 1086 | 1087 | @media (min-width: 980px) { 1088 | .nav-collapse.collapse { 1089 | height: auto !important; 1090 | overflow: visible !important; 1091 | } 1092 | } 1093 | --------------------------------------------------------------------------------