Very small optimizations and first upload

This commit is contained in:
nipos 2018-02-26 17:56:23 +01:00
parent fcf58d31a0
commit 6afd23142e
89 changed files with 16607 additions and 2 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
config.ini
.ftpconfig

View File

@ -1,2 +1,62 @@
# halcyon # Halcyon for Mastodon
A Mastodon web client that looks like Twitter - Demo at https://halcyon.cybre.space A Mastodon web client that looks like Twitter
>The original author of this genious piece of software was inactive for a while and then shut down his demo instance and deleted this repository. I love Halcyon, it's the thing which makes Mastodon the best social network in the world. I took the Code from the Halcyon fork of cybre.space which still works but doesn't seem to get updates, too. I uploaded it here to make the original link work again and don't link into the big nothing. I'm working on much other stuff, too, what is why I won't be very active here in the near future but I do things which are required to keep it working and if I have much more time, I already know some nice features which could come. As for now I don't have an own demo instance but the one from cybre.space works pretty good and when I changed many things, I will open an own one.
<img src="https://halcyon.cybre.space/login/assets/images/preview0.png">
## Demo
https://halcyon.cybre.space/
Please note that this demo is not owned by me and I have not control over it. Currently I'm not providing an own demo so I link to that one. That may change in future.
## Features
- Twitter like UI, familiar interface.
- Able to use on all instances.
- No tracking, No ads.
## Requirement
- Apache
- PHP
- MySQL
## Setup
I didn't prepared setup script so you have to setup manually...
### PDO MySQL
After installed PHP, run this.
```bash
sudo pecl install pdo_mysql
sudo vi php.ini
```
change to this
```php.ini
extension=mysqli.so
extension=pdo_mysql.so
```
### MySQL
After installed MySQL, create a user, run this.
```sql
CREATE DATABASE DATABASE_NAME DEFAULT CHARACTER SET utf8;
CREATE TABLE DATABASE_NAME.instances(domain varchar(261), client_id varchar(64), client_secret varchar(64));
```
and make file `/config.ini` like this
```config.ini
~~~ line 3 ~~~
api_client_name = <APP NAME>
api_client_website = <APP WEBSITE LINK>
~~~ line 8 ~~~
db_host = <DATABASE HOST DOMAIN>
db_user = <DATABASE USERNAME>
db_pass = <DATABASE PASSWORD>
db_name = <DATABASE NAME>
```
## Credits
- [Kirschn/mastodon.js](https://github.com/Kirschn/mastodon.js)
- [yks118/Mastodon-api-php](https://github.com/yks118/Mastodon-api-php)

17
Roadmap.md Normal file
View File

@ -0,0 +1,17 @@
# Roadmap
These plans may change if I think something else is more important or if I consider something here as not that important!
## Next version
- [x] Oauth login
- [x] Support responsive
- [ ] Add meida attachment's next/prev button
## Future
- [ ] Multi account
- [ ] Emoji picker
- [ ] Native profile edit
- [ ] Dark theme
- [ ] Translation support
- [ ] Mobile devices support
- [ ] PHP PSR4 / Autoload

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 KwangSeon Yun
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,970 @@
<?php
// defined('BASEPATH') OR exit('No direct script access allowed');
/**
* Class Mastodon_api
*
* PHP version 7.1
*
* Mastodon https://mastodon.social/
* API LIST https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md
*
* @author KwangSeon Yun <middleyks@hanmail.net>
* @copyright KwangSeon Yun
* @license https://raw.githubusercontent.com/yks118/Mastodon-api-php/master/LICENSE MIT License
* @link https://github.com/yks118/Mastodon-api-php
*/
class Mastodon_api {
private $mastodon_url = '';
private $client_id = '';
private $client_secret = '';
private $token = array();
private $scopes = array();
public function __construct () {}
public function __destruct () {}
/**
* _post
*
* curl post
*
* @param string $url
* @param array $data
*
* @return array $response
*/
private function _post ($url,$data = array()) {
$parameters = array();
$parameters[CURLOPT_POST] = 1;
// set access_token
if (isset($this->token['access_token'])) {
$data['access_token'] = $this->token['access_token'];
}
if (count($data)) {
$parameters[CURLOPT_POSTFIELDS] = http_build_query($data);
}
$url = $this->mastodon_url.$url;
$response = $this->get_content_curl($url,$parameters);
return $response;
}
/**
* _get
*
* @param string $url
* @param array $data
*
* @return array $response
*/
private function _get ($url,$data = array()) {
$parameters = array();
// set authorization bearer
if (isset($this->token['access_token'])) {
$authorization = 'Authorization: '.$this->token['token_type'].' '.$this->token['access_token'];
$parameters[CURLOPT_HTTPHEADER] = array('Content-Type: application/json',$authorization);
}
$url = $this->mastodon_url.$url;
if (count($data)) {
$url .= '?'.http_build_query($data);
}
$response = $this->get_content_curl($url,$parameters);
return $response;
}
/**
* _patch
*
* @param string $url
* @param array $data
*
* @return array $parameters
*/
private function _patch ($url,$data = array()) {
$parameters = array();
$parameters[CURLOPT_CUSTOMREQUEST] = 'PATCH';
// set authorization bearer
if (isset($this->token['access_token'])) {
$authorization = 'Authorization: '.$this->token['token_type'].' '.$this->token['access_token'];
$parameters[CURLOPT_HTTPHEADER] = array('Content-Type: application/json',$authorization);
}
if (count($data)) {
$parameters[CURLOPT_POSTFIELDS] = json_encode($data);
}
$url = $this->mastodon_url.$url;
$response = $this->get_content_curl($url,$parameters);
return $response;
}
/**
* _delete
*
* @param string $url
*
* @return array $response
*/
private function _delete ($url) {
$parameters = array();
$parameters[CURLOPT_CUSTOMREQUEST] = 'DELETE';
// set authorization bearer
if (isset($this->token['access_token'])) {
$authorization = 'Authorization: '.$this->token['token_type'].' '.$this->token['access_token'];
$parameters[CURLOPT_HTTPHEADER] = array('Content-Type: application/json',$authorization);
}
$url = $this->mastodon_url.$url;
$response = $this->get_content_curl($url,$parameters);
return $response;
}
/**
* get_content_curl
*
* @param string $url
* @param array $parameters
*
* @return array $data
*/
protected function get_content_curl ($url,$parameters = array()) {
$data = array();
// set CURLOPT_USERAGENT
if (!isset($parameters[CURLOPT_USERAGENT])) {
if (isset($_SERVER['HTTP_USER_AGENT'])) {
$parameters[CURLOPT_USERAGENT] = $_SERVER['HTTP_USER_AGENT'];
} else {
// default IE11
$parameters[CURLOPT_USERAGENT] = 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko';
}
}
// check curl_init
if (function_exists('curl_init')) {
$ch = curl_init();
// url 설정
curl_setopt($ch,CURLOPT_URL,$url);
foreach ($parameters as $key => $value) {
curl_setopt($ch,$key,$value);
}
// https
if (!isset($parameters[CURLOPT_SSL_VERIFYPEER])) {
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
}
if (!isset($parameters[CURLOPT_SSLVERSION])) {
curl_setopt($ch,CURLOPT_SSLVERSION,6);
}
// no header
if (!isset($parameters[CURLOPT_HEADER])) {
curl_setopt($ch,CURLOPT_HEADER,0);
}
// POST / GET (default : GET)
if (!isset($parameters[CURLOPT_POST]) && !isset($parameters[CURLOPT_CUSTOMREQUEST])) {
curl_setopt($ch,CURLOPT_POST,0);
}
// response get php value
if (!isset($parameters[CURLOPT_RETURNTRANSFER])) {
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
}
// HTTP2
if (!isset($parameters[CURLOPT_HTTP_VERSION])) {
curl_setopt($ch,CURLOPT_HTTP_VERSION,3);
}
if (!isset($parameters[CURLINFO_HEADER_OUT])) {
curl_setopt($ch,CURLINFO_HEADER_OUT,TRUE);
}
$data['html'] = json_decode(curl_exec($ch),true);
$data['response'] = curl_getinfo($ch);
curl_close($ch);
}
return $data;
}
/**
* set_url
*
* @param string $path
*/
public function set_url ($path) {
$this->mastodon_url = $path;
}
/**
* set_client
*
* @param string $id
* @param string $secret
*/
public function set_client ($id,$secret) {
$this->client_id = $id;
$this->client_secret = $secret;
}
/**
* set_token
*
* @param string $token
* @param string $type
*/
public function set_token ($token,$type) {
$this->token['access_token'] = $token;
$this->token['token_type'] = $type;
}
/**
* set_scopes
*
* @param array $scopes read / write / follow
*/
public function set_scopes ($scopes) {
$this->scopes = $scopes;
}
/**
* create_app
*
* @param string $client_name
* @param array $scopes read / write / follow
* @param string $redirect_uris
* @param string $website
*
* @return array $response
* int $response['id']
* string $response['redirect_uri']
* string $response['client_id']
* string $response['client_secret']
*/
public function create_app ($client_name,$scopes = array(),$redirect_uris = '',$website = '') {
$parameters = array();
if (count($scopes) == 0) {
if (count($this->scopes) == 0) {
$scopes = array('read','write','follow');
} else {
$scopes = $this->scopes;
}
}
$parameters['client_name'] = $client_name;
$parameters['scopes'] = implode(' ',$scopes);
if (empty($redirect_uris)) {
$parameters['redirect_uris'] = 'urn:ietf:wg:oauth:2.0:oob';
} else {
$parameters['redirect_uris'] = $redirect_uris;
}
if ($website) {
$parameters['website'] = $website;
}
$response = $this->_post('/api/v1/apps',$parameters);
if (isset($response['html']['client_id'])) {
$this->client_id = $response['html']['client_id'];
$this->client_secret = $response['html']['client_secret'];
}
return $response;
}
/**
* login
*
* @param string $id E-mail Address
* @param string $password Password
*
* @return array $response
* string $response['access_token']
* string $response['token_type'] bearer
* string $response['scope'] read
* int $response['created_at'] time
*/
public function login ($id,$password) {
$parameters = array();
$parameters['client_id'] = $this->client_id;
$parameters['client_secret'] = $this->client_secret;
$parameters['grant_type'] = 'password';
$parameters['username'] = $id;
$parameters['password'] = $password;
if (count($this->scopes) == 0) {
$parameters['scope'] = implode(' ',array('read','write','follow'));
} else {
$parameters['scope'] = implode(' ',$this->scopes);
}
$response = $this->_post('/oauth/token',$parameters);
if (isset($response['html']["access_token"])) {
$this->token['access_token'] = $response['html']['access_token'];
$this->token['token_type'] = $response['html']['token_type'];
}
return $response;
}
public function get_access_token ($redirect_uri,$code) {
$parameters = array();
$parameters['grant_type'] = 'authorization_code';
$parameters['redirect_uri'] = $redirect_uri;
$parameters['client_id'] = $this->client_id;
$parameters['client_secret'] = $this->client_secret;
$parameters['code'] = $code;
$response = $this->_post('/oauth/token',$parameters);
if (isset($response['html']["access_token"])) {
$this->token['access_token'] = $response['html']['access_token'];
$this->token['token_type'] = $response['html']['token_type'];
}
return $response;
}
/**
* accounts
*
* @see https://your-domain/web/accounts/:id
*
* @param int $id
*
* @return array $response
* int $response['id']
* string $response['username']
* string $response['acct']
* string $response['display_name'] The name to display in the user's profile
* bool $response['locked']
* string $response['created_at']
* int $response['followers_count']
* int $response['following_count']
* int $response['statuses_count']
* string $response['note'] A new biography for the user
* string $response['url']
* string $response['avatar'] A base64 encoded image to display as the user's avatar
* string $response['avatar_static']
* string $response['header'] A base64 encoded image to display as the user's header image
* string $response['header_static']
*/
public function accounts ($id) {
$response = $this->_get('/api/v1/accounts/'.$id);
return $response;
}
/**
* accounts_verify_credentials
*
* Getting the current user
*
* @return array $response
*/
public function accounts_verify_credentials () {
$response = $this->_get('/api/v1/accounts/verify_credentials');
return $response;
}
/**
* accounts_update_credentials
*
* Updating the current user
*
* @param array $parameters
* string $parameters['display_name'] The name to display in the user's profile
* string $parameters['note'] A new biography for the user
* string $parameters['avatar'] A base64 encoded image to display as the user's avatar
* string $parameters['header'] A base64 encoded image to display as the user's header image
*
* @return array $response
*/
public function accounts_update_credentials ($parameters) {
$response = $this->_patch('/api/v1/accounts/update_credentials',$parameters);
return $response;
}
/**
* accounts_followers
*
* @see https://your-domain/web/accounts/:id
*
* @param int $id
*
* @return array $response
*/
public function accounts_followers ($id) {
$response = $this->_get('/api/v1/accounts/'.$id.'/followers');
return $response;
}
/**
* accounts_following
*
* @see https://your-domain/web/accounts/:id
*
* @param int $id
*
* @return array $response
*/
public function accounts_following ($id) {
$response = $this->_get('/api/v1/accounts/'.$id.'/following');
return $response;
}
/**
* accounts_statuses
*
* @see https://your-domain/web/accounts/:id
*
* @param int $id
*
* @return array $response
*/
public function accounts_statuses ($id) {
$response = $this->_get('/api/v1/accounts/'.$id.'/statuses');
return $response;
}
/**
* accounts_follow
*
* @see https://your-domain/web/accounts/:id
*
* @param int $id
*
* @return array $response
*/
public function accounts_follow ($id) {
$response = $this->_post('/api/v1/accounts/'.$id.'/follow');
return $response;
}
/**
* accounts_unfollow
*
* @see https://your-domain/web/accounts/:id
*
* @param int $id
*
* @return array $response
*/
public function accounts_unfollow ($id) {
$response = $this->_post('/api/v1/accounts/'.$id.'/unfollow');
return $response;
}
/**
* accounts_block
*
* @see https://your-domain/web/accounts/:id
*
* @param int $id
*
* @return array $response
*/
public function accounts_block ($id) {
$response = $this->_post('/api/v1/accounts/'.$id.'/block');
return $response;
}
/**
* accounts_unblock
*
* @see https://your-domain/web/accounts/:id
*
* @param int $id
*
* @return array $response
*/
public function accounts_unblock ($id) {
$response = $this->_post('/api/v1/accounts/'.$id.'/unblock');
return $response;
}
/**
* accounts_mute
*
* @see https://your-domain/web/accounts/:id
*
* @param int $id
*
* @return array $response
*/
public function accounts_mute ($id) {
$response = $this->_post('/api/v1/accounts/'.$id.'/mute');
return $response;
}
/**
* accounts_unmute
*
* @see https://your-domain/web/accounts/:id
*
* @param int $id
*
* @return array $response
*/
public function accounts_unmute ($id) {
$response = $this->_post('/api/v1/accounts/'.$id.'/unmute');
return $response;
}
/**
* accounts_relationships
*
* @see https://your-domain/web/accounts/:id
*
* @param array $parameters
* int $parameters['id']
*
* @return array $response
* int $response['id']
* bool $response['following']
* bool $response['followed_by']
* bool $response['blocking']
* bool $response['muting']
* bool $response['requested']
*/
public function accounts_relationships ($parameters) {
$response = $this->_get('/api/v1/accounts/relationships',$parameters);
return $response;
}
/**
* accounts_search
*
* @param array $parameters
* string $parameters['q']
* int $parameters['limit'] default : 40
*
* @return array $response
*/
public function accounts_search ($parameters) {
$response = $this->_get('/api/v1/accounts/search',$parameters);
return $response;
}
/**
* blocks
*
* @return array $response
*/
public function blocks () {
$response = $this->_get('/api/v1/blocks');
return $response;
}
/**
* favourites
*
* @return array $response
*/
public function favourites () {
$response = $this->_get('/api/v1/favourites');
return $response;
}
/**
* follow_requests
*
* @return array $response
*/
public function follow_requests () {
$response = $this->_get('/api/v1/follow_requests');
return $response;
}
/**
* follow_requests_authorize
*
* @see https://your-domain/web/accounts/:id
*
* @param int $id
*
* @return array $response
*/
public function follow_requests_authorize ($id) {
$response = $this->_post('/api/v1/follow_requests/authorize',array('id'=>$id));
return $response;
}
/**
* follow_requests_reject
*
* @see https://your-domain/web/accounts/:id
*
* @param int $id
* @return array $response
*/
public function follow_requests_reject ($id) {
$response = $this->_post('/api/v1/follow_requests/reject',array('id'=>$id));
return $response;
}
/**
* follows
*
* Following a remote user
*
* @param string $uri username@domain of the person you want to follow
* @return array $response
*/
public function follows ($uri) {
$response = $this->_post('/api/v1/follows',array('uri'=>$uri));
return $response;
}
/**
* instance
*
* Getting instance information
*
* @return array $response
* string $response['uri']
* string $response['title']
* string $response['description']
* string $response['email']
*/
public function instance () {
$response = $this->_get('/api/v1/instance');
return $response;
}
/**
* media
*
* Uploading a media attachment
*
* @param string $file_path local path / http path
*
* @return array $response
* int $response['id'] ID of the attachment
* string $response['type'] One of: "image", "video", "gifv"
* string $response['url'] URL of the locally hosted version of the image
* string $response['remote_url'] For remote images, the remote URL of the original image
* string $response['preview_url'] URL of the preview image
* string $response['text_url'] Shorter URL for the image, for insertion into text (only present on local images)
*/
public function media ($file_path) {
$url = $this->mastodon_url.'/api/v1/media';
$parameters = $data = array();
$parameters[CURLOPT_HTTPHEADER] = array('Content-Type'=>'multipart/form-data');
$parameters[CURLOPT_POST] = true;
// set access_token
if (isset($this->token['access_token'])) {
$parameters[CURLOPT_POSTFIELDS]['access_token'] = $this->token['access_token'];
}
if (is_file($file_path)) {
$mime_type = mime_content_type($file_path);
$cf = curl_file_create($file_path,$mime_type,'file');
$parameters[CURLOPT_POSTFIELDS]['file'] = $cf;
}
$response = $this->get_content_curl($url,$parameters);
return $response;
}
/**
* mutes
*
* Fetching a user's mutes
*
* @return array $response
*/
public function mutes () {
$response = $this->_get('/api/v1/mutes');
return $response;
}
/**
* notifications
*
* @param int $id
*
* @return array $response
*/
public function notifications ($id = 0) {
$url = '/api/v1/notifications';
if ($id > 0) {
$url .= '/'.$id;
}
$response = $this->_get($url);
return $response;
}
/**
* notifications_clear
*
* Clearing notifications
*
* @return array $response
*/
public function notifications_clear () {
$response = $this->_post('/api/v1/notifications/clear');
return $response;
}
/**
* get_reports
*
* Fetching a user's reports
*
* @return array $response
*/
public function get_reports () {
$response = $this->_get('/api/v1/reports');
return $response;
}
/**
* post_reports
*
* Reporting a user
*
* @param array $parameters
* int $parameters['account_id'] The ID of the account to report
* int $parameters['status_ids'] The IDs of statuses to report (can be an array)
* string $parameters['comment'] A comment to associate with the report.
*
* @return array $response
*/
public function post_reports ($parameters) {
$response = $this->_post('/api/v1/reports',$parameters);
return $response;
}
/**
* search
*
* Searching for content
*
* @param array $parameters
* string $parameters['q'] The search query
* string $parameters['resolve'] Whether to resolve non-local accounts
*
* @return array $response
*/
public function search ($parameters) {
$response = $this->_get('/api/v1/search',$parameters);
return $response;
}
/**
* statuses
*
* Fetching a status
*
* @param int $id
*
* @return array $response
*/
public function statuses ($id) {
$response = $this->_get('/api/v1/statuses/'.$id);
return $response;
}
/**
* statuses_context
*
* Getting status context
*
* @param int $id
*
* @return array $response
*/
public function statuses_context ($id) {
$response = $this->_get('/api/v1/statuses/'.$id.'/context');
return $response;
}
/**
* statuses_card
*
* Getting a card associated with a status
*
* @param int $id
*
* @return array $response
*/
public function statuses_card ($id) {
$response = $this->_get('/api/v1/statuses/'.$id.'/card');
return $response;
}
/**
* statuses_reblogged_by
*
* Getting who reblogged a status
*
* @param int $id
*
* @return array $response
*/
public function statuses_reblogged_by ($id) {
$response = $this->_get('/api/v1/statuses/'.$id.'/reblogged_by');
return $response;
}
/**
* statuses_favourited_by
*
* Getting who favourited a status
*
* @param int $id
*
* @return array $response
*/
public function statuses_favourited_by ($id) {
$response = $this->_get('/api/v1/statuses/'.$id.'/favourited_by');
return $response;
}
/**
* post_statuses
*
* @param array $parameters
* string $parameters['status'] The text of the status
* int $parameters['in_reply_to_id'] (optional): local ID of the status you want to reply to
* int $parameters['media_ids'] (optional): array of media IDs to attach to the status (maximum 4)
* string $parameters['sensitive'] (optional): set this to mark the media of the status as NSFW
* string $parameters['spoiler_text'] (optional): text to be shown as a warning before the actual content
* string $parameters['visibility'] (optional): either "direct", "private", "unlisted" or "public"
*
* @return array $response
*/
public function post_statuses ($parameters) {
$response = $this->_post('/api/v1/statuses',$parameters);
return $response;
}
/**
* delete_statuses
*
* Deleting a status
*
* @param int $id
*
* @return array $response empty
*/
public function delete_statuses ($id) {
$response = $this->_delete('/api/v1/statuses/'.$id);
return $response;
}
/**
* statuses_reblog
*
* Reblogging a status
*
* @param int $id
*
* @return array $response
*/
public function statuses_reblog ($id) {
$response = $this->_post('/api/v1/statuses/'.$id.'/reblog');
return $response;
}
/**
* statuses_unreblog
*
* Unreblogging a status
*
* @param int $id
*
* @return array $response
*/
public function statuses_unreblog ($id) {
$response = $this->_post('/api/v1/statuses/'.$id.'/unreblog');
return $response;
}
/**
* statuses_favourite
*
* Favouriting a status
*
* @param int $id
*
* @return array $response
*/
public function statuses_favourite ($id) {
$response = $this->_post('/api/v1/statuses/'.$id.'/favourite');
return $response;
}
/**
* statuses_unfavourite
*
* Unfavouriting a status
*
* @param int $id
*
* @return array $response
*/
public function statuses_unfavourite ($id) {
$response = $this->_post('/api/v1/statuses/'.$id.'/unfavourite');
return $response;
}
/**
* timelines_home
*
* @return array $response
*/
public function timelines_home () {
$response = $this->_get('/api/v1/timelines/home');
return $response;
}
/**
* timelines_public
*
* @param array $parameters
* bool $parameters['local'] Only return statuses originating from this instance
*
* @return array $response
*/
public function timelines_public ($parameters = array()) {
$response = $this->_get('/api/v1/timelines/public',$parameters);
return $response;
}
/**
* timelines_tag
*
* @param string $hashtag
* @param array $parameters
* bool $parameters['local'] Only return statuses originating from this instance
*
* @return array $response
*/
public function timelines_tag ($hashtag,$parameters = array()) {
$response = $this->_get('/api/v1/timelines/tag/'.$hashtag,$parameters);
return $response;
}
}

View File

@ -0,0 +1,19 @@
# Mastodon-api-php
A GNU Social-compatible microblogging server https://mastodon.social PHP API
## How to use
require_once '/path/Mastodon_api.php';<br />
<br />
$mastodon_api = new Mastodon_api();<br />
$mastodon_api->set_url('Mastodon url');<br />
<br />
// print_r($mastodon_api->create_app('APP Name',null,null,'Mastodon url'));<br />
$mastodon_api->set_client('client_id','client_secret');<br />
<br />
// print_r($mastodon_api->login('your login email','your login password'));<br />
$mastodon_api->set_token('access_token','token_type');<br />
<br />
$mastodon_api->timelines_home();
## Test Mastodon
https://ery.kr

82
authorize/Mastodon.php Executable file
View File

@ -0,0 +1,82 @@
<?php
#!/usr/bin/env php
namespace HalcyonSuite\HalcyonForMastodon;
require_once('database.php');
require_once('Mastodon-api-php/Mastodon_api.php');
use HalcyonSuite\HalcyonForMastodon\Database;
use PDO;
use Exception;
/*-------------------
class for halcyon
--------------------*/
class Mastodon extends \Mastodon_api
{
function __construct(){
$appSettings = parse_ini_file('../../config.ini', true);
$this->clientName = $appSettings["App"]["api_client_name"];
$this->clientRedirectUris = $appSettings["App"]["api_client_website"].'/auth urn:ietf:wg:oauth:2.0:oob';
$this->clientWebsite = $appSettings["App"]["api_client_website"];
$this->clientScopes = array('read', 'write', 'follow');
$this->instances = array();
$this->dbHost = $appSettings["Mysql"]["db_host"];
$this->dbUser = $appSettings["Mysql"]["db_user"];
$this->dbPass = $appSettings["Mysql"]["db_pass"];
$this->dbName = $appSettings["Mysql"]["db_name"];
$this->database = new Database($this->dbHost, $this->dbUser, $this->dbPass, $this->dbName);
$this->readInstances();
}
/* note: $domainって書いてあるけど、ドメインじゃなくてURLです。すみません */
private function newInstance($domain)
{
$res = $this->create_app($this->clientName, $this->clientScopes, $this->clientRedirectUris, $this->clientWebsite);
if (isset($res['html']['client_id'])) {
$this->instances[$domain] = $res['html'];
$this->database->dbExecute("insert into instances(domain, client_id, client_secret) values(?,?,?)", array($domain, $res['html']['client_id'], $res['html']['client_secret']));
// insert into instances(domain, client_id, client_secret) values($domain, $client_id, $client_secret)
}else{
throw new Exception("Invalid instance");
}
}
public function selectInstance($domain)
{
$this->set_url($domain);
if (!$this->instanceExists($domain)) {
$this->newInstance($domain);
}
$this->set_client($this->instances[$domain]['client_id'], $this->instances[$domain]['client_secret']);
}
public function getInstance($domain)
{
$this->set_url($domain);
if (!$this->instanceExists($domain)) {
$this->newInstance($domain);
}
return array('client_id' => $this->instances[$domain]['client_id'], 'client_secret' => $this->instances[$domain]['client_secret']);
}
public function instanceExists($domain)
{
return isset($this->instances[$domain]);
}
private function readInstances()
{
$stmt = $this->database->dbExecute("select domain,client_id,client_secret from instances");
foreach($stmt->fetchAll(PDO::FETCH_ASSOC) as $row){
$this->instances[$row['domain']] = $row;
}
}
}
?>

50
authorize/database.php Executable file
View File

@ -0,0 +1,50 @@
<?php
#!/usr/bin/env php
namespace HalcyonSuite\HalcyonForMastodon;
use PDO;
use Exception;
class Database{
public function __construct($dbhost, $dbuser, $dbpass, $dbname){
$this->dbhost = $dbhost;
$this->dbuser = $dbuser;
$this->dbpass = $dbpass;
$this->dbname = $dbname;
$this->dsn = "mysql:dbname=".$this->dbname.";host=".$this->dbhost.";charset=utf8";
$this->connecting = false;
$this->dbConnect();
}
public function dbConnect($commit=True){
try{
$dbh = new PDO($this->dsn, $this->dbuser, $this->dbpass);
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
if(!$commit){
$dbh->beginTransaction();
}
}catch (PDOException $e){
throw new Exception($e);
}
$this->dbh = $dbh;
$this->connecting = true;
return $dbh;
}
public function dbClose(){
$this->dbh = Null;
$this->connecting = false;
}
public function dbExecute($sql, $attr = null){
if ($attr === null) {
$attr = array();
}
if (!$this->connecting) {
$this->dbConnect();
}
$stmt = $this->dbh->prepare($sql);
$stmt->execute($attr);
return $stmt;
}
}
?>

11
config.ini.sample Normal file
View File

@ -0,0 +1,11 @@
; Registar App Settings
[App]
api_client_name = APPLICATION'S NAME
api_client_website = HTTPS://YOURDOMAIN.COM/
; MySQL Settings
[Mysql]
db_host = YOUR DATABASE'S HOST
db_user = YOUR DATABASE'S USER
db_pass = YOUR DATABASE'S PASSWORD
db_name = YOUR DATABASE'S NAME

56
public_html/.htaccess Normal file
View File

@ -0,0 +1,56 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.(.+?)$
RewriteRule ^(.*)$ https://$1 [R=301,L]
RewriteCond %{SERVER_PORT} 80
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# Main
RewriteBase /
RewriteRule ^home/?$ / [R=301]
RewriteBase /login
RewriteRule ^login/?$ login/login\.php [NC,L,QSA]
RewriteRule ^auth/?$ login/auth\.php [NC,L,QSA]
RewriteRule ^logout/?$ login/logout\.php [NC,L,QSA]
RewriteRule ^terms/?$ login/terms\.php [NC,L,QSA]
# LTL
RewriteBase /local
RewriteRule ^local/?$ local\.php [NC,L]
# FTL
RewriteBase /federated
RewriteRule ^federated/?$ federated\.php [NC,L]
# Notice
RewriteBase /notifications
RewriteRule ^notifications/?$ notifications\.php [NC,L]
# Search
RewriteBase /search
RewriteRule ^search/?$ search_hash_tag\.php [NC,L,QSA]
RewriteRule ^search/users/?$ search_user\.php [NC,L,QSA]
# User
RewriteBase /
RewriteRule ^@(.+)@(.+)\.([a-z]+)/?$ user\.php?user=@$1@$2\.$3 [NC,L,QSA]
RewriteRule ^@(.+)@(.+)\.([a-z]+)/status/(.+?)?$ user\.php?user=@$1@$2\.$3&status=$4 [NC,L,QSA]
RewriteRule ^@(.+)@(.+)\.([a-z]+)/media/?$ user_only_media\.php?user=@$1@$2\.$3 [NC,L,QSA]
RewriteRule ^@(.+)@(.+)\.([a-z]+)/with_replies/?$ user_include_replies\.php?user=@$1@$2\.$3 [NC,L,QSA]
RewriteRule ^@(.+)@(.+)\.([a-z]+)/followers/?$ user_followers\.php?user=@$1@$2\.$3 [NC,L,QSA]
RewriteRule ^@(.+)@(.+)\.([a-z]+)/following/?$ user_following\.php?user=@$1@$2\.$3 [NC,L,QSA]
RewriteRule ^@(.+)@(.+)\.([a-z]+)/favourites/?$ user_favorite\.php?user=@$1@$2\.$3 [NC,L,QSA]
# Image
RewriteBase /
RewriteRule ^avatars/original/missing\.png$ assets/images/missing\.png [NC,L]
RewriteRule ^headers/original/missing\.png$ assets/images/missing_header\.png [NC,L]
# 404
RewriteRule ^404/?$ 404\.php [NC,L,QSA]
ErrorDocument 404 /404
</IfModule>

25
public_html/404.php Normal file
View File

@ -0,0 +1,25 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Halcyon / ?</title>
<link rel="shortcut icon" href="/assets/images/favicon.ico" />
<link rel="stylesheet" type="text/css" href="/assets/css/404.css" media="all" />
</head>
<body>
<article>
<h1>404</h1>
<h2>Sorry, something went wrong.</h2>
</article>
</body>
</html>

View File

@ -0,0 +1,169 @@
@charset "utf-8";
/*--------------------------------------
Reset
--------------------------------------*/
* {
margin: 0;
padding: 0;
font-size: 100%;
}
a {
text-decoration: none;
word-break: break-all;
color: inherit;
}
a:hover {
text-decoration: underline;
}
ul, ol {
list-style: none;
padding: 0;
margin: 0;
}
img {
vertical-align: top;
border: 0;
max-width: 100%;
max-height: 100%;
}
button {
font-size: 100%;
}
.clear {
clear: both;
}
.red {
color: red!important;
}
.invisible {
display: none!important;
}
.no-events {
pointer-events: none;
}
.disallow_select {
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
}
.text_ellipsis {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
h1, h2, h3, h4, h5, h6 {
font-weight: normal;
line-height: 1;
margin: 0;
padding: 0;
}
blockquote, q {
quotes: none;
margin: 0;
}
blockquote * {
margin: 0;
word-break: break-all;
}
blockquote:before, blockquote:after,
q:before, q:after {
content:'';
content:none;
}
article,aside,details,figcaption,figure,
footer,header,hgroup,menu,nav,section {
display:block;
}
textarea {
width: 100%;
}
input {
max-width: 100%;
}
button, input[type="submit"], input[type="button"]{
background-color: transparent;
border: none;
cursor: pointer;
outline: none;
padding: 0;
appearance: none;
}
/* Twitter Emoji Prefix */
img.emoji {
height: 1em;
width: 1em;
margin: 0 .05em 0 .1em;
vertical-align: -0.1em;
}
.emoji_poss .auth_emoji {
display: inline-block;
height: 100%;
margin: auto;
margin-left: 4px;
}
/*--------------------------------------
Base
--------------------------------------*/
html {
font-family : "Helvetica Neue",Helvetica,"ヒラギノ角ゴ Pro W3",
"Hiragino Kaku Gothic Pro",Meiryo,"メイリオ"," Pゴシック",Arial,sans-serif;
font-size : 100%;
line-height : 1;
color: #333;
min-width: 100%;
min-height: 100%;
}
body {
margin: 0;
padding: 0;
min-width: 100%;
min-height: 100%;
word-wrap:break-word;
background-color: #189EFC;
}
article {
margin: auto;
padding: 24px;
}
h1 {
font-size: 248px;
text-align: center;
color: #fff;
font-weight: 600;
}
h2 {
font-size: 56px;
text-align: center;
color: #fff;
font-weight: 200;
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,258 @@
/*-----------------------------------
get Link from XHR Header
-----------------------------------*/
function getLinkFromXHRHeader(xhrheaderstring) {
const re = xhrheaderstring.match(/link: <.+api\/v1\/(.+?)>; rel="(.+?)", <.+api\/v1\/(.+?)>; rel="(.+?)"/);
let di = new Object();
if(re){
di[re[2]] = re[1];
di[re[4]] = re[3];
}
return di;
}
/*----------------------------------
Make relative URL
----------------------------------*/
function getRelativeURL(url, id, options) {
const array = url.split('/');
if ( array.length >= 4 ) {
if ( !options ) {
var options = ""
};
if (id) {
// IF WITHIN ID
if (array[array.length-1].substr(0,1) === '@') {
const link = '/'+array[array.length-1]+'@'+array[2]+options+'?mid='+id+'&';
return link;
} else {
const link = '/@'+array[array.length-1]+'@'+array[2]+options+'?mid='+id+'&';
return link;
}
} else {
// IF WITHOUT ID (URL ONLY)
if (array[array.length-1].substr(0,1) === '@') {
const link = '/'+array[array.length-1]+'@'+array[2]+options;
return link;
} else {
const link = '/@'+array[array.length-1]+'@'+array[2]+options;
return link;
}
}
}
}
/*-----------------------------------
Replace Mastodon link for Halcyon
-----------------------------------*/
function replaceInternalLink(){
// REPLIES
$(".h-card > a").each(function(i) {
$(this).attr('href',getRelativeURL($(this).attr('href')));
});
// HASHTAGS
$(".toot_article a").each(function(i) {
const tags = $(this).attr('href').match(/https:\/\/.+..+\/tags\/(.+)\/?/);
if (tags) {
$(this).attr('href','/search?q='+tags[1]);
}
});
}
/*-----------------------------------
Datetime exchange
-----------------------------------*/
function getConversionedDate(key, value) {
if (value === null ||
value.constructor !== String ||
value.search(/^\d{4}-\d{2}-\d{2}/g) === -1)
return value;
return new Date(value);
}
/*----------------------------------
Relative Datetime
----------------------------------*/
function getRelativeDatetime(current_time, posted_time) {
const calendar = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
var posted_time_original = posted_time,
posted_time = getConversionedDate(null, posted_time_original).getTime(),
elapsedTime = Math.ceil((current_time-posted_time)/1000);
if (elapsedTime < 60) {
const datetime = "・" + elapsedTime + "s";
return datetime;
} else if (elapsedTime < 120) {
const datetime = "・1m";
return datetime;
} else if (elapsedTime < (60*60)) {
const datetime = "・" + (Math.floor(elapsedTime / 60) < 10 ? " " : "") + Math.floor(elapsedTime / 60) + "m";
return datetime;
} else if (elapsedTime < (120*60)) {
const datetime = "・1h";
return datetime;
} else if (elapsedTime < (24*60*60)) {
const datetime = "・" + (Math.floor(elapsedTime / 3600) < 10 ? " " : "") + Math.floor(elapsedTime / 3600) + "h";
return datetime;
} else {
const datetime = "・" + calendar[posted_time_original.getMonth()] + " " + posted_time_original.getDate();
return datetime;
}
}
/*-----------------------------------
ResetApp
-----------------------------------*/
function resetApp() {
/* IMPORTANT */
current_id = Number(localStorage.getItem("current_id"));
current_instance = localStorage.getItem("current_instance");
authtoken = localStorage.getItem("current_authtoken");
// Start api
api = new MastodonAPI({
instance: 'https://'+current_instance,
api_user_token: authtoken
});
// Set local storage items
api.get("accounts/verify_credentials", function(AccountObj) {
// SAVE PROFILE FOR LOCALSTORAGE (ENABLE BETWEEN ONE SESSION)
localStorage.setItem("current_display_name", AccountObj["display_name"]);
localStorage.setItem("current_acct", AccountObj["acct"]);
localStorage.setItem("current_url", getRelativeURL(AccountObj["url"],AccountObj["id"]));
localStorage.setItem("current_header", AccountObj["header"]);
localStorage.setItem("current_avatar", AccountObj["avatar"]);
localStorage.setItem("current_statuses_count", AccountObj["statuses_count"]);
localStorage.setItem("current_following_count", AccountObj["following_count"]);
localStorage.setItem("current_followers_count", AccountObj["followers_count"]);
localStorage.setItem("current_statuses_count_link", getRelativeURL(AccountObj["url"],AccountObj["id"]));
localStorage.setItem("current_following_count_link", getRelativeURL(AccountObj["url"],AccountObj["id"],'/following'));
localStorage.setItem("current_followers_count_link", getRelativeURL(AccountObj["url"],AccountObj["id"],'/followers'));
localStorage.setItem("current_favourites_link", getRelativeURL(AccountObj["url"],AccountObj["id"],'/favourites'));
// PROFILE
current_display_name = localStorage.getItem("current_display_name");
current_acct = localStorage.getItem("current_acct");
current_url = localStorage.getItem("current_url");
current_header = localStorage.getItem("current_header");
current_avatar = localStorage.getItem("current_avatar");
current_statuses_count = localStorage.getItem("current_statuses_count");
current_following_count = localStorage.getItem("current_following_count");
current_followers_count = localStorage.getItem("current_followers_count");
current_statuses_count_link = localStorage.getItem("current_statuses_count_link");
current_following_count_link = localStorage.getItem("current_following_count_link");
current_followers_count_link = localStorage.getItem("current_followers_count_link");
current_favourites_link = localStorage.getItem("current_favourites_link");
// REPLACE USER'S INFORMATIONS
$(".js_current_profile_displayname").text(current_display_name);
$(".js_current_profile_username").text(current_acct);
$(".js_current_profile_link").attr('href', current_url);
$(".js_current_header_image").attr('src', current_header);
$(".js_current_profile_image").attr('src', current_avatar);
$(".js_current_toots_count").text(current_statuses_count);
$(".js_current_following_count").text(current_following_count);
$(".js_current_followers_count").text(current_followers_count);
$(".current_toots_count_link").attr('href', current_statuses_count_link);
$(".current_following_count_link").attr('href', current_following_count_link);
$(".current_followers_count_link").attr('href', current_followers_count_link);
replace_emoji();
});
$.cookie("session", "true", { path: '/' });
}
/*-----------------------------------
RefreshApp
-----------------------------------*/
function refreshApp() {
// IMPORTANT
current_id = Number(localStorage.getItem("current_id"));
current_instance = localStorage.getItem("current_instance");
authtoken = localStorage.getItem("current_authtoken");
// Start api
api = new MastodonAPI({
instance: "https://"+current_instance,
api_user_token: authtoken
});
// PROFILE
current_display_name = localStorage.getItem("current_display_name");
current_acct = localStorage.getItem("current_acct");
current_url = localStorage.getItem("current_url");
current_header = localStorage.getItem("current_header");
current_avatar = localStorage.getItem("current_avatar");
current_statuses_count = localStorage.getItem("current_statuses_count");
current_following_count = localStorage.getItem("current_following_count");
current_followers_count = localStorage.getItem("current_followers_count");
current_statuses_count_link = localStorage.getItem("current_statuses_count_link");
current_following_count_link = localStorage.getItem("current_following_count_link");
current_followers_count_link = localStorage.getItem("current_followers_count_link");
current_favourites_link = localStorage.getItem("current_favourites_link");
}
/*-----------------------------------
Profile
-----------------------------------*/
function setCurrentProfile() {
// REPLACE USER'S INFORMATIONS
$(".js_current_profile_displayname").text(current_display_name);
$(".js_current_profile_username").text(current_acct);
$(".js_current_profile_link").attr("href", current_url);
$(".js_current_header_image").attr("src", current_header);
$(".js_current_profile_image").attr("src", current_avatar);
$(".js_current_toots_count").text(current_statuses_count);
$(".js_current_following_count").text(current_following_count);
$(".js_current_followers_count").text(current_followers_count);
$(".current_toots_count_link").attr("href", current_statuses_count_link);
$(".current_following_count_link").attr("href", current_following_count_link);
$(".current_followers_count_link").attr("href", current_followers_count_link);
replace_emoji();
}
/*----------------------------------
Set User recent images
----------------------------------*/
function putMessage(Message) {
$('#overlay_message').addClass('view');
$('#overlay_message section span').text(Message);
setTimeout(function(){
$("#overlay_message").removeClass("view");
},3000);
};

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,73 @@
HEAD
-----
1.4.1
-----
- Added support for CommonJS.
- Added support for package managers: Jam (http://jamjs.org), volo (http://volojs.org), Component (http://component.io), jspm (http://jspm.io).
- The expires option now interpretes fractions of numbers (e.g. days) correctly.
1.4.0
-----
- Support for AMD.
- Removed deprecated method `$.cookie('name', null)` for deleting a cookie,
use `$.removeCookie('name')`.
- `$.cookie('name')` now returns `undefined` in case such cookie does not exist
(was `null`). Because the return value is still falsy, testing for existence
of a cookie like `if ( $.cookie('foo') )` keeps working without change.
- Renamed bower package definition (component.json -> bower.json) for usage
with up-to-date bower.
- Badly encoded cookies no longer throw exception upon reading but do return
undefined (similar to how we handle JSON parse errors with json = true).
- Added conversion function as optional last argument for reading,
so that values can be changed to a different representation easily on the fly.
Useful for parsing numbers for instance:
```javascript
$.cookie('foo', '42');
$.cookie('foo', Number); // => 42
```
1.3.1
-----
- Fixed issue where it was no longer possible to check for an arbitrary cookie,
while json is set to true, there was a SyntaxError thrown from JSON.parse.
- Fixed issue where RFC 2068 decoded cookies were not properly read.
1.3.0
-----
- Configuration options: `raw`, `json`. Replaces raw option, becomes config:
```javascript
$.cookie.raw = true; // bypass encoding/decoding the cookie value
$.cookie.json = true; // automatically JSON stringify/parse value
```
Thus the default options now cleanly contain cookie attributes only.
- Removing licensing under GPL Version 2, the plugin is now released under MIT License only
(keeping it simple and following the jQuery library itself here).
- Bugfix: Properly handle RFC 2068 quoted cookie values.
- Added component.json for bower.
- Added jQuery plugin package manifest.
- `$.cookie()` returns all available cookies.
1.2.0
-----
- Adding `$.removeCookie('foo')` for deleting a cookie, using `$.cookie('foo', null)` is now deprecated.
1.1
---
- Adding default options.

View File

@ -0,0 +1,51 @@
##Issues
- Report issues or feature requests on [GitHub Issues](https://github.com/carhartl/jquery-cookie/issues).
- If reporting a bug, please add a [simplified example](http://sscce.org/).
##Pull requests
- Create a new topic branch for every separate change you make.
- Create a test case if you are fixing a bug or implementing an important feature.
- Make sure the build runs successfully.
## Development
###Tools
We use the following tools for development:
- [Qunit](http://qunitjs.com/) for tests.
- [NodeJS](http://nodejs.org/download/) required to run grunt.
- [Grunt](http://gruntjs.com/getting-started) for task management.
###Getting started
Install [NodeJS](http://nodejs.org/).
Install globally grunt-cli using the following command:
$ npm install -g grunt-cli
Browse to the project root directory and install the dev dependencies:
$ npm install -d
To execute the build and tests run the following command in the root of the project:
$ grunt
You should see a green message in the console:
Done, without errors.
###Tests
You can also run the tests in the browser.
Start a test server from the project root:
$ grunt connect:tests
This will automatically open the test suite at http://127.0.0.1:9998 in the default browser, with livereload enabled.
_Note: we recommend cleaning all the browser cookies before running the tests, that can avoid false positive failures._
###Automatic build
You can build automatically after a file change using the following command:
$ grunt watch

View File

@ -0,0 +1,168 @@
/*jshint node:true, quotmark:single */
'use strict';
module.exports = function (grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
qunit: {
all: 'test/index.html'
},
jshint: {
options: {
jshintrc: true
},
grunt: 'Gruntfile.js',
source: 'src/**/*.js',
tests: 'test/**/*.js'
},
uglify: {
options: {
banner: '/*! <%= pkg.name %> v<%= pkg.version %> | <%= pkg.license %> */\n'
},
build: {
files: {
'build/jquery.cookie-<%= pkg.version %>.min.js': 'src/jquery.cookie.js'
}
}
},
watch: {
options: {
livereload: true
},
files: '{src,test}/**/*.js',
tasks: 'default'
},
compare_size: {
files: [
'build/jquery.cookie-<%= pkg.version %>.min.js',
'src/jquery.cookie.js'
],
options: {
compress: {
gz: function (fileContents) {
return require('gzip-js').zip(fileContents, {}).length;
}
}
}
},
connect: {
saucelabs: {
options: {
port: 9999,
base: ['.', 'test']
}
},
tests: {
options: {
port: 9998,
base: ['.', 'test'],
open: 'http://127.0.0.1:9998',
keepalive: true,
livereload: true
}
}
},
'saucelabs-qunit': {
all: {
options: {
urls: ['http://127.0.0.1:9999'],
build: process.env.TRAVIS_JOB_ID,
browsers: [
// iOS
{
browserName: 'iphone',
platform: 'OS X 10.9',
version: '7.1'
},
{
browserName: 'ipad',
platform: 'OS X 10.9',
version: '7.1'
},
// Android
{
browserName: 'android',
platform: 'Linux',
version: '4.3'
},
// OS X
{
browserName: 'safari',
platform: 'OS X 10.9',
version: '7'
},
{
browserName: 'safari',
platform: 'OS X 10.8',
version: '6'
},
{
browserName: 'firefox',
platform: 'OS X 10.9',
version: '28'
},
// Windows
{
browserName: 'internet explorer',
platform: 'Windows 8.1',
version: '11'
},
{
browserName: 'internet explorer',
platform: 'Windows 8',
version: '10'
},
{
browserName: 'internet explorer',
platform: 'Windows 7',
version: '11'
},
{
browserName: 'internet explorer',
platform: 'Windows 7',
version: '10'
},
{
browserName: 'internet explorer',
platform: 'Windows 7',
version: '9'
},
{
browserName: 'internet explorer',
platform: 'Windows 7',
version: '8'
},
{
browserName: 'firefox',
platform: 'Windows 7',
version: '29'
},
{
browserName: 'chrome',
platform: 'Windows 7',
version: '34'
},
// Linux
{
browserName: 'firefox',
platform: 'Linux',
version: '29'
}
]
}
}
}
});
// Loading dependencies
for (var key in grunt.file.readJSON('package.json').devDependencies) {
if (key !== 'grunt' && key.indexOf('grunt') === 0) {
grunt.loadNpmTasks(key);
}
}
grunt.registerTask('default', ['jshint', 'qunit', 'uglify', 'compare_size']);
grunt.registerTask('saucelabs', ['connect:saucelabs', 'saucelabs-qunit']);
grunt.registerTask('ci', ['jshint', 'qunit', 'saucelabs']);
};

View File

@ -0,0 +1,20 @@
Copyright 2014 Klaus Hartl
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,162 @@
# IMPORTANT!
This project was moved to https://github.com/js-cookie/js-cookie, check [the discussion](https://github.com/carhartl/jquery-cookie/issues/349).
New issues should be opened at https://github.com/js-cookie/js-cookie/issues
# jquery.cookie [![Build Status](https://travis-ci.org/carhartl/jquery-cookie.png?branch=master)](https://travis-ci.org/carhartl/jquery-cookie) [![Code Climate](https://codeclimate.com/github/carhartl/jquery-cookie.png)](https://codeclimate.com/github/carhartl/jquery-cookie)
A simple, lightweight jQuery plugin for reading, writing and deleting cookies.
**If you're viewing this, you're reading the documentation for the old repository.
[View documentation for the latest backwards compatible release (1.5.1).](https://github.com/js-cookie/js-cookie/tree/v1.5.1)**
## Build Status Matrix
[![Selenium Test Status](https://saucelabs.com/browser-matrix/jquery-cookie.svg)](https://saucelabs.com/u/jquery-cookie)
## Installation
Include script *after* the jQuery library (unless you are packaging scripts somehow else):
```html
<script src="/path/to/jquery.cookie.js"></script>
```
**Do not include the script directly from GitHub (http://raw.github.com/...).** The file is being served as text/plain and as such being blocked
in Internet Explorer on Windows 7 for instance (because of the wrong MIME type). Bottom line: GitHub is not a CDN.
The plugin can also be loaded as AMD or CommonJS module.
## Usage
Create session cookie:
```javascript
$.cookie('name', 'value');
```
Create expiring cookie, 7 days from then:
```javascript
$.cookie('name', 'value', { expires: 7 });
```
Create expiring cookie, valid across entire site:
```javascript
$.cookie('name', 'value', { expires: 7, path: '/' });
```
Read cookie:
```javascript
$.cookie('name'); // => "value"
$.cookie('nothing'); // => undefined
```
Read all available cookies:
```javascript
$.cookie(); // => { "name": "value" }
```
Delete cookie:
```javascript
// Returns true when cookie was successfully deleted, otherwise false
$.removeCookie('name'); // => true
$.removeCookie('nothing'); // => false
// Need to use the same attributes (path, domain) as what the cookie was written with
$.cookie('name', 'value', { path: '/' });
// This won't work!
$.removeCookie('name'); // => false
// This will work!
$.removeCookie('name', { path: '/' }); // => true
```
*Note: when deleting a cookie, you must pass the exact same path, domain and secure options that were used to set the cookie, unless you're relying on the default options that is.*
## Configuration
### raw
By default the cookie value is encoded/decoded when writing/reading, using `encodeURIComponent`/`decodeURIComponent`. Bypass this by setting raw to true:
```javascript
$.cookie.raw = true;
```
### json
Turn on automatic storage of JSON objects passed as the cookie value. Assumes `JSON.stringify` and `JSON.parse`:
```javascript
$.cookie.json = true;
```
## Cookie Options
Cookie attributes can be set globally by setting properties of the `$.cookie.defaults` object or individually for each call to `$.cookie()` by passing a plain object to the options argument. Per-call options override the default options.
### expires
expires: 365
Define lifetime of the cookie. Value can be a `Number` which will be interpreted as days from time of creation or a `Date` object. If omitted, the cookie becomes a session cookie.
### path
path: '/'
Define the path where the cookie is valid. *By default the path of the cookie is the path of the page where the cookie was created (standard browser behavior).* If you want to make it available for instance across the entire domain use `path: '/'`. Default: path of page where the cookie was created.
**Note regarding Internet Explorer:**
> Due to an obscure bug in the underlying WinINET InternetGetCookie implementation, IEs document.cookie will not return a cookie if it was set with a path attribute containing a filename.
(From [Internet Explorer Cookie Internals (FAQ)](http://blogs.msdn.com/b/ieinternals/archive/2009/08/20/wininet-ie-cookie-internals-faq.aspx))
This means one cannot set a path using `path: window.location.pathname` in case such pathname contains a filename like so: `/check.html` (or at least, such cookie cannot be read correctly).
### domain
domain: 'example.com'
Define the domain where the cookie is valid. Default: domain of page where the cookie was created.
### secure
secure: true
If true, the cookie transmission requires a secure protocol (https). Default: `false`.
## Converters
Provide a conversion function as optional last argument for reading, in order to change the cookie's value
to a different representation on the fly.
Example for parsing a value into a number:
```javascript
$.cookie('foo', '42');
$.cookie('foo', Number); // => 42
```
Dealing with cookies that have been encoded using `escape` (3rd party cookies):
```javascript
$.cookie.raw = true;
$.cookie('foo', unescape);
```
You can pass an arbitrary conversion function.
## Contributing
Check out the [Contributing Guidelines](CONTRIBUTING.md)
## Authors
[Klaus Hartl](https://github.com/carhartl)

View File

@ -0,0 +1,18 @@
{
"name": "jquery.cookie",
"version": "1.4.1",
"main": [
"src/jquery.cookie.js"
],
"dependencies": {
"jquery": ">=1.2"
},
"ignore": [
"test",
".*",
"*.json",
"*.md",
"*.txt",
"Gruntfile.js"
]
}

View File

@ -0,0 +1,14 @@
{
"name": "jquery.cookie",
"repo": "carhartl/jquery-cookie",
"description": "A simple, lightweight jQuery plugin for reading, writing and deleting cookies",
"version": "1.4.1",
"keywords": [],
"dependencies": {},
"development": {},
"license": "MIT",
"main": "src/jquery.cookie.js",
"scripts": [
"src/jquery.cookie.js"
]
}

View File

@ -0,0 +1,32 @@
{
"name": "cookie",
"version": "1.4.1",
"title": "jQuery Cookie",
"description": "A simple, lightweight jQuery plugin for reading, writing and deleting cookies.",
"author": {
"name": "Klaus Hartl",
"url": "https://github.com/carhartl"
},
"maintainers": [
{
"name": "Klaus Hartl",
"url": "https://github.com/carhartl"
},
{
"name": "Fagner Martins",
"url": "https://github.com/FagnerMartinsBrack"
}
],
"licenses": [
{
"type": "MIT",
"url": "https://raw.github.com/carhartl/jquery-cookie/master/MIT-LICENSE.txt"
}
],
"dependencies": {
"jquery": ">=1.2"
},
"bugs": "https://github.com/carhartl/jquery-cookie/issues",
"homepage": "https://github.com/carhartl/jquery-cookie",
"docs": "https://github.com/carhartl/jquery-cookie#readme"
}

View File

@ -0,0 +1,50 @@
{
"name": "jquery.cookie",
"version": "1.4.1",
"description": "A simple, lightweight jQuery plugin for reading, writing and deleting cookies.",
"main": "src/jquery.cookie.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "grunt"
},
"repository": {
"type": "git",
"url": "git://github.com/carhartl/jquery-cookie.git"
},
"author": "Klaus Hartl",
"license": "MIT",
"gitHead": "bd3c9713222bace68d25fe2128c0f8633cad1269",
"readmeFilename": "README.md",
"devDependencies": {
"grunt": "~0.4.1",
"grunt-contrib-jshint": "~0.10.0",
"grunt-contrib-uglify": "~0.2.0",
"grunt-contrib-qunit": "~0.2.0",
"grunt-contrib-watch": "~0.6.1",
"grunt-compare-size": "~0.4.0",
"grunt-saucelabs": "~7.0.0",
"grunt-contrib-connect": "~0.7.1",
"gzip-js": "~0.3.0"
},
"volo": {
"url": "https://raw.github.com/carhartl/jquery-cookie/v{version}/src/jquery.cookie.js"
},
"jspm": {
"main": "jquery.cookie",
"files": ["src/jquery.cookie.js"],
"buildConfig": {
"uglify": true
}
},
"jam": {
"dependencies": {
"jquery": ">=1.2"
},
"main": "src/jquery.cookie.js",
"include": [
"src/jquery.cookie.js"
]
}
}

View File

@ -0,0 +1,13 @@
{
"browser": true,
"camelcase": true,
"jquery": true,
"quotmark": "single",
"globals": {
"define": true,
"module": true,
"require": true
},
"extends": "../.jshintrc"
}

View File

@ -0,0 +1,114 @@
/*!
* jQuery Cookie Plugin v1.4.1
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2006, 2014 Klaus Hartl
* Released under the MIT license
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD (Register as an anonymous module)
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS
module.exports = factory(require('jquery'));
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
var pluses = /\+/g;
function encode(s) {
return config.raw ? s : encodeURIComponent(s);
}
function decode(s) {
return config.raw ? s : decodeURIComponent(s);
}
function stringifyCookieValue(value) {
return encode(config.json ? JSON.stringify(value) : String(value));
}
function parseCookieValue(s) {
if (s.indexOf('"') === 0) {
// This is a quoted cookie as according to RFC2068, unescape...
s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
try {
// Replace server-side written pluses with spaces.
// If we can't decode the cookie, ignore it, it's unusable.
// If we can't parse the cookie, ignore it, it's unusable.
s = decodeURIComponent(s.replace(pluses, ' '));
return config.json ? JSON.parse(s) : s;
} catch(e) {}
}
function read(s, converter) {
var value = config.raw ? s : parseCookieValue(s);
return $.isFunction(converter) ? converter(value) : value;
}
var config = $.cookie = function (key, value, options) {
// Write
if (arguments.length > 1 && !$.isFunction(value)) {
options = $.extend({}, config.defaults, options);
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setMilliseconds(t.getMilliseconds() + days * 864e+5);
}
return (document.cookie = [
encode(key), '=', stringifyCookieValue(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// Read
var result = key ? undefined : {},
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling $.cookie().
cookies = document.cookie ? document.cookie.split('; ') : [],
i = 0,
l = cookies.length;
for (; i < l; i++) {
var parts = cookies[i].split('='),
name = decode(parts.shift()),
cookie = parts.join('=');
if (key === name) {
// If second argument (value) is a function it's a converter...
result = read(cookie, value);
break;
}
// Prevent storing a cookie that we couldn't decode.
if (!key && (cookie = read(cookie)) !== undefined) {
result[name] = cookie;
}
}
return result;
};
config.defaults = {};
$.removeCookie = function (key, options) {
// Must not alter options, thus extending a fresh object...
$.cookie(key, '', $.extend({}, options, { expires: -1 }));
return !$.cookie(key);
};
}));

View File

@ -0,0 +1,9 @@
{
"browser": true,
"jquery": true,
"qunit": true,
"-W053": true,
"extends": "../.jshintrc"
}

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>jquery.cookie Test Suite</title>
<link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.14.0.css">
<script src="http://code.jquery.com/qunit/qunit-1.14.0.js"></script>
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="../src/jquery.cookie.js"></script>
<script src="tests.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="../src/jquery.cookie.js"></script>
<script>
try {
Object.defineProperty(document, "cookie", { get: function() { return "first=one; ; second=two"; } });
window.testValue = $.cookie("second");
window.ok = true;
} catch (er) {
}
</script>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,345 @@
// Required for exposing test results to the Sauce Labs API.
// Can be removed when the following issue is fixed:
// https://github.com/axemclion/grunt-saucelabs/issues/84
QUnit.done(function (details) {
window.global_test_results = details;
});
var lifecycle = {
teardown: function () {
$.cookie.defaults = {};
delete $.cookie.raw;
delete $.cookie.json;
$.each($.cookie(), $.removeCookie);
}
};
module('read', lifecycle);
test('simple value', function () {
expect(1);
document.cookie = 'c=v';
strictEqual($.cookie('c'), 'v', 'should return value');
});
test('empty value', function () {
expect(1);
// IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, which
// resulted in a bug while reading such a cookie.
$.cookie('c', '');
strictEqual($.cookie('c'), '', 'should return value');
});
test('not existing', function () {
expect(1);
strictEqual($.cookie('whatever'), undefined, 'return undefined');
});
test('RFC 2068 quoted string', function () {
expect(1);
document.cookie = 'c="v@address.com\\"\\\\\\""';
strictEqual($.cookie('c'), 'v@address.com"\\"', 'should decode RFC 2068 quoted string');
});
test('decode', function () {
expect(1);
document.cookie = encodeURIComponent(' c') + '=' + encodeURIComponent(' v');
strictEqual($.cookie(' c'), ' v', 'should decode key and value');
});
test('decode pluses to space for server side written cookie', function () {
expect(1);
document.cookie = 'c=foo+bar';
strictEqual($.cookie('c'), 'foo bar', 'should convert pluses back to space');
});
test('raw = true', function () {
expect(2);
$.cookie.raw = true;
document.cookie = 'c=%20v';
strictEqual($.cookie('c'), '%20v', 'should not decode value');
// see https://github.com/carhartl/jquery-cookie/issues/50
$.cookie('c', 'foo=bar');
strictEqual($.cookie('c'), 'foo=bar', 'should include the entire value');
});
test('json = true', function () {
expect(1);
if ('JSON' in window) {
$.cookie.json = true;
$.cookie('c', { foo: 'bar' });
deepEqual($.cookie('c'), { foo: 'bar' }, 'should parse JSON');
} else {
ok(true);
}
});
test('not existing with json = true', function () {
expect(1);
if ('JSON' in window) {
$.cookie.json = true;
strictEqual($.cookie('whatever'), undefined, "won't throw exception");
} else {
ok(true);
}
});
test('string with json = true', function () {
expect(1);
if ('JSON' in window) {
$.cookie.json = true;
$.cookie('c', 'v');
strictEqual($.cookie('c'), 'v', 'should return value');
} else {
ok(true);
}
});
test('invalid JSON string with json = true', function () {
expect(1);
if ('JSON' in window) {
$.cookie('c', 'v');
$.cookie.json = true;
strictEqual($.cookie('c'), undefined, "won't throw exception, returns undefined");
} else {
ok(true);
}
});
test('invalid URL encoding', function () {
expect(1);
document.cookie = 'bad=foo%';
strictEqual($.cookie('bad'), undefined, "won't throw exception, returns undefined");
// Delete manually here because it requires raw === true...
$.cookie.raw = true;
$.removeCookie('bad');
});
asyncTest('malformed cookie value in IE (#88, #117)', function () {
expect(1);
// Sandbox in an iframe so that we can poke around with document.cookie.
var iframe = $('<iframe src="malformed_cookie.html"></iframe>')[0];
$(iframe).on('load', function () {
start();
if (iframe.contentWindow.ok) {
strictEqual(iframe.contentWindow.testValue, 'two', 'reads all cookie values, skipping duplicate occurences of "; "');
} else {
// Skip the test where we can't stub document.cookie using
// Object.defineProperty. Seems to work fine in
// Chrome, Firefox and IE 8+.
ok(true, 'N/A');
}
});
document.body.appendChild(iframe);
});
test('Call to read all when there are cookies', function () {
$.cookie('c', 'v');
$.cookie('foo', 'bar');
deepEqual($.cookie(), { c: 'v', foo: 'bar' }, 'returns object containing all cookies');
});
test('Call to read all when there are no cookies at all', function () {
deepEqual($.cookie(), {}, 'returns empty object');
});
test('Call to read all with json: true', function () {
$.cookie.json = true;
$.cookie('c', { foo: 'bar' });
deepEqual($.cookie(), { c: { foo: 'bar' } }, 'returns JSON parsed cookies');
});
test('Call to read all with a badly encoded cookie', function () {
expect(1);
document.cookie = 'bad=foo%';
document.cookie = 'good=foo';
deepEqual($.cookie(), { good: 'foo' }, 'returns object containing all decodable cookies');
// Delete manually here because it requires raw === true...
$.cookie.raw = true;
$.removeCookie('bad');
});
module('write', lifecycle);
test('String primitive', function () {
expect(1);
$.cookie('c', 'v');
strictEqual($.cookie('c'), 'v', 'should write value');
});
test('String object', function () {
expect(1);
$.cookie('c', new String('v'));
strictEqual($.cookie('c'), 'v', 'should write value');
});
test('value "[object Object]"', function () {
expect(1);
$.cookie('c', '[object Object]');
strictEqual($.cookie('c'), '[object Object]', 'should write value');
});
test('number', function () {
expect(1);
$.cookie('c', 1234);
strictEqual($.cookie('c'), '1234', 'should write value');
});
test('null', function () {
expect(1);
$.cookie('c', null);
strictEqual($.cookie('c'), 'null', 'should write value');
});
test('undefined', function () {
expect(1);
$.cookie('c', undefined);
strictEqual($.cookie('c'), 'undefined', 'should write value');
});
test('expires option as days from now', function () {
expect(1);
var sevenDaysFromNow = new Date();
sevenDaysFromNow.setDate(sevenDaysFromNow.getDate() + 21);
strictEqual($.cookie('c', 'v', { expires: 21 }), 'c=v; expires=' + sevenDaysFromNow.toUTCString(),
'should write the cookie string with expires');
});
test('expires option as fraction of a day', function () {
expect(1);
var now = new Date().getTime();
var expires = Date.parse($.cookie('c', 'v', { expires: 0.5 }).replace(/.+expires=/, ''));
// When we were using Date.setDate() fractions have been ignored
// and expires resulted in the current date. Allow 1000 milliseconds
// difference for execution time.
ok(expires > now + 1000, 'should write expires attribute with the correct date');
});
test('expires option as Date instance', function () {
expect(1);
var sevenDaysFromNow = new Date();
sevenDaysFromNow.setDate(sevenDaysFromNow.getDate() + 7);
strictEqual($.cookie('c', 'v', { expires: sevenDaysFromNow }), 'c=v; expires=' + sevenDaysFromNow.toUTCString(),
'should write the cookie string with expires');
});
test('return value', function () {
expect(1);
strictEqual($.cookie('c', 'v'), 'c=v', 'should return written cookie string');
});
test('defaults', function () {
expect(2);
$.cookie.defaults.path = '/foo';
ok($.cookie('c', 'v').match(/path=\/foo/), 'should use options from defaults');
ok($.cookie('c', 'v', { path: '/bar' }).match(/path=\/bar/), 'options argument has precedence');
});
test('raw = true', function () {
expect(1);
$.cookie.raw = true;
strictEqual($.cookie('c[1]', 'v[1]'), 'c[1]=v[1]', 'should not encode');
// Delete manually here because it requires raw === true...
$.removeCookie('c[1]');
});
test('json = true', function () {
expect(1);
$.cookie.json = true;
if ('JSON' in window) {
$.cookie('c', { foo: 'bar' });
strictEqual(document.cookie, 'c=' + encodeURIComponent(JSON.stringify({ foo: 'bar' })), 'should stringify JSON');
} else {
ok(true);
}
});
module('removeCookie', lifecycle);
test('deletion', function () {
expect(1);
$.cookie('c', 'v');
$.removeCookie('c');
strictEqual(document.cookie, '', 'should delete the cookie');
});
test('when sucessfully deleted', function () {
expect(1);
$.cookie('c', 'v');
strictEqual($.removeCookie('c'), true, 'returns true');
});
test('when cookie does not exist', function () {
expect(1);
strictEqual($.removeCookie('c'), true, 'returns true');
});
test('when deletion failed', function () {
expect(1);
$.cookie('c', 'v');
var originalCookie = $.cookie;
$.cookie = function () {
// Stub deletion...
if (arguments.length === 1) {
return originalCookie.apply(null, arguments);
}
};
strictEqual($.removeCookie('c'), false, 'returns false');
$.cookie = originalCookie;
});
test('with options', function () {
expect(1);
var options = { path: '/' };
$.cookie('c', 'v', options);
$.removeCookie('c', options);
strictEqual(document.cookie, '', 'should delete the cookie');
});
test('passing options reference', function () {
expect(1);
var options = { path: '/' };
$.cookie('c', 'v', options);
$.removeCookie('c', options);
deepEqual(options, { path: '/' }, "won't alter options object");
});
test('[] used in name', function () {
expect(1);
$.cookie.raw = true;
document.cookie = 'c[1]=foo';
$.removeCookie('c[1]');
strictEqual(document.cookie, '', 'delete the cookie');
});
module('conversion', lifecycle);
test('read converter', function() {
expect(1);
$.cookie('c', '1');
strictEqual($.cookie('c', Number), 1, 'converts read value');
});
test('read converter with raw = true', function() {
expect(1);
$.cookie.raw = true;
$.cookie('c', '1');
strictEqual($.cookie('c', Number), 1, 'does not decode, but converts read value');
});

Binary file not shown.

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,3 @@
Javascript Mastodon API Client Library for Browser Clients
Check out index.html for a "how-to"

View File

@ -0,0 +1,117 @@
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="mastodon.js"></script>
<script>
// initialize library
// api_user_token has not to be set at this point if you don't have one yet
// NOTE: It needs jQuery!
var api = new MastodonAPI({
instance: "",
api_user_token: ""
});
//
// BASIC REST FUNCTIONS
// you can use every api endpoint from https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md
//
api.post("statuses", {status:"i like pineapple on pizza"}, function (data) {
// will be called if the toot was successful
// data is the http response object
//sidenote: please do not actually execute this request, you could be bullied by your friends
});
api.get("accounts/1/following", [
["since_id", 420],
["max_id", 1337]
], function(data) {
// returns all users account id 1 is following in the id range from 420 to 1337
// you don't have to supply the parameters, you can also just go with .get(endpoint, callback)
});
api.delete("statuses/1", function (data) {
//deletes toot with id 1
});
//
// STREAMING
//
//lets initialize a stream! stream types are
// user for your local home TL and notifications
// public for your federated TL
// public:local for your home TL
// hashtag&tag=fuckdonaldtrump for the stream of #fuckdonaldtrump
api.stream("user", function(data) {
// data is an object containing two entries
// event determines which type of data you got
// payload is the actual data
// event can be notification or update
if (data.event === "notification") {
// data.payload is a notification
} else if (data.event === "update") {
// status update for one of your timelines
} else {
// probably an error
}
})
// AUTH
// first, we register our application
// for this we need no key since we don't have one yet
api.registerApplication("my cool application name",
window.location.href, // redirect uri, we will need this later on
["read", "write", "follow"], //scopes
"http://mycoolwebsite.com", //website on the login screen
function(data) {
// we got our application
// lets save it to our browser storage
localStorage.setItem("mastodon_client_id", data["client_id"]);
localStorage.setItem("mastodon_client_secret", data["client_secret"]);
localStorage.setItem("mastodon_client_redirect_uri", data["redirect_uri"]);
// now, that we have saved our application data, generate an oauth url and send
// our user to it!
window.location.href = api.generateAuthLink(data["client_id"],
data["redirect_uri"],
"code", // oauth method
["read", "write", "follow"] //scopes
);
}
);
// now we get to the part why the redirect uri was set to our current location
// we can just access to code from the url hash
if (window.location.href.indexOf("?code=") !== -1) {
// nice, we got our auth code!
// lets put it into a variable
var authCode = window.location.href.replace(window.location.origin + window.location.pathname + "?code=", "");
// nice variable clusterfuck, eh?
// we have everything needed to access our oauth token
api.getAccessTokenFromAuthCode(
localStorage.getItem("mastodon_client_id"),
localStorage.getItem("mastodon_client_secret"),
localStorage.getItem("mastodon_client_redirect_uri"),
authCode,
function(data) {
// AAAND DATA CONTAINS OUR TOKEN!
// use api.setConfig("api_user_token", tokenvar) to set it without having to reinit the entire
// library.
}
)
}
</script>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,277 @@
// mastodon javascript lib
// by @kirschn@pleasehug.me 2017
// no fucking copyright
// do whatever you want with it
// but please don't hurt it (and keep this header)
var MastodonAPI = function(config) {
var apiBase = config.instance + "/api/v1/";
return {
setConfig: function (key, value) {
// modify initial config afterwards
config[key] = value;
},
getConfig: function(key) {
//get config key
return config[key];
},
get: function (endpoint) {
// for GET API calls
// can be called with two or three parameters
// endpoint, callback
// or
// endpoint, queryData, callback
// where querydata is an object {["paramname1", "paramvalue1], ["paramname2","paramvalue2"]}
// variables
var queryData, callback,
queryStringAppend = "?";
// check with which arguments we're supplied
if (typeof arguments[1] === "function") {
queryData = {};
callback = arguments[1];
} else {
queryData = arguments[1];
callback = arguments[2];
}
// build queryData Object into a URL Query String
for (var i in queryData) {
if (queryData.hasOwnProperty(i)) {
if (typeof queryData[i] === "string") {
queryStringAppend += queryData[i] + "&";
} else if (typeof queryData[i] === "object") {
queryStringAppend += queryData[i].name + "="+ queryData[i].data + "&";
}
}
}
// ajax function
$.ajax({
url: apiBase + endpoint + queryStringAppend,
type: "GET",
headers: {"Authorization": "Bearer " + config.api_user_token},
success: function(data, textStatus, xhr) {
//weeey it was successful
console.log("Successful GET API request to " +apiBase+endpoint);
responce_headers = xhr.getAllResponseHeaders();
//aaand start the callback
//might have to check what "textStatus" actually is, jquery docs are a bit dodgy
callback(data,textStatus);
},
error: function(xhr, textStatus, errorThrown) {
putMessage(`[${xhr.status}] ${xhr.responseJSON['error']}`);
if ( xhr.status === 401 ) {
location.href = "/logout"
}
}
});
},
getArray: function (endpoint) {
// for GET API calls
// can be called with two or three parameters
// endpoint, callback
// or
// endpoint, queryData, callback
// where querydata is an object {["paramname1", "paramvalue1], ["paramname2","paramvalue2"]}
// variables
var queryData, callback,
queryStringAppend = "?";
// check with which arguments we're supplied
if (typeof arguments[1] === "function") {
queryData = {};
callback = arguments[1];
} else {
queryData = arguments[1];
callback = arguments[2];
}
// build queryData Object into a URL Query String
for (var i in queryData) {
if (queryData.hasOwnProperty(i)) {
if (typeof queryData[i] === "string") {
queryStringAppend += queryData[i] + "&";
} else if (typeof queryData[i] === "object") {
for ( var j in queryData[i].data ){
queryStringAppend += queryData[i].name + "[]="+ queryData[i].data[j] + "&";
}
}
}
}
// ajax function
$.ajax({
url: apiBase + endpoint + queryStringAppend,
type: "GET",
headers: {"Authorization": "Bearer " + config.api_user_token},
success: function(data, textStatus, xhr) {
//weeey it was successful
console.log("Successful GET API request to " +apiBase+endpoint);
responce_headers = xhr.getAllResponseHeaders();
//aaand start the callback
//might have to check what "textStatus" actually is, jquery docs are a bit dodgy
callback(data,textStatus);
},
error: function(xhr, textStatus, errorThrown) {
putMessage(`[${xhr.status}] ${xhr.responseJSON['error']}`);
if ( xhr.status === 401 ) {
location.href = "/logout"
}
}
});
},
getOther: function (domainAndEndpoint) {
var queryData, callback,
queryStringAppend = "?";
// check with which arguments we're supplied
if (typeof arguments[1] === "function") {
queryData = {};
callback = arguments[1];
} else {
queryData = arguments[1];
callback = arguments[2];
}
// build queryData Object into a URL Query String
for (var i in queryData) {
if (queryData.hasOwnProperty(i)) {
if (typeof queryData[i] === "string") {
queryStringAppend += queryData[i] + "&";
} else if (typeof queryData[i] === "object") {
queryStringAppend += queryData[i].name + "="+ queryData[i].data + "&";
}
}
}
// ajax function
$.ajax({
url: domainAndEndpoint + queryStringAppend,
type: "GET",
success: function(data, textStatus, xhr) {
//weeey it was successful
console.log("Successful GET API request to " +domainAndEndpoint);
responce_headers = xhr.getAllResponseHeaders();
//aaand start the callback
//might have to check what "textStatus" actually is, jquery docs are a bit dodgy
callback(data,textStatus);
},
error: function(xhr, textStatus, errorThrown) {
putMessage(`[${xhr.status}] ${xhr.responseJSON['error']}`);
if ( xhr.status === 401 ) {
location.href = "/logout"
}
}
});
},
post: function (endpoint) {
// for POST API calls
var postData, callback;
// check with which arguments we're supplied
if (typeof arguments[1] === "function") {
postData = {};
callback = arguments[1];
} else {
postData = arguments[1];
callback = arguments[2];
}
$.ajax({
url: apiBase + endpoint,
type: "POST",
data: postData,
headers: {"Authorization": "Bearer " + config.api_user_token},
success: function(data, textStatus) {
console.log("Successful POST API request to " +apiBase+endpoint);
callback(data,textStatus)
},
error: function(xhr, textStatus, errorThrown) {
putMessage(`[${xhr.status}] ${xhr.responseJSON['error']}`);
if ( xhr.status === 401 ) {
location.href = "/logout"
}
}
});
},
postMedia: function (endpoint) {
// for POST API calls
var postData, callback;
// check with which arguments we're supplied
if (typeof arguments[1] === "function") {
postData = {};
callback = arguments[1];
} else {
postData = arguments[1];
callback = arguments[2];
}
$.ajax({
url: apiBase + endpoint,
type: "POST",
data: postData,
contentType: false,
processData: false,
headers: {"Authorization": "Bearer " + config.api_user_token},
success: function(data, textStatus) {
console.log("Successful POST API request to " +apiBase+endpoint);
callback(data,textStatus)
},
error: function(xhr, textStatus, errorThrown) {
putMessage(`[${xhr.status}] ${xhr.responseJSON['error']}`);
if ( xhr.status === 401 ) {
location.href = "/logout"
}
}
});
},
delete: function (endpoint, callback) {
// for DELETE API calls.
$.ajax({
url: apiBase + endpoint,
type: "DELETE",
headers: {"Authorization": "Bearer " + config.api_user_token},
success: function(data, textStatus) {
console.log("Successful DELETE API request to " +apiBase+endpoint);
callback(data,textStatus)
},
error: function(xhr, textStatus, errorThrown) {
putMessage(`[${xhr.status}] ${xhr.responseJSON['error']}`);
if ( xhr.status === 401 ) {
location.href = "/logout"
}
}
});
},
stream: function (streamType, onData) {
// Event Stream Support
// websocket streaming is undocumented. i had to reverse engineer the fucking web client.
// streamType is either
// user for your local home TL and notifications
// public for your federated TL
// public:local for your home TL
// hashtag&tag=fuckdonaldtrump for the stream of #fuckdonaldtrump
// callback gets called whenever new data ist recieved
// callback { event: (eventtype), payload: {mastodon object as described in the api docs} }
// eventtype could be notification (=notification) or update (= new toot in TL)
var es = new WebSocket("wss://" + apiBase.substr(8)
+"streaming?access_token=" + config.api_user_token + "&stream=" + streamType);
var listener = function (event) {
console.log("Got Data from Stream " + streamType);
event = JSON.parse(event.data);
event.payload = JSON.parse(event.payload);
onData(event);
};
es.onmessage = listener;
}
};
};
// node.js
if (typeof module !== 'undefined') { module.exports = MastodonAPI; };

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,223 @@
/**
* http://www.openjs.com/scripts/events/keyboard_shortcuts/
* Version : 2.01.B
* By Binny V A
* License : BSD
*/
shortcut = {
'all_shortcuts':{},//All the shortcuts are stored in this array
'add': function(shortcut_combination,callback,opt) {
//Provide a set of default options
var default_options = {
'type':'keydown',
'propagate':false,
'disable_in_input':false,
'target':document,
'keycode':false
}
if(!opt) opt = default_options;
else {
for(var dfo in default_options) {
if(typeof opt[dfo] == 'undefined') opt[dfo] = default_options[dfo];
}
}
var ele = opt.target;
if(typeof opt.target == 'string') ele = document.getElementById(opt.target);
var ths = this;
shortcut_combination = shortcut_combination.toLowerCase();
//The function to be called at keypress
var func = function(e) {
e = e || window.event;
if(opt['disable_in_input']) { //Don't enable shortcut keys in Input, Textarea fields
var element;
if(e.target) element=e.target;
else if(e.srcElement) element=e.srcElement;
if(element.nodeType==3) element=element.parentNode;
if(element.tagName == 'INPUT' || element.tagName == 'TEXTAREA') return;
}
//Find Which key is pressed
if (e.keyCode) code = e.keyCode;
else if (e.which) code = e.which;
var character = String.fromCharCode(code).toLowerCase();
if(code == 188) character=","; //If the user presses , when the type is onkeydown
if(code == 190) character="."; //If the user presses , when the type is onkeydown
var keys = shortcut_combination.split("+");
//Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked
var kp = 0;
//Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken
var shift_nums = {
"`":"~",
"1":"!",
"2":"@",
"3":"#",
"4":"$",
"5":"%",
"6":"^",
"7":"&",
"8":"*",
"9":"(",
"0":")",
"-":"_",
"=":"+",
";":":",
"'":"\"",
",":"<",
".":">",
"/":"?",
"\\":"|"
}
//Special Keys - and their codes
var special_keys = {
'esc':27,
'escape':27,
'tab':9,
'space':32,
'return':13,
'enter':13,
'backspace':8,
'scrolllock':145,
'scroll_lock':145,
'scroll':145,
'capslock':20,
'caps_lock':20,
'caps':20,
'numlock':144,
'num_lock':144,
'num':144,
'pause':19,
'break':19,
'insert':45,
'home':36,
'delete':46,
'end':35,
'pageup':33,
'page_up':33,
'pu':33,
'pagedown':34,
'page_down':34,
'pd':34,
'left':37,
'up':38,
'right':39,
'down':40,
'f1':112,
'f2':113,
'f3':114,
'f4':115,
'f5':116,
'f6':117,
'f7':118,
'f8':119,
'f9':120,
'f10':121,
'f11':122,
'f12':123
}
var modifiers = {
shift: { wanted:false, pressed:false},
ctrl : { wanted:false, pressed:false},
alt : { wanted:false, pressed:false},
meta : { wanted:false, pressed:false} //Meta is Mac specific
};
if(e.ctrlKey) modifiers.ctrl.pressed = true;
if(e.shiftKey) modifiers.shift.pressed = true;
if(e.altKey) modifiers.alt.pressed = true;
if(e.metaKey) modifiers.meta.pressed = true;
for(var i=0; k=keys[i],i<keys.length; i++) {
//Modifiers
if(k == 'ctrl' || k == 'control') {
kp++;
modifiers.ctrl.wanted = true;
} else if(k == 'shift') {
kp++;
modifiers.shift.wanted = true;
} else if(k == 'alt') {
kp++;
modifiers.alt.wanted = true;
} else if(k == 'meta') {
kp++;
modifiers.meta.wanted = true;
} else if(k.length > 1) { //If it is a special key
if(special_keys[k] == code) kp++;
} else if(opt['keycode']) {
if(opt['keycode'] == code) kp++;
} else { //The special keys did not match
if(character == k) kp++;
else {
if(shift_nums[character] && e.shiftKey) { //Stupid Shift key bug created by using lowercase
character = shift_nums[character];
if(character == k) kp++;
}
}
}
}
if(kp == keys.length &&
modifiers.ctrl.pressed == modifiers.ctrl.wanted &&
modifiers.shift.pressed == modifiers.shift.wanted &&
modifiers.alt.pressed == modifiers.alt.wanted &&
modifiers.meta.pressed == modifiers.meta.wanted) {
callback(e);
if(!opt['propagate']) { //Stop the event
//e.cancelBubble is supported by IE - this will kill the bubbling process.
e.cancelBubble = true;
e.returnValue = false;
//e.stopPropagation works in Firefox.
if (e.stopPropagation) {
e.stopPropagation();
e.preventDefault();
}
return false;
}
}
}
this.all_shortcuts[shortcut_combination] = {
'callback':func,
'target':ele,
'event': opt['type']
};
//Attach the function with the event
if(ele.addEventListener) ele.addEventListener(opt['type'], func, false);
else if(ele.attachEvent) ele.attachEvent('on'+opt['type'], func);
else ele['on'+opt['type']] = func;
},
//Remove the shortcut - just specify the shortcut and I will remove the binding
'remove':function(shortcut_combination) {
shortcut_combination = shortcut_combination.toLowerCase();
var binding = this.all_shortcuts[shortcut_combination];
delete(this.all_shortcuts[shortcut_combination])
if(!binding) return;
var type = binding['event'];
var ele = binding['target'];
var callback = binding['callback'];
if(ele.detachEvent) ele.detachEvent('on'+type, callback);
else if(ele.removeEventListener) ele.removeEventListener(type, callback, false);
else ele['on'+type] = false;
}
}

66
public_html/federated.php Normal file
View File

@ -0,0 +1,66 @@
<?php include ('header.php'); ?>
<!-- MAIN -->
<main id="main" class="federated">
<div class="article_wrap">
<aside class="left_column">
<?php include dirname(__FILE__).('/widgets/side_current_user.php'); ?>
<?php include dirname(__FILE__).('/widgets/side_load_options.php'); ?>
<div class="right_column_clone">
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</div>
</aside>
<article class="center_column">
<header class="timeline_header">
<?php include dirname(__FILE__).('/widgets/create_status.php'); ?>
</header>
<div id="js-stream_update">
<button>
View <span></span> new Toots
</button>
</div>
<ul id="js-timeline" class="timeline">
</ul>
<footer id="js-timeline_footer" class="timeline_footer">
<i class="fa fa-spin fa-circle-o-notch" aria-hidden="true"></i>
</footer>
</article>
<aside class="right_column">
<section class="side_widgets_wrap">
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
</section>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</aside>
</div>
</main>
<script>
// namespace
current_file = location.pathname;
setTimeline("timelines/public");
$("#federated_nav").addClass('view');
$('title').text('Halcyon / Federated');
</script>
<?php include ('footer.php'); ?>

76
public_html/footer.php Normal file
View File

@ -0,0 +1,76 @@
<!-- FOOTER -->
<footer id="footer">
<!-- Be enjoying your Mastodon!-->
</footer>
<?php include dirname(__FILE__).('/widgets/overlay_message.php'); ?>
<div id="js-overlay_content_wrap">
<div id="js-overlay_content">
<div class="temporary_object">
</div>
<div class="parmanent_object">
<?php include dirname(__FILE__).('/widgets/overlay_create_status.php'); ?>
<?php include dirname(__FILE__).('/widgets/overlay_single_reply.php'); ?>
<?php include dirname(__FILE__).('/widgets/overlay_copy_link.php'); ?>
</div>
<button class="close_button"><i class="fa fa-times" aria-hidden="true"></i></button>
</div>
</div>
<script>
<?php if (isset($_GET['status'])): ?>
setOverlayStatus('<?php echo $_GET['status']; ?>');
<?php endif; ?>
setCurrentProfile();
badges_update();
$('.header_settings_link').attr('href','https://'+current_instance+'/settings/preferences');
$('.footer_widget_about').attr('href','https://'+current_instance+'/about');
$('.footer_widget_instance').attr('href','https://'+current_instance+'/about/more');
$('.footer_widget_terms').attr('href','https://'+current_instance+'/terms');
</script>
<script>
const what_to_follow_0 = JSON.parse(localStorage.getItem("what_to_follow_0"));
const what_to_follow_1 = JSON.parse(localStorage.getItem("what_to_follow_1"));
const what_to_follow_2 = JSON.parse(localStorage.getItem("what_to_follow_2"));
$('.what_to_follow_0 > .icon_box img').attr('src', what_to_follow_0.avatar);
$('.what_to_follow_0 .label_box > a').attr('href', getRelativeURL(what_to_follow_0.url, what_to_follow_0.id) );
$('.what_to_follow_0 .label_box > a > h3 .dn').text(what_to_follow_0.display_name);
$('.what_to_follow_0 .label_box > a > h3 .un').text('@'+what_to_follow_0.username);
$('.what_to_follow_0 .label_box > .follow_button').attr('mid', what_to_follow_0.id);
$('.what_to_follow_0 .label_box > .follow_button').attr('data', what_to_follow_0.url);
$('.what_to_follow_1 > .icon_box img').attr('src', what_to_follow_1.avatar);
$('.what_to_follow_1 .label_box > a').attr('href', getRelativeURL(what_to_follow_1.url, what_to_follow_1.id) );
$('.what_to_follow_1 .label_box > a > h3 .dn').text(what_to_follow_1.display_name);
$('.what_to_follow_1 .label_box > a > h3 .un').text('@'+what_to_follow_1.username);
$('.what_to_follow_1 .label_box > .follow_button').attr('mid', what_to_follow_1.id);
$('.what_to_follow_0 .label_box > .follow_button').attr('data', what_to_follow_1.url);
$('.what_to_follow_2 > .icon_box img').attr('src', what_to_follow_2.avatar);
$('.what_to_follow_2 .label_box > a').attr('href', getRelativeURL(what_to_follow_2.url, what_to_follow_2.id) );
$('.what_to_follow_2 .label_box > a > h3 .dn').text(what_to_follow_2.display_name);
$('.what_to_follow_2 .label_box > a > h3 .un').text('@'+what_to_follow_2.username);
$('.what_to_follow_2 .label_box > .follow_button').attr('mid', what_to_follow_2.id);
$('.what_to_follow_0 .label_box > .follow_button').attr('data', what_to_follow_2.url);
replace_emoji();
</script>
</body>
</html>

160
public_html/header.php Normal file
View File

@ -0,0 +1,160 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Halcyon</title>
<link rel="shortcut icon" href="/assets/images/favicon.ico" />
<link rel="stylesheet" type="text/css" href="/assets/css/style.css" media="all" />
<link rel="stylesheet" type="text/css" href="//cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" media="all" />
<script src="//yastatic.net/jquery/3.2.1/jquery.min.js"></script>
<script src="/assets/js/halcyon/halcyonFunctions.js"></script>
<script src="/assets/js/mastodon.js/mastodon.js"></script><!-- thx @kirschn -->
<script src="/assets/js/jquery-cookie/src/jquery.cookie.js"></script>
<script src="/assets/js/shortcut.js"></script>
<script src="/assets/js/replace_emoji.js"></script>
<script src="/assets/js/halcyon/halcyonUI.js"></script>
<script src="//cdn.staticfile.org/twemoji/2.2.5/twemoji.min.js"></script>
<script>
if (
!localStorage.getItem("current_id") |
!localStorage.getItem("current_instance") |
!localStorage.getItem("current_authtoken")
){
location.href = "/login";
} else {
if( $.cookie("session") === "true" ) {
refreshApp();
} else if ( $.cookie("session") === undefined ) {
resetApp();
}
}
</script>
</head>
<body>
<!-- HEADER -->
<header id="header">
<div class="header_nav_wrap">
<!-- HEADER LEFT -->
<nav class="header_left_box">
<ul class="header_nav_list">
<!-- HOME -->
<li id="header_nav_item_home" class="header_nav_item">
<a href="/" id="home_nav">
<i class="fa fa-fw fa-home"></i>
<span>Home</span>
</a>
<div class="home_badge nav_badge invisible"></div>
</li>
<!-- LOCAL -->
<li id="header_nav_item_local" class="header_nav_item local_nav">
<a href="/local" id="local_nav">
<i class="fa fa-fw fa-users"></i>
<span>Local</span>
</a>
<div class="local_badge nav_badge invisible"></div>
</li>
<!-- FEDERATED -->
<li id="header_nav_item_federated" class="header_nav_item federated_nav">
<a href="/federated" id="federated_nav">
<i class="fa fa-fw fa-globe"></i>
<span>Federated</span>
</a>
<div class="federated_badge nav_badge invisible"></div>
</li>
<!-- NOTIFICATIONS -->
<li id="header_nav_item_notifications" class="header_nav_item notifications_nav">
<a href="/notifications" id="notifications_nav">
<i class="fa fa-fw fa-bell"></i>
<span>Notifications</span>
</a>
<div class="notification_badge nav_badge invisible"></div>
</li>
</ul>
</nav>
<!-- HEADER CENTER -->
<div class="header_center_box">
<h1 class="header_nav_item mastodon_logo logo_box">
<a href="/">
<img src="/assets/images/logo_halcyon.png" alt="Halcyon for Mastodon" />
</a>
</h1>
</div>
<!-- HEADER RIGHT -->
<nav class="header_right_box">
<ul class="header_nav_list">
<!-- SEARCH -->
<li class="header_nav_item serch_form_wrap">
<form class="search_form" action="/search" method="GET">
<input id="search_form" class="search_form_input" placeholder="Search Mastodon" type="text" name="q" accesskey="/"/>
<span class="search_form_submit">
<button type="submit">
<i class="fa fa-fw fa-search"></i>
</button>
</span>
</form>
</li>
<!-- ACCOUNT -->
<li class="header_nav_item my_account_wrap">
<button class="header_account_avatar">
<div class="my_account">
<img class="js_current_profile_image" />
</div>
</button>
<nav class="header_my_account_nav invisible">
<ul>
<li>
<a class="js_current_profile_link emoji_poss">
<span class="js_current_profile_displayname display_name"></span>
<span>View profile</span>
</a>
</li>
</ul>
<ul>
<li>
<a class="header_settings_link" href="">Settings</a>
</li>
<li>
<a href="/logout">Log out</a>
</li>
</ul>
</nav>
</li>
<!-- TOOT -->
<li class="header_nav_item toot_button_wrap">
<button id="creat_status" class="toot_button" accesskey="n">
<div class="toot_button_label">
<i class="fa fa-fw fa-pencil-square-o"></i>
<span>Toot</span>
</div>
</button>
</li>
</ul>
</nav>
</div>
</header>

62
public_html/home.php Normal file
View File

@ -0,0 +1,62 @@
<?php include ('header.php'); ?>
<!-- MAIN -->
<main id="main" class="home">
<div class="article_wrap">
<aside class="left_column">
<?php include dirname(__FILE__).('/widgets/side_current_user.php'); ?>
<?php include dirname(__FILE__).('/widgets/side_load_options.php'); ?>
<div class="right_column_clone">
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</div>
</aside>
<article class="center_column">
<header class="timeline_header">
<?php include dirname(__FILE__).('/widgets/create_status.php'); ?>
</header>
<div id="js-stream_update">
<button>
View <span></span> new Toots
</button>
</div>
<ul id="js-timeline" class="timeline">
</ul>
<footer id="js-timeline_footer" class="timeline_footer">
<i class="fa fa-spin fa-circle-o-notch" aria-hidden="true"></i>
</footer>
</article>
<aside class="right_column">
<section class="side_widgets_wrap">
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
</section>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</aside>
</div>
</main>
<script>
// namespace
current_file = location.pathname;
setTimeline("timelines/home");
$("#home_nav").addClass('view');
$('title').text('Halcyon');
</script>
<?php include ('footer.php'); ?>

1
public_html/index.php Normal file
View File

@ -0,0 +1 @@
<?php include ('home.php'); ?>

76
public_html/local.php Normal file
View File

@ -0,0 +1,76 @@
<?php include ('header.php'); ?>
<!-- MAIN -->
<main id="main">
<div class="article_wrap">
<aside class="left_column">
<?php include dirname(__FILE__).('/widgets/side_current_user.php'); ?>
<?php include dirname(__FILE__).('/widgets/side_load_options.php'); ?>
<div class="right_column_clone">
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</div>
</aside>
<article class="center_column">
<header class="timeline_header">
<?php include dirname(__FILE__).('/widgets/create_status.php'); ?>
</header>
<div id="js-stream_update">
<button>
View <span></span> new Toots
</button>
</div>
<ul id="js-timeline" class="timeline">
</ul>
<footer id="js-timeline_footer" class="timeline_footer">
<i class="fa fa-spin fa-circle-o-notch" aria-hidden="true"></i>
</footer>
</article>
<aside class="right_column">
<section class="side_widgets_wrap">
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
</section>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</aside>
</div>
</main>
<script>
// namespace
current_file = location.pathname;
if (
localStorage.getItem("setting_local_instance") === "default" |
localStorage.getItem("setting_local_instance") === current_instance
) {
setTimeline("timelines/public", [{name:"local",data:"ture"}]);
} else {
setOtherTimeline(localStorage.getItem("setting_local_instance")+"/api/v1/", [{name:"local",data:"ture"}]);
}
$("#local_nav").addClass('view');
$('title').text('Halcyon / Local');
</script>
<?php include ('footer.php'); ?>

View File

@ -0,0 +1,571 @@
@charset "utf-8";
/*--------------------------------------
Reset
--------------------------------------*/
* {
margin: 0;
padding: 0;
font-size: 100%;
}
a {
text-decoration: none;
word-break: break-all;
color: inherit;
}
a:hover {
text-decoration: underline;
}
ul, ol {
list-style: none;
padding: 0;
margin: 0;
}
img {
vertical-align: top;
border: 0;
max-width: 100%;
max-height: 100%;
}
button {
font-size: 100%;
}
.clear {
clear: both;
}
.red {
color: red!important;
}
.invisible {
display: none!important;
}
.no-events {
pointer-events: none!important;
}
.no-underline,
.no-underline:hover {
text-decoration: none!important;
}
.disallow_select {
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
}
.text_ellipsis {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.pointer {
cursor: pointer;
}
.khmer_font {
font-family: "Khmer MN", Helvetica, Arial, sans-serif!important;
}
.gill_font {
font-family: "Gill Sans", Helvetica, Arial, sans-serif!important;
}
.trebuchet_font {
font-family: "Trebuchet MS", Helvetica, Arial, sans-serif!important;
}
h1, h2, h3, h4, h5, h6 {
font-weight: normal;
line-height: 1;
margin: 0;
padding: 0;
}
blockquote, q {
quotes: none;
margin: 0;
}
blockquote * {
margin: 0;
word-break: break-all;
}
blockquote:before, blockquote:after,
q:before, q:after {
content:'';
content:none;
}
article,aside,details,figcaption,figure,
footer,header,hgroup,menu,nav,section {
display:block;
}
textarea {
width: 100%;
}
input {
max-width: 100%;
}
button,
input[type="submit"],
input[type="button"],
input[type="text"],
input[type="password"],
textarea,
select{
background-color: transparent;
border: none;
cursor: pointer;
outline: none;
padding: 0;
appearance: none;
}
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0px 1000px transparent inset;
}
/* Twitter Emoji Prefix */
img.emoji {
height: 1em;
width: 1em;
margin: 0 .05em 0 .1em;
vertical-align: -0.1em;
}
/*--------------------------------------
Base
--------------------------------------*/
html {
font-family : "Trebuchet MS",Helvetica,"ヒラギノ角ゴ Pro W3",
"Hiragino Kaku Gothic Pro",Meiryo,"メイリオ"," Pゴシック",Arial,sans-serif;
font-size : 100%;
line-height : 1;
color: #333;
min-width: 100%;
min-height: 100%;
}
body {
margin: 0;
padding: 0;
min-width: 100%;
min-height: 100%;
word-wrap:break-word;
background-color: #F5F8FA;
}
/*-------------------------------------
HEADER AREA
-------------------------------------*/
#header {
width: 100%;
height: 56px;
color: #fff;
background-color: #222;
box-shadow: 0px 3px 5px rgba(0,0,0,0.26);
position: fixed;
z-index: 999;
}
#header #header_wrap {
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
height: 100%;
}
#header #header_wrap > .header_box {
height: 100%;
flex-grow: 1;
flex-shrink: 0;
}
/*-------------------------------------
HEADER AREA: TITLE
-------------------------------------*/
#header #header_wrap .header_box.header_right_box {
display: flex;
justify-content: flex-start;
}
#header #header_wrap .header_box.header_right_box .title_box {
height: 100%;
margin-left: 24px;
box-sizing: border-box;
}
/*-------------------------------------
HEADER AREA: NAV
-------------------------------------*/
#header #header_wrap .header_box.header_left_box {
display: flex;
justify-content: flex-end;
}
#header #header_wrap .header_box.header_left_box nav {
height: 100%;
}
#header #header_wrap .header_box.header_left_box .nav_box ul {
display: block;
height: 100%;
width: 100%;
}
#header #header_wrap .header_box.header_left_box .nav_box ul a {
height: 100%;
}
#header #header_wrap .header_box.header_left_box .nav_box ul li {
display: block;
float: left;
cursor: pointer;
height: calc(100% - 5px*2 );
margin-top: 5px;
border-bottom: 0px solid #007BD0;
}
#header #header_wrap .header_box.header_left_box .nav_box ul li span {
display: block;
height: 100%;
margin: auto;
padding: 15px 24px;
font-size: 14px;
box-sizing: border-box;
}
#header #header_wrap .header_box.header_left_box .nav_box ul li span i {
margin-right: 4px;
}
#header #header_wrap .header_box.header_left_box .nav_box ul li:hover {
color: #189EFC;
border-bottom-width: 5px;
transition: 0.15s ease-out;
}
/*-------------------------------------
MAIN
-------------------------------------*/
#main {
padding-top: 56px;
}
/*-------------------------------------
FORM
-------------------------------------*/
#main #login_form_wrap {
width: 100%;
height: 560px;
padding: 56px 0;
margin-bottom: -200px;
box-sizing: border-box;
background-image: url("/login/assets/images/background.jpg");
background-size: cover;
background-repeat: no-repeat;
}
#main #login_form_wrap .login_form {
position: relative;
width: 640px;
height: 220px;
padding: 36px 24px;
margin: auto;
overflow: hidden;
background: inherit;
box-sizing: border-box;
border-radius: 4px;
border: 1px solid #C9EFF8;
text-align: center;
box-shadow: 0px 5px 5px rgba(0,0,0,0.26);
transition: 0.25s ease-out;
}
#main #login_form_wrap .login_form.expand {
width: 780px;
transition: 0.25s ease-out;
}
#main #login_form_wrap .login_form::before {
display: block;
position: absolute;
content: "";
top: -3px;
left: -3px;
right: -3px;
bottom: -3px;
background: inherit;
background-position: top center;
filter: blur(3px);
border-radius: 5px;
z-index: 0;
}
#main #login_form_wrap .login_form form {
position: relative;
margin: auto;
z-index: 1;
color: #C9EFF8;
}
#main #login_form_wrap .login_form form a {
color: #fff;
}
#main #login_form_wrap .login_form h2 {
font-size: 32px;
font-weight: 600;
margin-bottom: 16px;
transition: 0.25s ease-out;
}
#main #login_form_wrap .login_form.expand h2 {
font-size: 42px;
transition: 0.25s ease-out;
}
#main #login_form_wrap .login_form p {
margin-bottom: 16px;
transition: 0.25s ease-out;
}
#main #login_form_wrap .login_form.expand p {
opacity: 0;
margin-bottom: 0;
transition: 0.25s ease-out;
}
#main #login_form_wrap .login_form .login_form_main {
display: flex;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
margin: auto;
height: 32px;
width: 276px;
border: 1px solid #C9EFF8;
border-radius: 32px;
background-color: rgba(0,0,0,.3);
transition: 0.25s ease-out;
}
#main #login_form_wrap .login_form.expand .login_form_main {
width: 360px;
height: 42px;
transition: 0.25s ease-out;
}
#main #login_form_wrap .login_form .login_form_main.active {
box-shadow: 0px 3px 5px rgba(0,0,0,0.26);
}
#main #login_form_wrap .login_form .login_form_main input[name="acct"] {
display: block;
-webkit-appearance : none;
flex-grow: 1;
padding: 4px 0 4px 14px;
color: #C9EFF8;
border: none;
background: none;
box-sizing: border-box;
transition: 0.25s ease-out;
}
#main #login_form_wrap .login_form.expand .login_form_main input[name="acct"] {
flex-grow: 2;
padding: 8px 0 8px 14px;
transition: 0.25s ease-out;
}
#main #login_form_wrap .login_form .login_form_main .login_form_continue {
display: block;
flex-grow: 1;
color: #C9EFF8;
}
#main #login_form_wrap .login_form .login_form_main .login_form_continue:hover {
color: #fff;
}
#main #login_form_wrap .login_form .login_form_agree {
margin: 16px auto 0;
}
#main #login_form_wrap .login_form .login_form_agree i {
color: #fff;
}
/*-------------------------------------
ARTICLE
-------------------------------------*/
#article {
position: relative;
width: 640px;
margin: auto;
margin-bottom: 56px;
padding: 24px 36px 36px;
border-radius: 4px;
background-color: #fff;
box-sizing: border-box;
box-shadow: 3px 3px 5px rgba(0,0,0,0.26);
}
#article h2 {
font-size: 32px;
font-weight: 600;
margin: 16px 0;
}
#article p {
display: inline-block;
margin-bottom: 16px;
font-size: 16px;
line-height: 1.2;
}
#article .image_wrap {
display: block;
position: relative;
margin: 0 -36px 24px;
border-top: 0.5px solid #555;
border-bottom: 0.5px solid #555;
}
#article .image_wrap ul {
display: block;
position: relative;
width: 100%;
height: 370px;
overflow: hidden;
}
#article .image_wrap ul li {
position: absolute;
top: 0;
left: 0;
}
@keyframes fadeout {
0% {opacity: 1;}
100% {opacity: 0;}
}
@keyframes fadein {
0% {opacity: 0;}
100% {opacity: 1;}
}
#article .image_wrap ul li.fadeout {
animation-name: fadeout;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
#article .image_wrap ul li.fadein {
animation-name: fadein;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
#article .image_wrap .switch_button {
display: block;
opacity: 0;
position: absolute;
top: 0;
width: 30px;
height: 100%;
color: #fff;
background-color: rgba(0, 0, 0, .3);
transition: 0.15s ease-out;
}
#article .image_wrap:hover .switch_button {
opacity: 1;
transition: 0.15s ease-out;
}
@keyframes switching_image_list {
0% {background-color: rgba(0, 0, 0, .3);}
50% {background-color: rgba(0, 0, 0, .8);}
100% {background-color: rgba(0, 0, 0, .3);}
}
#article .image_wrap:hover .switch_button.active {
animation-name: switching_image_list;
animation-duration: 0.35s;
}
#article .image_wrap .switch_button.prev_button {
left: 0;
}
#article .image_wrap .switch_button.next_button {
right: 0;
}
#article a {
color: #189EFC;
}
/*-------------------------------------
FOOTER
-------------------------------------*/
#footer {
width: 640px;
padding: 24px 36px 12px;
box-sizing: border-box;
margin: auto;
border-top-right-radius: 4px;
border-top-left-radius: 4px;
box-shadow: 3px 3px 5px rgba(0,0,0,0.26);
background-color: #222;
}
#footer .footer_anchor {
display: flex;
justify-content: center;
align-items: center;
color: #444;
font-size: 36px;
transition: 0.15s ease-out;
}
#footer .footer_anchor:hover {
color: #555;
transition: 0.15s ease-out;
}
#footer span {
font-size: 14px;
color: #ddd;
margin: auto;
display: block;
text-align: center;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

View File

@ -0,0 +1,55 @@
$(function() {
$(document).on('change','.login_form_agree #agree', function(e) {
const icon = $(this).parent().find('i.fa');
if ( $(this).prop('checked') ) {
icon.addClass("fa-check-square-o");
icon.removeClass("fa-square-o");
} else if ( !$(this).prop('checked') ) {
icon.addClass("fa-square-o");
icon.removeClass("fa-check-square-o");
}
});
$(document).on('focus', '#main #login_form_wrap .login_form .login_form_main input[name="acct"]', function(e) {
$(this).parent().addClass('active')
$(".login_form").addClass('expand');
})
$(document).on('blur', '#main #login_form_wrap .login_form .login_form_main input[name="acct"]', function(e) {
$(this).parent().removeClass('active')
$(".login_form").removeClass('expand');
})
$(document).on('mousedown', '.image_wrap .switch_button', function(e) {
const self = $(this);
self.addClass('active');
setTimeout(function() {
self.removeClass('active');
}, 500);
})
$(document).on('click', '.image_wrap .prev_button', function(e) {
const firstChild = $(this).parent(".image_wrap").find('ul li:first-child');
firstChild.appendTo(".image_wrap ul");
firstChild.addClass('fadein');
setTimeout(function() {
firstChild.removeClass('fadein');
}, 500);
})
$(document).on('click', '.image_wrap .next_button', function(e) {
const lastChild = $(this).parent(".image_wrap").find('ul li:last-child');;
lastChild.addClass('fadeout');
setTimeout(function() {
lastChild.prependTo(".image_wrap ul");
lastChild.removeClass('fadeout');
}, 500);
})
})

View File

@ -0,0 +1,70 @@
<!DOCTYPE HTML>
<html lang='en'>
<head>
<script>
if(
localStorage.getItem('current_id') |
localStorage.getItem('current_instance') |
localStorage.getItem('current_authtoken')
){
location.href = '/logout';
};
</script>
<?php
#!/usr/bin/env php
#ini_set("display_errors", On);
#error_reporting(E_ALL);
require_once('../../authorize/Mastodon.php');
use HalcyonSuite\HalcyonForMastodon\Mastodon;
use Exception;
$api = new Mastodon();
if ($_GET['code']) {
$domain = htmlspecialchars((string)filter_input(INPUT_GET, 'host'), ENT_QUOTES);
$URL = 'https://'.$domain;
$api->selectInstance($URL);
$response = $api->get_access_token($api->clientWebsite.'/auth?&host='.$domain, htmlspecialchars((string)filter_input(INPUT_GET, 'code'), ENT_QUOTES));
if ($response['html']["access_token"]) {
$access_token = $response['html']["access_token"];
$account_id = $api->accounts_verify_credentials()['html']['id'];
echo "
<script>
localStorage.setItem('current_id', '$account_id');
localStorage.setItem('current_instance', '$domain');
localStorage.setItem('current_authtoken', '$access_token');
localStorage.setItem('setting_post_stream', 'manual');
localStorage.setItem('setting_post_privacy', 'public');
localStorage.setItem('setting_local_instance', 'default');
localStorage.setItem('setting_search_filter', 'all');
localStorage.setItem('what_to_follow_0', JSON.stringify({id:'',username:'Halcyon',display_name:'Halcyon for Mastodon',url:'https://mastodon.social/@Halcyon',avatar:'https://files.mastodon.social/accounts/avatars/000/132/199/original/1ca33302b092376b.png'}));
localStorage.setItem('what_to_follow_1', JSON.stringify({id:'',username:'Gargron',display_name:'Eugen',url:'https://mastodon.social/@Gargron',avatar:'https://files.mastodon.social/accounts/avatars/000/000/001/original/J3IHut1v.png'}));
localStorage.setItem('what_to_follow_2', JSON.stringify({id:'',username:'Mastodon',display_name:'Mastodon',url:'https://mastodon.social/@Mastodon',avatar:'https://files.mastodon.social/accounts/avatars/000/013/179/original/logo-41b041930be24e8039129c8ac4ff4840ef467f40c3f2d5044db50a4b15ceb285.png'}));
location.href = '/';
</script>
";
}
}
?>
<script src="//yastatic.net/jquery/3.2.1/jquery.min.js"></script>
<script src="/assets/js/mastodon.js/mastodon.js"></script>
</head>
<body>
</body>
</html>

234
public_html/login/login.php Normal file
View File

@ -0,0 +1,234 @@
<?php
#!/usr/bin/env php
#ini_set("display_errors", On);
#error_reporting(E_ALL);
require_once('../../authorize/Mastodon.php');
use HalcyonSuite\HalcyonForMastodon\Mastodon;
use Exception;
?>
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Halcyon for Mastodon</title>
<link rel="shortcut icon" href="/assets/images/favicon.ico" />
<link rel="stylesheet" type="text/css" href="/login/assets/css/style.css" media="all" />
<link rel="stylesheet" type="text/css" href="//cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" media="all" />
<script src="//yastatic.net/jquery/3.2.1/jquery.min.js"></script>
<script src="/assets/js/jquery-cookie/src/jquery.cookie.js"></script>
<script src="/login/assets/js/halcyon_login.js"></script>
<script>
if(
localStorage.getItem("current_id") |
localStorage.getItem("current_instance") |
localStorage.getItem("current_authtoken")
){
location.href = "/";
};
</script>
<?php
if (isset($_POST['acct'])) {
$domain = explode("@", mb_strtolower(htmlspecialchars((string)filter_input(INPUT_POST, 'acct'), ENT_QUOTES)))[2];
$URL = 'https://'.$domain;
$api = new Mastodon();
if ( !preg_match('/(^[a-z0-9\-\.\/]+?\.[a-z0-9-]+$)/', $domain) ) {
header('Location: '.$api->clientWebsite.'/login?cause=domain', true, 303);
die();
} else {
try {
$client_id = $api->getInstance($URL)["client_id"];
$authorizeURL = $URL.'/oauth/authorize?client_id='.$client_id.'&response_type=code&scope=read+write+follow&website='.$api->clientWebsite.'&redirect_uri='.urlencode($api->clientWebsite.'/auth?&host='.$domain);
header("Location: {$authorizeURL}", true, 303);
die();
} catch (Exception $e) {
header('Location: '.$api->clientWebsite.'/login?cause=domain', true, 303);
die();
}
}
}
?>
</head>
<body>
<!-- HEADER -->
<header id="header">
<div id="header_wrap">
<div id="header_title_wrap" class="header_box header_right_box">
<div class="header_box_child title_box">
<a href="/">
<!-- TITLE IMAGE -->
<img src="/login/assets/images/halcyon-title.png" alt="Halcyon for mastodon"/>
</a>
</div>
</div>
<div id="header_menu_wrap" class="header_box header_left_box">
<nav class="header_box_child nav_box">
<ul>
<!-- NEWS-->
<!--<a href="https://mastodon.social/@halcyon" class="no-underline">
<li>
<span><i class="fa fa-newspaper-o" aria-hidden="true"></i>News</span>
</li>
</a>-->
<!-- SOURCE-->
<a href="https://github.com/halcyon-suite/halcyon" class="no-underline">
<li>
<span><i class="fa fa-code" aria-hidden="true"></i>Source</span>
</li>
</a>
<!-- TERMS -->
<a class="no-underline">
<li>
<span><i class="fa fa-balance-scale" aria-hidden="true"></i>Terms</span>
</li>
</a>
<!-- CONTACT -->
<a href="http://www.nikisoft.one/contact.php" class="no-underline">
<li>
<span><i class="fa fa-envelope" aria-hidden="true"></i>Contact</span>
</li>
</a>
<!-- SIGN IN -->
<a href="#login_form_wrap" class="no-underline">
<li>
<span><i class="fa fa-user-circle-o" aria-hidden="true"></i>Login</span>
</li>
</a>
</ul>
</nav>
</div>
</div>
</header>
<!-- MAIN -->
<main id="main">
<div id="login_form_wrap">
<div class="login_form">
<form method="POST" >
<h2>Login to Halcyon</h2>
<p>
or <a href="https://joinmastodon.org/">create an account</a>
</p>
<div class="session_aleart">
<span></span>
</div>
<div class="login_form_main">
<input name="acct" type="text" class="login_form_input" placeholder="@halcyon@mastodon.social" required/>
<label class="login_form_continue pointer">
<i class="fa fa-chevron-circle-right" aria-hidden="true"></i>
<input id="login_continue" type="submit" value="" class="invisible"></input>
</label>
</div>
<div class="login_form_agree">
<label class="login_form_agree_check disallow_select pointer">
<i class="fa fa-check-square-o" aria-hidden="true"></i>
I agree with the <a href="/terms">Terms</a>
<input id="agree" type="checkbox" required checked class="invisible"/>
</label>
</div>
</form>
</div>
</div>
<article id="article">
<h2>What is Halcyon</h2>
<p>
Halcyon is standard <span style="font-weight: bold">Twitter like client</span> of Mastodon, And you can use it just by login to your instance. Let's Toot like a tweet.
</p>
<div class="image_wrap">
<ul>
<li><img src="/login/assets/images/preview2.png" alt="halcyon_screenshot"/></li>
<li><img src="/login/assets/images/preview1.png" alt="halcyon_screenshot"/></li>
<li><img src="/login/assets/images/preview0.png" alt="halcyon_screenshot"/></li>
</ul>
<button class="prev_button switch_button"><i class="fa fa-angle-left" aria-hidden="true"></i></button>
<button class="next_button switch_button"><i class="fa fa-angle-right" aria-hidden="true"></i></button>
</div>
<h2>Contact / Feedback</h2>
<p>
Mastodon: <a href="http://social.wiuwiu.de/@nipos" target="_blank">nipos@social.wiuwiu.de</a><br />
Email: <a href="http://www.nikisoft.one/contact.php" target="_blank">Use my contact form</a><br />
Github: <a href="https://github.com/halcyon-suite" target="_blank">/halcyon-suite</a>
</p>
<h2>Help us</h2>
<p>
Bitcoin: 1D6GThQqHQYnruKYrKyW9JC86ZGWxjt1hK<br />
</p>
</article>
</main>
<!-- FOOTER -->
<footer id="footer">
<div class="footer_anchor">
<a href="#">
<i class="fa fa-angle-up" aria-hidden="true"></i>
</a>
</div>
<span>Photo by <a href="https://www.flickr.com/photos/95387826@N08/">Michio Morimoto on Flickr</a> (CC BY 2.0)</span>
</footer>
</body>
<?php if (isset($_GET['cause'])): ?>
<script>
$(function() {
var cause = "<?= htmlspecialchars((string)filter_input(INPUT_GET, 'cause'), ENT_QUOTES) ?>";
if ( cause === "domain" ) {
$('.login_form_main').addClass('error');
$('.session_aleart').removeClass('invisible');
$('.session_aleart > span').text('This instance does not exsist.');
}
});
$(document).on('click','.login_form_main', function(e) {
$(this).removeClass('error');
});
</script>
<?php endif; ?>
</html>

View File

@ -0,0 +1,3 @@
<script src="//yastatic.net/jquery/3.2.1/jquery.min.js"></script>
<script src="/assets/js/jquery-cookie/src/jquery.cookie.js"></script>
<script>localStorage.clear();$.removeCookie('session');location.href="/login";</script>

128
public_html/login/terms.php Normal file
View File

@ -0,0 +1,128 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Halcyon for Mastodon</title>
<link rel="shortcut icon" href="/assets/images/favicon.ico" />
<link rel="stylesheet" type="text/css" href="/login/assets/css/style.css" media="all" />
<link rel="stylesheet" type="text/css" href="//cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" media="all" />
<script src="//yastatic.net/jquery/3.2.1/jquery.min.js"></script>
<script src="/assets/js/jquery-cookie/src/jquery.cookie.js"></script>
<script src="/login/assets/js/halcyon_login.js"></script>
</head>
<body>
<header id="header">
<div id="header_wrap">
<div id="header_title_wrap" class="header_box header_right_box">
<div class="header_box_child title_box">
<a href="/">
<!-- TITLE IMAGE -->
<img src="/login/assets/images/halcyon-title.png" alt="Halcyon for mastodon"/>
</a>
</div>
</div>
<div id="header_menu_wrap" class="header_box header_left_box">
<nav class="header_box_child nav_box">
<ul>
<!-- NEWS-->
<!--<a href="https://mastodon.social/@halcyon" class="no-underline">
<li>
<span><i class="fa fa-newspaper-o" aria-hidden="true"></i>News</span>
</li>
</a>-->
<!-- SOURCE-->
<a href="https://github.com/halcyon-suite/halcyon" class="no-underline">
<li>
<span><i class="fa fa-code" aria-hidden="true"></i>Source</span>
</li>
</a>
<!-- TERMS -->
<a class="no-underline">
<li>
<span><i class="fa fa-balance-scale" aria-hidden="true"></i>Terms</span>
</li>
</a>
<!-- CONTACT -->
<a href="http://www.nikisoft.one/contact.php" class="no-underline">
<li>
<span><i class="fa fa-envelope" aria-hidden="true"></i>Contact</span>
</li>
</a>
<!-- SIGN IN -->
<a href="#login_form_wrap" class="no-underline">
<li>
<span><i class="fa fa-user-circle-o" aria-hidden="true"></i>Login</span>
</li>
</a>
</ul>
</nav>
</div>
</div>
</header>
<main id="main">
<article id="article">
<h1>Halcyon Terms of Use</h1>
<p class="description">This terms of use agreement is for the users of web service Halcyon for Mastodon (Halcyon for short) hosted at <a href="https://halcyon.social">halcyon.social</a>.</p>
<br/>
<h2>1. Agreement</h2>
<p>By logging into Halcyon you agree to this terms of use agreement. Do not use this service if you do not agree to these terms.</p>
<br/>
<h2>2. Change</h2>
<p>We reserve the right to modify these terms of use at any time if deemed necessary.</p>
<br/>
<h2>3. Account</h2>
<p>Users can connect to various Mastodon accounts from Halcyon, and Halcyon will not interfere with those connection.</p>
<br/>
<h2>4. Content</h2>
<p>Halcyon does not own the right to or hold responsibility for any text, image, animation, audio content accessible from Halcyon. The right and responsibility belong to the owners of instances that host those content.</p>
<br/>
<h2>5. Guideline</h2>
<p>Halcyon reserves the right to terminate service for</p>
<ul>
<li>actions violating the laws of countries</li>
<li>actions contrary to public order and standards of decency</li>
<li>violation of third partys right to intellectual property, brand, privacy, etc.</li>
<li>acts of violence, sexual nature, of discrimination</li>
<li>phishing or spamming</li>
<li>actions causing problem to the network infrastructure of Halcyon</li>
</ul>
<br/>
<h2>6. Disclaimer</h2>
<p>Halcyon is not responsible for any damage caused by using this service.</p>
<br/>
<p>Last updated 2017/5/12</p>
</article>
</main>
<footer id="footer">
<div class="footer_anchor">
<a href="#">
<i class="fa fa-angle-up" aria-hidden="true"></i>
</a>
</div>
<span>Photo by <a href="https://www.flickr.com/photos/95387826@N08/">Michio Morimoto on Flickr</a> (CC BY 2.0)</span>
</footer>
</body>

View File

@ -0,0 +1,69 @@
<?php include ('header.php'); ?>
<!-- MAIN -->
<main id="main">
<div class="article_wrap">
<aside class="left_column">
</aside>
<article class="center_column">
<header class="timeline_header">
<ul class="header_items">
<li class="item toots view">
<a href="#">
All
</a>
</li>
</ul>
</header>
<div id="js-stream_update">
<button>
View <span></span> new notiotification
</button>
</div>
<ul id="js-timeline" class="timeline">
</ul>
<footer id="js-timeline_footer" class="timeline_footer">
<i class="fa fa-spin fa-circle-o-notch" aria-hidden="true"></i>
</footer>
</article>
<aside class="right_column">
<section class="side_widgets_wrap">
<?php include dirname(__FILE__).('/widgets/side_what_to_follow.php'); ?>
</section>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</aside>
</div>
</main>
<script>
// namespace
current_file = location.pathname;
$("#notifications_nav").addClass('view');
// reset count
localStorage.setItem("notification_count", 0);
setNotifications();
$('title').text('Halcyon / Notifications')
</script>
<?php include ('footer.php'); ?>

View File

@ -0,0 +1,80 @@
<?php include ('header.php'); ?>
<!-- MAIN -->
<main id="main">
<?php include dirname(__FILE__).('/widgets/search_header.php'); ?>
<div class="article_wrap">
<aside class="left_column">
<?php include dirname(__FILE__).('/widgets/side_load_options.php'); ?>
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</aside>
<article class="center_column">
<header class="timeline_header">
<ul class="header_items">
<li class="item toots view">
</li>
</ul>
</header>
<div id="js-stream_update">
<button>
View <span></span> new Toots
</button>
</div>
<ul id="js-timeline" class="timeline">
</ul>
<footer id="js-timeline_footer" class="timeline_footer">
<i class="fa fa-spin fa-circle-o-notch" aria-hidden="true"></i>
</footer>
</article>
<aside class="right_column"></aside>
</div>
</main>
<script>
// namespace
current_file = location.pathname;
<?php if (isset($_GET['q'])): ?>
$(function() {
// find by mid
const query = "<?= htmlspecialchars((string)filter_input(INPUT_GET, 'q'), ENT_QUOTES) ?>";
$('#main > .article_wrap > .center_column > .timeline_header > .header_items > .item').text("#"+query);
$('#js-search_title_box > h1').text(query);
$('title').text('#'+query+' - Halcyon Search');
$('#js-search_nav_toots').toggleClass('view');
$('#js-search_nav_toots a ').attr('href','/search'+location.search);
$('#js-search_nav_peoples a ').attr('href','/search/users'+location.search)
if ( localStorage.getItem("setting_search_filter") === "all" ) {
setTimeline("timelines/tag/"+query);
} else if ( localStorage.getItem("setting_search_filter") === "local" ) {
setTimeline("timelines/tag/"+query, [{name:"local",data:"ture"}]);
}
replace_emoji();
});
<?php endif; ?>
</script>
<?php include ('footer.php'); ?>

View File

@ -0,0 +1,51 @@
<?php include ('header.php'); ?>
<!-- MAIN -->
<main id="main">
<?php include dirname(__FILE__).('/widgets/search_header.php'); ?>
<div class="article_wrap">
<aside class="left_column">
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</aside>
<article class="center_column">
<div id="js-follows_profile">
</div>
<footer id="js-follows_footer">
<i class="fa fa-spin fa-circle-o-notch" aria-hidden="true"></i>
</footer>
</article>
</div>
</main>
<script>
// namespace
current_file = location.pathname;
<?php if(isset($_GET['q'])): ?>
const query = "<?= htmlspecialchars((string)filter_input(INPUT_GET, 'q'), ENT_QUOTES) ?>";
$('title').text(query+' - Halcyon Search');
$('#js-search_title_box > h1').text(query);
$('#js-search_nav_peoples').toggleClass('view');
$('#js-search_nav_toots a ').attr('href','/search'+location.search);
$('#js-search_nav_peoples a ').attr('href','/search/users'+location.search);
setUserSearch(query);
<?php endif; ?>
</script>
<?php include ('footer.php'); ?>

158
public_html/user.php Normal file
View File

@ -0,0 +1,158 @@
<?php include ('header.php'); ?>
<!-- MAIN -->
<main id="main">
<?php include dirname(__FILE__).('/widgets/user_header.php'); ?>
<div class="article_wrap">
<aside class="left_column">
<!-- Profile avatar -->
<div class="profile_icon_box">
<img id="js_profile_image" src="/assets/images/missing.png" mediaaccess="true"/>
</div>
<!-- Display/username, bio-->
<section class="profile_section_wrap">
<h1 class="profile_displayname">
<a id="js_profile_displayname" class="emoji_poss" href="#"></a>
</h1>
<h2 class="profile_username">
@<a id="js_profile_username" href="#"></a><span class="profile_followed_by invisible">FOLLOWS YOU</span>
</h2>
<p id="js_profile_bio" class="profile_bio emoji_poss"></p>
<?php include dirname(__FILE__).('/widgets/user_recent_images.php'); ?>
</section>
<div class="right_column_clone">
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</div>
</aside>
<article class="center_column">
<!-- TIMELINE HEADER -->
<header class="timeline_header">
<ul class="header_items">
<!-- TOOT -->
<li class="item toots view">
<a id="toots_link">
Toots
</a>
</li>
<!--- TOOTS & REPLIES -->
<li class="item wreplies">
<a id="with_replies_link">
Toots &amp; replies
</a>
</li>
<!-- MEDIA -->
<li class="item media">
<a id="media_link">
Media
</a>
</li>
</ul>
</header>
<!-- UPDATE BUTTON -->
<div id="js-stream_update">
<button>
View <span></span> new Toots
</button>
</div>
<!-- TIMELINE -->
<ul id="js-timeline" class="timeline">
</ul>
<!-- TIMELINE FOOTER -->
<footer id="js-timeline_footer" class="timeline_footer">
<i class="fa fa-spin fa-circle-o-notch" aria-hidden="true"></i>
</footer>
</article>
<aside class="right_column">
<section class="side_widgets_wrap">
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
</section>
<!-- FOOTER -->
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</aside>
</div>
</main>
<script>
// namespace
current_file = "/user";
$("#toots_link").attr('href', location.pathname+location.search);
$("#with_replies_link").attr('href', location.pathname+'/with_replies'+location.search);
$("#media_link").attr('href', location.pathname+'/media'+location.search);
$("#js-profile_nav_toots").toggleClass("view");
$("#js-profile_nav_toots > a").attr('href', location.pathname+location.search);
$("#js-profile_nav_following > a").attr('href', location.pathname+'/following'+location.search);
$("#js-profile_nav_followers > a").attr('href', location.pathname+'/followers'+location.search);
$("#js-profile_nav_favourites > a").attr('href', location.pathname+'/favourites'+location.search);
<?php if (isset($_GET['mid'])): ?>
$(function() {
// find by mid
const account_id = <?= htmlspecialchars((string)filter_input(INPUT_GET, 'mid'), ENT_QUOTES) ?>;
api.get('accounts/'+account_id, function(userprofile) {
if ( userprofile !== null ) {
$('title').text(replaced_emoji_return(userprofile.display_name)+' (@'+userprofile.acct+') | Halcyon');
setAccount(userprofile);
setTimeline("accounts/"+userprofile.id+"/statuses",[{name:'exclude_replies',data:'true'}]);
setRecentImages(userprofile.id)
} else {
location.href = "/404.php";
}
});
});
<?php elseif((isset($_GET['user']))): ?>
$(function(){
// find by url
<?php
$name = preg_split("/@/", $_GET['user'])[1];
$domain = preg_split("/@/", $_GET['user'])[2];
$url = "https://$domain/@$name";
?>
const query = '<?= htmlspecialchars((string)filter_input(INPUT_GET, 'user'), ENT_QUOTES) ?>';
api.get('search', [{name:'q',data:query},{name:'resolve',data:'true'}], function(search) {
if ( !search.accounts.length ) {
location.href = "/404.php";
} else if ( "@"+search.accounts[0].acct === query ) {
$('title').text(replaced_emoji_return(search.accounts[0].display_name)+' (@'+search.accounts[0].acct+') | Halcyon');
setAccount(search.accounts[0]);
setTimeline("accounts/"+search.accounts[0].id+"/statuses",[{name:'exclude_replies',data:'true'}]);
setRecentImages(search.accounts[0].id)
} else {
location.href = "/404.php";
}
});
})
<?php endif; ?>
</script>
<?php include ('footer.php'); ?>

View File

@ -0,0 +1,147 @@
<?php include ('header.php'); ?>
<!-- MAIN -->
<main id="main">
<?php include dirname(__FILE__).('/widgets/user_header.php'); ?>
<div class="article_wrap">
<aside class="left_column">
<div class="profile_icon_box">
<img id="js_profile_image" src="/assets/images/missing.png" mediaaccess="true"/>
</div>
<section class="profile_section_wrap">
<h1 class="profile_displayname">
<a id="js_profile_displayname" class="emoji_poss" href="#"></a>
</h1>
<h2 class="profile_username">
@<a id="js_profile_username" href="#"></a>
</h2>
<p id="js_profile_bio" class="profile_bio emoji_poss"></p>
<?php include dirname(__FILE__).('/widgets/user_recent_images.php'); ?>
</section>
<div class="right_column_clone">
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</div>
</aside>
<article class="center_column">
<header class="timeline_header">
<ul class="header_items">
<li class="item toots view">
<a id="toots_link">
Favourites
</a>
</li>
</ul>
</header>
<div id="js-stream_update">
<button>
View <span></span> new Toots
</button>
</div>
<ul id="js-timeline" class="timeline">
</ul>
<footer id="js-timeline_footer" class="timeline_footer">
<i class="fa fa-spin fa-circle-o-notch" aria-hidden="true"></i>
</footer>
</article>
<aside class="right_column">
<section class="side_widgets_wrap">
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
</section>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</aside>
</div>
</main>
<script>
// namespace
current_file = location.pathname;
$("#js-profile_nav_favourites").toggleClass("view");
$("#js-profile_nav_toots > a").attr('href', './'+location.search);
$("#js-profile_nav_following > a").attr('href', 'following'+location.search);
$("#js-profile_nav_followers > a").attr('href', 'followers'+location.search);
$("#js-profile_nav_favourites > a").attr('href', 'favourites'+location.search);
<?php if (isset($_GET['mid'])): ?>
$(function() {
// find by mid
const account_id = <?php echo $_GET['mid']; ?>;
api.get('accounts/'+account_id, function(AccountObj) {
if ( AccountObj !== null ) {
setAccount(AccountObj);
setTimeline("favourites");
setRecentImages(AccountObj.id);
} else {
location.href = "/404.php";
}
});
});
<?php elseif((isset($_GET['user']))): ?>
$(function(){
// find by url
<?php
$name = preg_split("/@/", $_GET['user'])[1];
$domain = preg_split("/@/", $_GET['user'])[2];
$url = "https://$domain/@$name";
?>
const query = '<?= htmlspecialchars((string)filter_input(INPUT_GET, 'user'), ENT_QUOTES) ?>';
api.get('search', [{name:'q',data:query},{name:'resolve',data:'true'}], function(search) {
if ( !search.accounts.length ) {
location.href="/404.php";
} else if ( "@"+search.accounts[0].acct === query ) {
setAccount(search.accounts[0]);
setTimeline("favourites");
setRecentImages(search.accounts[0]);
} else {
location.href="/404.php";
}
});
})
<?php endif; ?>
</script>
<?php include ('footer.php'); ?>

View File

@ -0,0 +1,115 @@
<?php include ('header.php'); ?>
<!-- MAIN -->
<main id="main">
<?php include dirname(__FILE__).('/widgets/user_header.php'); ?>
<div class="article_wrap">
<aside class="left_column">
<div class="profile_icon_box">
<img id="js_profile_image" src="/assets/images/missing.png" mediaaccess="true"/>
</div>
<section class="profile_section_wrap">
<h1 class="profile_displayname">
<a id="js_profile_displayname" class="emoji_poss" href="#"></a>
</h1>
<h2 class="profile_username">
@<a id="js_profile_username" href="#"></a>
</h2>
<p id="js_profile_bio" class="profile_bio emoji_poss"></p>
<?php include dirname(__FILE__).('/widgets/user_recent_images.php'); ?>
</section>
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</aside>
<article class="center_column">
<div id="js-follows_profile">
</div>
<footer id="js-follows_footer">
<i class="fa fa-spin fa-circle-o-notch" aria-hidden="true"></i>
</footer>
</article>
</div>
</main>
<script>
// namespace
current_file = location.pathname;
$("#js-profile_nav_followers").toggleClass("view");
$("#js-profile_nav_toots > a").attr('href', './'+location.search);
$("#js-profile_nav_following > a").attr('href', 'following'+location.search);
$("#js-profile_nav_followers > a").attr('href', 'followers'+location.search);
$("#js-profile_nav_favourites > a").attr('href','favourites'+location.search);
<?php if (isset($_GET['mid'])): ?>
$(function() {
// find by mid
const account_id = <?= htmlspecialchars((string)filter_input(INPUT_GET, 'mid'), ENT_QUOTES) ?>;
api.get('accounts/'+account_id, function(AccountObj) {
if ( AccountObj !== null ) {
setAccount(AccountObj);
setFollows(account_id,'followers',[{name:'limit',data:18}]);
setRecentImages(AccountObj.id);
} else {
location.href="/404.php";
}
});
});
<?php elseif((isset($_GET['user']))): ?>
$(function(){
// find by url
<?php
$name = preg_split("/@/", $_GET['user'])[1];
$domain = preg_split("/@/", $_GET['user'])[2];
$url = "https://$domain/@$name";
?>
const query = '<?= htmlspecialchars((string)filter_input(INPUT_GET, 'user'), ENT_QUOTES) ?>';
api.get('search', [{name:'q',data:query},{name:'resolve',data:'true'}], function(search) {
if ( !search.accounts.length ) {
location.href="/404.php";
} else if ( "@"+search.accounts[0].acct === query ) {
setAccount(search.accounts[0]);
setFollows(search.accounts[0].id,'followers',[{name:'limit',data:18}]);
setRecentImages(search.accounts[0].id);
} else {
location.href="/404.php";
}
});
})
<?php endif; ?>
</script>
<?php include ('footer.php'); ?>

View File

@ -0,0 +1,115 @@
<?php include ('header.php'); ?>
<!-- MAIN -->
<main id="main">
<?php include dirname(__FILE__).('/widgets/user_header.php'); ?>
<div class="article_wrap">
<aside class="left_column">
<div class="profile_icon_box">
<img id="js_profile_image" src="/assets/images/missing.png" mediaaccess="true"/>
</div>
<section class="profile_section_wrap">
<h1 class="profile_displayname">
<a id="js_profile_displayname" class="emoji_poss" href="#"></a>
</h1>
<h2 class="profile_username">
@<a id="js_profile_username" href="#"></a>
</h2>
<p id="js_profile_bio" class="profile_bio emoji_poss"></p>
<?php include dirname(__FILE__).('/widgets/user_recent_images.php'); ?>
</section>
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</aside>
<article class="center_column">
<div id="js-follows_profile">
</div>
<footer id="js-follows_footer">
<i class="fa fa-spin fa-circle-o-notch" aria-hidden="true"></i>
</footer>
</article>
</div>
</main>
<script>
// namespace
current_file = location.pathname;
$("#js-profile_nav_following").toggleClass("view");
$("#js-profile_nav_toots > a").attr('href', './'+location.search);
$("#js-profile_nav_following > a").attr('href', 'following'+location.search);
$("#js-profile_nav_followers > a").attr('href', 'followers'+location.search);
$("#js-profile_nav_favourites > a").attr('href','favourites'+location.search);
<?php if (isset($_GET['mid'])): ?>
$(function() {
// find by mid
const account_id = <?= htmlspecialchars((string)filter_input(INPUT_GET, 'mid'), ENT_QUOTES) ?>;
api.get('accounts/'+account_id, function(AccountObj) {
if ( AccountObj !== null ) {
setAccount(AccountObj);
setFollows(account_id,'following',[{name:'limit',data:18}]);
setRecentImages(AccountObj.id);
} else {
location.href="/404.php";
}
});
});
<?php elseif((isset($_GET['user']))): ?>
$(function(){
// find by url
<?php
$name = preg_split("/@/", $_GET['user'])[1];
$domain = preg_split("/@/", $_GET['user'])[2];
$url = "https://$domain/@$name";
?>
const query = '<?= htmlspecialchars((string)filter_input(INPUT_GET, 'user'), ENT_QUOTES) ?>';
api.get('search', [{name:'q',data:query},{name:'resolve',data:'true'}], function(search) {
if ( !search.accounts.length ) {
location.href="/404.php";
} else if ( "@"+search.accounts[0].acct === query ) {
setAccount(search.accounts[0]);
setFollows(search.accounts[0].id,'following',[{name:'limit',data:18}]);
setRecentImages(search.accounts[0].id);
} else {
location.href="/404.php";
}
});
})
<?php endif; ?>
</script>
<?php include ('footer.php'); ?>

View File

@ -0,0 +1,159 @@
<?php include ('header.php'); ?>
<!-- MAIN -->
<main id="main">
<?php include dirname(__FILE__).('/widgets/user_header.php'); ?>
<div class="article_wrap">
<aside class="left_column">
<div class="profile_icon_box">
<img id="js_profile_image" src="/assets/images/missing.png" mediaaccess="true"/>
</div>
<section class="profile_section_wrap">
<h1 class="profile_displayname">
<a id="js_profile_displayname" class="emoji_poss" href="#"></a>
</h1>
<h2 class="profile_username">
@<a id="js_profile_username" href="#"></a>
</h2>
<p id="js_profile_bio" class="profile_bio emoji_poss"></p>
<?php include dirname(__FILE__).('/widgets/user_recent_images.php'); ?>
</section>
<div class="right_column_clone">
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</div>
</aside>
<article class="center_column">
<header class="timeline_header">
<ul class="header_items">
<li class="item toots">
<a id="toots_link">
Toots
</a>
</li>
<li class="item wreplies view">
<a id="with_replies_link">
Toots &amp; replies
</a>
</li>
<li class="item media">
<a id="media_link">
Media
</a>
</li>
</ul>
</header>
<div id="js-stream_update">
<button>
View <span></span> new Toots
</button>
</div>
<ul id="js-timeline" class="timeline">
</ul>
<footer id="js-timeline_footer" class="timeline_footer">
<i class="fa fa-spin fa-circle-o-notch" aria-hidden="true"></i>
</footer>
</article>
<aside class="right_column">
<section class="side_widgets_wrap">
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
</section>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</aside>
</div>
</main>
<script>
// namespace
current_file = location.pathname;
$("#toots_link").attr('href', './'+location.search);
$("#with_replies_link").attr('href', './with_replies'+location.search);
$("#media_link").attr('href', './media'+location.search);
$("#js-profile_nav_toots > a").toggleClass("view");
$("#js-profile_nav_toots > a").attr('href', location.pathname+location.search);
$("#js-profile_nav_following > a").attr('href', './following'+location.search);
$("#js-profile_nav_followers > a").attr('href', './followers'+location.search);
$("#js-profile_nav_favourites > a").attr('href', './favourites'+location.search);
<?php if (isset($_GET['mid'])): ?>
$(function() {
// find by mid
const account_id = <?= htmlspecialchars((string)filter_input(INPUT_GET, 'mid'), ENT_QUOTES) ?>;
api.get('accounts/'+account_id, function(AccountObj) {
if ( AccountObj !== null ) {
setAccount(AccountObj);
setTimeline("accounts/"+AccountObj.id+"/statuses");
setRecentImages(AccountObj.id);
} else {
location.href = "/404.php";
}
});
});
<?php elseif((isset($_GET['user']))): ?>
$(function(){
// find by url
<?php
$name = preg_split("/@/", $_GET['user'])[1];
$domain = preg_split("/@/", $_GET['user'])[2];
$url = "https://$domain/@$name";
?>
const query = '<?= htmlspecialchars((string)filter_input(INPUT_GET, 'user'), ENT_QUOTES) ?>';
api.get('search', [{name:'q',data:query},{name:'resolve',data:'true'}], function(search) {
if ( !search.accounts.length ) {
location.href = "/404.php";
} else if ( "@"+search.accounts[0].acct === query ) {
setAccount(search.accounts[0]);
setTimeline("accounts/"+search.accounts[0].id+"/statuses");
setRecentImages(search.accounts[0].id);
} else {
location.href = "/404.php";
}
});
})
<?php endif; ?>
</script>
<?php include ('footer.php'); ?>

View File

@ -0,0 +1,160 @@
<?php include ('header.php'); ?>
<!-- MAIN -->
<main id="main">
<?php include dirname(__FILE__).('/widgets/user_header.php'); ?>
<div class="article_wrap">
<aside class="left_column">
<div class="profile_icon_box">
<img id="js_profile_image" src="/assets/images/missing.png" mediaaccess="true"/>
</div>
<section class="profile_section_wrap">
<h1 class="profile_displayname">
<a id="js_profile_displayname" class="emoji_poss" href="#"></a>
</h1>
<h2 class="profile_username">
@<a id="js_profile_username" href="#"></a>
</h2>
<p id="js_profile_bio" class="profile_bio emoji_poss"></p>
<?php include dirname(__FILE__).('/widgets/user_recent_images.php'); ?>
</section>
<div class="right_column_clone">
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</div>
</aside>
<article class="center_column">
<header class="timeline_header">
<ul class="header_items">
<li class="item toots">
<a id="toots_link">
Toots
</a>
</li>
<li class="item wreplies">
<a id="with_replies_link">
Toots &amp; replies
</a>
</li>
<li class="item media view">
<a id="media_link">
Media
</a>
</li>
</ul>
</header>
<div id="js-stream_update">
<button>
View <span></span> new Toots
</button>
</div>
<ul id="js-timeline" class="timeline">
</ul>
<footer id="js-timeline_footer" class="timeline_footer">
<i class="fa fa-spin fa-circle-o-notch" aria-hidden="true"></i>
</footer>
</article>
<aside class="right_column">
<section class="side_widgets_wrap">
<?php include dirname(__FILE__).('/widgets/side_who_to_follow.php'); ?>
</section>
<?php include dirname(__FILE__).('/widgets/side_footer.php'); ?>
</aside>
</div>
</main>
<script>
// namespace
current_file = location.pathname;
$("#toots_link").attr('href', './'+location.search);
$("#with_replies_link").attr('href', './with_replies'+location.search);
$("#media_link").attr('href', './media'+location.search);
$("#js-profile_nav_toots > a").toggleClass("view");
$("#js-profile_nav_toots > a").attr('href', location.pathname+location.search);
$("#js-profile_nav_following > a").attr('href', './following'+location.search);
$("#js-profile_nav_followers > a").attr('href', './followers'+location.search);
$("#js-profile_nav_favourites > a").attr('href', './favourites'+location.search);
<?php if (isset($_GET['mid'])): ?>
$(function() {
// find by mid
const account_id = <?= htmlspecialchars((string)filter_input(INPUT_GET, 'mid'), ENT_QUOTES) ?>;
api.get('accounts/'+account_id, function(AccountObj) {
if ( AccountObj !== null ) {
setAccount(AccountObj);
setTimeline("accounts/"+AccountObj.id+"/statuses",[{name:'only_media',data:'true'}]);
setRecentImages(AccountObj.id);
} else {
location.href = "/404.php";
}
});
});
<?php elseif((isset($_GET['user']))): ?>
$(function(){
// find by url
<?php
$name = preg_split("/@/", $_GET['user'])[1];
$domain = preg_split("/@/", $_GET['user'])[2];
$url = "https://$domain/@$name";
?>
const query = '<?= htmlspecialchars((string)filter_input(INPUT_GET, 'user'), ENT_QUOTES) ?>';
api.get('search', [{name:'q',data:query},{name:'resolve',data:'true'}], function(search) {
if ( !search.accounts.length ) {
location.href="/404.php";
} else if ( "@"+search.accounts[0].acct === query ) {
setAccount(search.accounts[0]);
setTimeline("accounts/"+search.accounts[0].id+"/statuses",[{name:'only_media',data:'true'}]);
setRecentImages(search.accounts[0].id);
} else {
location.href="/404.php";
}
});
})
<?php endif; ?>
</script>
<?php include ('footer.php'); ?>

View File

@ -0,0 +1,101 @@
<form id="header_status_form" name="header_status_form" class="status_form ready">
<div class="status_top">
<input class="status_spoiler invisible" name="status_spoiler" placeholder="Content warning" type="text"/>
</div>
<div class="status_main">
<!-- current avatar -->
<div class="icon_box">
<img class="js_current_profile_image" />
</div>
<!-- text area -->
<div class="status_textarea">
<textarea class="emoji_poss" name="status_textarea" placeholder="What's happening?"></textarea>
<div class="media_attachments_preview_area invisible"></div>
</div>
</div>
<div class="status_bottom invisible">
<!-- Media Attachment -->
<label for="header_status_media_atta" class="status_media_attachment status_option_button">
<i class="fa fa-camera" aria-hidden="true"></i>
</label>
<!-- Content warning -->
<label for="header_status_cw" class="status_CW status_option_button">
<span class="disallow_select">CW</span>
</label>
<!-- Not safe for work -->
<label for="header_status_nsfw" class="status_NSFW status_option_button">
<span class="disallow_select">NSFW</span>
</label>
<!-- Privacy options -->
<div class="status_privacy status_option_button expand_privacy_menu_button">
<!-- Expand menu -->
<i class="fa fa-globe" aria-hidden="true"></i>
<!-- Privacy options -->
<div class="expand_privacy_menu invisible">
<label for="header_status_public" class="status_privacy select_privacy disallow_select" privacyicon="fa fa-globe">
<i class="fa fa-globe" aria-hidden="true"></i>Public
</label>
<label for="header_status_unlisted" class="status_privacy select_privacy disallow_select" privacyicon="fa fa-unlock-alt">
<i class="fa fa-unlock-alt" aria-hidden="true"></i>Unlisted
</label>
<label for="header_status_fonly" class="status_privacy select_privacy disallow_select" privacyicon="fa fa-lock">
<i class="fa fa-lock" aria-hidden="true"></i>Followers-only
</label>
<label for="header_status_direct" class="status_privacy select_privacy disallow_select" privacyicon="fa fa-envelope">
<i class="fa fa-envelope" aria-hidden="true"></i>Direct
</label>
</div>
</div>
<input id="header_status_media_atta" name="files" type="file" multiple class="invisible"/>
<input id="header_status_cw" name="status_cw" type="checkbox" class="invisible" />
<input id="header_status_nsfw" name="status_nsfw" type="checkbox" class="invisible" />
<input id="header_status_public" name='privacy_option' value="public" class="invisible" type="radio" />
<input id="header_status_unlisted" name='privacy_option' value="unlisted" class="invisible" type="radio" />
<input id="header_status_fonly" name='privacy_option' value="private" class="invisible" type="radio" />
<input id="header_status_direct" name='privacy_option' value="direct" class="invisible" type="radio" />
<div class="submit_status_label_wrap">
<span class="character_count">
512
</span>
<!-- Submit -->
<label for="header_status_form_submit" class="submit_status_label">
<div class="toot_button_label disallow_select" >
<i class="fa fa-fw fa-pencil-square-o"></i>
<span>Toot</span>
</div>
</label>
</div>
<input id="header_status_form_submit" class="submit_status" type="button" class="invisible"/>
</div>
</form>

View File

@ -0,0 +1,11 @@
<div class="overlay_copy_link invisible">
<header class="overlay_copy_link_header">
<span class="emoji_poss">Copy link to Toot</span>
</header>
<div class="overlay_copy_link_form">
<input type="text" value="" readonly/>
</div>
</div>

View File

@ -0,0 +1,104 @@
<div class="overlay_status invisible">
<header class="overlay_status_header">
<span>Compose new Toot</span>
</header>
<form id="overlay_status_form" name="overlay_status_form" class="status_form ready">
<div class="status_top">
<input class="status_spoiler invisible" name="status_spoiler" placeholder="Content warning" type="text"/>
</div>
<div class="status_main">
<!-- text area -->
<div class="status_textarea">
<textarea class="emoji_poss" name="status_textarea" placeholder="What's happening?"></textarea>
<div class="media_attachments_preview_area invisible"></div>
</div>
</div>
<div class="status_bottom">
<!-- Media Attachment -->
<label for="overlay_status_media_atta" class="status_media_attachment status_option_button">
<i class="fa fa-camera" aria-hidden="true"></i>
</label>
<!-- Content warning -->
<label for="overlay_status_cw" class="status_CW status_option_button">
<span class="disallow_select">CW</span>
</label>
<!-- Not safe for work -->
<label for="overlay_status_nsfw" class="status_NSFW status_option_button">
<span class="disallow_select">NSFW</span>
</label>
<!-- Privacy options -->
<div class="status_privacy status_option_button expand_privacy_menu_button">
<!-- Expand menu -->
<i class="fa fa-globe" aria-hidden="true"></i>
<!-- Privacy options -->
<div class="expand_privacy_menu invisible">
<label for="overlay_status_public" class="status_privacy select_privacy disallow_select" privacyicon="fa fa-globe">
<i class="fa fa-globe" aria-hidden="true"></i>Public
</label>
<label for="overlay_status_unlisted" class="status_privacy select_privacy disallow_select" privacyicon="fa fa-unlock-alt">
<i class="fa fa-unlock-alt" aria-hidden="true"></i>Unlisted
</label>
<label for="overlay_status_fonly" class="status_privacy select_privacy disallow_select" privacyicon="fa fa-lock">
<i class="fa fa-lock" aria-hidden="true"></i>Followers-only
</label>
<label for="overlay_status_direct" class="status_privacy select_privacy disallow_select" privacyicon="fa fa-envelope">
<i class="fa fa-envelope" aria-hidden="true"></i>Direct
</label>
</div>
</div>
<input id="overlay_status_media_atta" name="files" type="file" multiple class="invisible"/>
<input id="overlay_status_cw" name="status_cw" type="checkbox" class="invisible" />
<input id="overlay_status_nsfw" name="status_nsfw" type="checkbox" class="invisible" />
<input id="overlay_status_public" name='privacy_option' value="public" class="invisible" type="radio" />
<input id="overlay_status_unlisted" name='privacy_option' value="unlisted" class="invisible" type="radio" />
<input id="overlay_status_fonly" name='privacy_option' value="private" class="invisible" type="radio" />
<input id="overlay_status_direct" name='privacy_option' value="direct" class="invisible" type="radio" />
<div class="submit_status_label_wrap">
<span class="character_count">
500
</span>
<!-- Submit -->
<label for="overlay_status_form_submit" class="submit_status_label">
<div class="toot_button_label disallow_select">
<i class="fa fa-fw fa-pencil-square-o"></i>
<span>Toot</span>
</div>
</label>
</div>
<input id="overlay_status_form_submit" class="submit_status" type="button" class="invisible"/>
</div>
</form>
</div>

View File

@ -0,0 +1,5 @@
<div id="overlay_message">
<section>
<span></span>
</section>
</div>

View File

@ -0,0 +1,105 @@
<div class="single_reply_status invisible">
<header class="single_reply_status_header">
<span class="emoji_poss">Reply to </span>
</header>
<div class="status_preview"></div>
<form id="single_reply_status_form" name="single_reply_status_form" class="status_form">
<div class="status_top">
<input class="status_spoiler invisible" name="status_spoiler" placeholder="Content warning" type="text"/>
</div>
<div class="status_main">
<!-- text area -->
<div class="status_textarea">
<textarea class="emoji_poss" name="status_textarea" placeholder="What's happening?"></textarea>
<div class="media_attachments_preview_area invisible"></div>
</div>
</div>
<div class="status_bottom">
<!-- Media Attachment -->
<label for="single_reply_status_media_atta" class="status_media_attachment status_option_button">
<i class="fa fa-camera" aria-hidden="true"></i>
</label>
<!-- Content warning -->
<label for="single_reply_status_cw" class="status_CW status_option_button">
<span class="disallow_select">CW</span>
</label>
<!-- Not safe for work -->
<label for="single_reply_status_nsfw" class="status_NSFW status_option_button">
<span class="disallow_select">NSFW</span>
</label>
<!-- Privacy options -->
<div class="status_privacy status_option_button expand_privacy_menu_button">
<!-- Expand menu -->
<i class="fa fa-globe" aria-hidden="true"></i>
<!-- Privacy options -->
<div class="expand_privacy_menu invisible">
<label for="single_reply_status_public" class="status_privacy select_privacy disallow_select" privacyicon="fa fa-globe">
<i class="fa fa-globe" aria-hidden="true"></i>Public
</label>
<label for="single_reply_status_unlisted" class="status_privacy select_privacy disallow_select" privacyicon="fa fa-unlock-alt">
<i class="fa fa-unlock-alt" aria-hidden="true"></i>Unlisted
</label>
<label for="single_reply_status_fonly" class="status_privacy select_privacy disallow_select" privacyicon="fa fa-lock">
<i class="fa fa-lock" aria-hidden="true"></i>Followers-only
</label>
<label for="single_reply_status_direct" class="status_privacy select_privacy disallow_select" privacyicon="fa fa-envelope">
<i class="fa fa-envelope" aria-hidden="true"></i>Direct
</label>
</div>
</div>
<input id="single_reply_status_media_atta" name="files" type="file" multiple class="invisible"/>
<input id="single_reply_status_cw" name="status_cw" type="checkbox" class="invisible" />
<input id="single_reply_status_nsfw" name="status_nsfw" type="checkbox" class="invisible" />
<input id="single_reply_status_public" name='privacy_option' value="public" class="invisible" type="radio" />
<input id="single_reply_status_unlisted" name='privacy_option' value="unlisted" class="invisible" type="radio" />
<input id="single_reply_status_fonly" name='privacy_option' value="private" class="invisible" type="radio" />
<input id="single_reply_status_direct" name='privacy_option' value="direct" class="invisible" type="radio" />
<div class="submit_status_label_wrap">
<span class="character_count">
500
</span>
<!-- Submit -->
<label for="single_reply_status_form_submit" class="submit_status_label">
<div class="toot_button_label disallow_select">
<i class="fa fa-reply" aria-hidden="true"></i>
<span>Reply</span>
</div>
</label>
</div>
<input id="single_reply_status_form_submit" class="submit_status" type="button" class="invisible"/>
</div>
</form>
</div>

View File

@ -0,0 +1,31 @@
<div id="js-search_title_box">
<h1></h1>
</div>
<!-- PROFILE NAV -->
<div class="search_nav_wrap">
<div class="search_nav">
<!-- PROFILE NAV LEFT -->
<div class="search_nav_left">
<ul class="search_nav_list">
<!-- TOOTS -->
<li id="js-search_nav_toots" class="search_nav_item search_toots">
<a>
<h2>TOOTS</h2>
</a>
</li>
<!-- FOLLOWING -->
<li id="js-search_nav_peoples" class="search_nav_item search_following">
<a>
<h2>PEOPLE</h2>
</a>
</li>
</ul>
</div>
</div>
</div>

View File

@ -0,0 +1,15 @@
<div class="side_widget stream_options">
<div class="form_title">
<h2>Change instance</h2>
</div>
<form>
<div class="local_instance_wrap">
<input name="local_instance" placeholder="Blank for default" type="text" class="disallow_enter"/>
</div>
</form>
</div>

View File

@ -0,0 +1,46 @@
<div class="current_profile_box">
<div class="current_profile_header">
<img class="js_current_header_image" />
</div>
<div class="current_profile">
<div class="current_profile_icon">
<img class="js_current_profile_image"/>
</div>
<div class="current_profile_name_box">
<a class="js_current_profile_link">
<h2 class="js_current_profile_displayname emoji_poss"></h2>
<span class="js_current_profile_username"></span>
</a>
</div>
<ul class="current_profile_counts">
<li class="current_profile_count current_profile_toots_count">
<a class="current_toots_count_link">
<span class="title">TOOTS</span>
<span class="js_current_toots_count count"></span>
</a>
</li>
<li class="current_profile_count current_profile_follows_count">
<a class="current_following_count_link">
<span class="title">FOLLOWING</span>
<span class="js_current_following_count count"></span>
</a>
</li>
<li class="current_profile_count current_profile_followers_count">
<a class="current_followers_count_link">
<span class="title">FOLLOWERS</span>
<span class="js_current_followers_count count"></span>
</a>
</li>
</ul>
</div>
</div>

View File

@ -0,0 +1,61 @@
<div class="side_widget what_to_follow">
<h2>Dev team</h2>
<ul class="account_list">
<li class="account_box">
<div class="icon_box">
<img src="/assets/images/icon.jpeg"/>
</div>
<div class="label_box">
<a href="/@neet@mastodon.cloud">
<h3>
<span class="dn">Neetshin</span>
<span class="un">@neet@mastodon.cloud</span>
</h3>
</a>
<button class="follow_button action_button" data="@neet@mastodon.cloud">
<i class="fa fa-fw fa-user-plus"></i>
<span>Follow</span>
</button>
</div>
</li>
<li class="account_box">
<div class="icon_box">
<img src="/assets/images/icon_pinfort.png"/>
</div>
<div class="label_box">
<a href="/@pinfort@jp-mstdn.com">
<h3>
<span class="dn">pinfort</span>
<span class="un">@pinfort@jp-mstdn.com</span>
</h3>
</a>
<button class="follow_button" data="@pinfort@jp-mstdn.com">
<i class="fa fa-fw fa-user-plus"></i>
<span>Follow</span>
</button>
</div>
</li>
<li class="account_box">
<div class="icon_box">
<img src="/assets/images/icon_peijun.jpeg"/>
</div>
<div class="label_box">
<a href="@peijun@mastodon.cloud">
<h3>
<span class="dn">Peijun</span>
<span class="un">@peijun@mastodon.cloud</span>
</h3>
</a>
<button class="follow_button" data="@peijun@mastodon.cloud">
<i class="fa fa-fw fa-user-plus"></i>
<span>Follow</span>
</button>
</div>
</li>
</ul>
</div>

View File

@ -0,0 +1,33 @@
<footer class="side_widgets_footer side_widget">
<ul>
<li>
Halcyon for <a href="https://github.com/tootsuite/mastodon">Mastodon</a>
</li>
<li>
<a class="footer_widget_about">About</a>
</li>
<li>
<a class="footer_widget_terms">Terms</a>
</li>
<li>
<a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">Apps</a>
</li>
<li>
<a href="https://github.com/tootsuite/mastodon">Source code</a>
</li>
<li>
<a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/List-of-Mastodon-instances.md">Other instances</a>
</li>
</ul>
</footer>
<img style="display:block;margin:16px auto;width: 30%;opacity: .3;" src="/assets/images/halcyon.png" />

View File

@ -0,0 +1,28 @@
<div class="side_widget stream_options">
<div class="form_title">
<h2>Preferences</h2>
<button>SHOW</button>
</div>
<form name="stream_options_form" class="pulldown_form">
<h3>New posts streaming</h3>
<div class="post_steraming_wrap">
</div>
<h3>Default post privacy</h3>
<div class="post_privacy_wrap">
</div>
<h3>Local instance</h3>
<div class="local_instance_wrap">
</div>
<h3>Hashtag search filter</h3>
<div class="search_filter_wrap">
</div>
</form>
</div>

View File

@ -0,0 +1,60 @@
<div class="side_widget what_to_follow">
<h2>Who to follow</h2>
<ul class="account_list">
<li class="account_box what_to_follow_0">
<div class="icon_box">
<img src=""/>
</div>
<div class="label_box">
<a href="">
<h3>
<span class="dn emoji_poss"></span>
<span class="un"></span>
</h3>
</a>
<button class="follow_button" mid="" data="">
<i class="fa fa-fw fa-user-plus"></i>
<span>Follow</span>
</button>
</div>
</li>
<li class="account_box what_to_follow_1">
<div class="icon_box">
<img src=""/>
</div>
<div class="label_box">
<a href="">
<h3>
<span class="dn emoji_poss"></span>
<span class="un"></span>
</h3>
</a>
<button class="follow_button" mid="" data="">
<i class="fa fa-fw fa-user-plus"></i>
<span>Follow</span>
</button>
</div>
</li>
<li class="account_box what_to_follow_2">
<div class="icon_box">
<img src=""/>
</div>
<div class="label_box">
<a href="">
<h3>
<span class="dn emoji_poss"></span>
<span class="un"></span>
</h3>
</a>
<button class="follow_button" mid="" data="">
<i class="fa fa-fw fa-user-plus"></i>
<span>Follow</span>
</button>
</div>
</li>
</ul>
</div>

View File

@ -0,0 +1,54 @@
<!-- HEADER IMAGE -->
<div class="header_image_box">
<img id="js_header_image" src="/assets/images/missing_header.png" />
</div>
<!-- PROFILE NAV -->
<div class="profile_nav_wrap">
<div class="profile_nav">
<!-- PROFILE NAV LEFT -->
<div class="profile_nav_left">
<!-- empty -->
</div>
<!-- PROFILE NAV RIGHT -->
<div class="profile_nav_right">
<ul class="profile_nav_list">
<!-- TOOTS -->
<li id="js-profile_nav_toots" class="profile_nav_item profile_toots">
<a>
<h2>TOOTS</h2>
<span id="js_toots_count"></span>
</a>
</li>
<!-- FOLLOWING -->
<li id="js-profile_nav_following" class="profile_nav_item profile_following">
<a>
<h2>FOLLOWING</h2>
<span id="js_following_count"></span>
</a>
</li>
<!-- FOLLOWERS -->
<li id="js-profile_nav_followers" class="profile_nav_item profile_followers">
<a>
<h2>FOLLOWERS</h2>
<span id="js_followers_count"></span>
</a>
</li>
<!-- FAVOURITES -->
<li id="js-profile_nav_favourites" class="profile_nav_item profile_favourites">
</li>
<!-- ACTION BUTTON -->
<li class="profile_button_box">
</li>
</ul>
</div>
</div>
</div>

View File

@ -0,0 +1,9 @@
<div id="js_profile_recent_images">
<a><i class="fa fa-camera" aria-hidden="true"></i><span></span></a>
<div id="js_profile_recent_images_box">
</div>
</div>