2017-08-18 12:16:56 +00:00
'use strict' ;
2018-02-22 02:27:17 +00:00
var formVars = { } ;
2018-09-08 12:47:37 +00:00
var alertMapping = { } ;
2017-06-10 07:58:15 +00:00
var alertList = [ ] ;
var alertCount = 0 ;
2018-06-30 03:40:50 +00:00
var moreTopicCount = 0 ;
2018-11-30 03:02:20 +00:00
var conn = false ;
2017-10-30 09:57:08 +00:00
var selectedTopics = [ ] ;
var attachItemCallback = function ( ) { }
2019-03-16 11:31:10 +00:00
var baseTitle = document . title ;
2019-03-16 23:14:47 +00:00
var wsBackoff = 0 ;
2017-10-30 09:57:08 +00:00
2018-01-14 12:03:20 +00:00
// Topic move
var forumToMoveTo = 0 ;
2017-10-30 09:57:08 +00:00
// TODO: Write a friendlier error handler which uses a .notice or something, we could have a specialised one for alerts
function ajaxError ( xhr , status , errstr ) {
console . log ( "The AJAX request failed" ) ;
console . log ( "xhr" , xhr ) ;
console . log ( "status" , status ) ;
console . log ( "errstr" , errstr ) ;
2019-03-16 11:31:10 +00:00
if ( status == "parsererror" ) console . log ( "The server didn't respond with a valid JSON response" ) ;
2017-10-30 09:57:08 +00:00
console . trace ( ) ;
}
2017-01-31 05:13:38 +00:00
2019-01-21 12:27:59 +00:00
function postLink ( event ) {
2016-12-02 07:38:54 +00:00
event . preventDefault ( ) ;
2017-10-30 09:57:08 +00:00
let formAction = $ ( event . target ) . closest ( 'a' ) . attr ( "href" ) ;
$ . ajax ( { url : formAction , type : "POST" , dataType : "json" , error : ajaxError , data : { js : "1" } } ) ;
2016-12-02 07:38:54 +00:00
}
2017-10-30 09:57:08 +00:00
function bindToAlerts ( ) {
2019-03-16 11:31:10 +00:00
console . log ( "bindToAlerts" ) ;
2019-03-11 08:47:45 +00:00
$ ( ".alertItem.withAvatar a" ) . unbind ( "click" ) ;
2017-08-18 12:16:56 +00:00
$ ( ".alertItem.withAvatar a" ) . click ( function ( event ) {
event . stopPropagation ( ) ;
2019-03-16 11:31:10 +00:00
event . preventDefault ( ) ;
$ . ajax ( {
url : "/api/?action=set&module=dismiss-alert" ,
type : "POST" ,
dataType : "json" ,
data : { asid : $ ( this ) . attr ( "data-asid" ) } ,
error : ajaxError ,
success : ( ) => {
window . location . href = this . getAttribute ( "href" ) ;
}
} ) ;
2017-08-18 12:16:56 +00:00
} ) ;
}
2018-09-08 12:47:37 +00:00
function addAlert ( msg , notice = false ) {
var mmsg = msg . msg ;
if ( "sub" in msg ) {
2019-03-16 11:31:10 +00:00
for ( var i = 0 ; i < msg . sub . length ; i ++ ) mmsg = mmsg . replace ( "\{" + i + "\}" , msg . sub [ i ] ) ;
2018-09-08 12:47:37 +00:00
}
let aItem = Template _alert ( {
ASID : msg . asid ,
Path : msg . path ,
Avatar : msg . avatar || "" ,
Message : mmsg
} )
2019-03-16 11:31:10 +00:00
//alertMapping[msg.asid] = aItem;
let div = document . createElement ( 'div' ) ;
div . innerHTML = aItem . trim ( ) ;
alertMapping [ msg . asid ] = div . firstChild ;
2018-09-08 12:47:37 +00:00
alertList . push ( msg . asid ) ;
if ( notice ) {
// TODO: Add some sort of notification queue to avoid flooding the end-user with notices?
// TODO: Use the site name instead of "Something Happened"
if ( Notification . permission === "granted" ) {
var n = new Notification ( "Something Happened" , {
body : mmsg ,
icon : msg . avatar ,
} ) ;
setTimeout ( n . close . bind ( n ) , 8000 ) ;
}
}
runInitHook ( "after_add_alert" ) ;
}
2018-05-14 08:56:56 +00:00
2018-09-08 12:47:37 +00:00
function updateAlertList ( menuAlerts ) {
let alertListNode = menuAlerts . getElementsByClassName ( "alertList" ) [ 0 ] ;
let alertCounterNode = menuAlerts . getElementsByClassName ( "alert_counter" ) [ 0 ] ;
2017-08-17 11:13:49 +00:00
alertCounterNode . textContent = "0" ;
2018-09-08 12:47:37 +00:00
2019-03-16 11:31:10 +00:00
alertListNode . innerHTML = "" ;
let any = false ;
/ * l e t o u t L i s t = " " ;
2018-09-08 12:47:37 +00:00
let j = 0 ;
for ( var i = 0 ; i < alertList . length && j < 8 ; i ++ ) {
outList += alertMapping [ alertList [ i ] ] ;
j ++ ;
2019-03-16 11:31:10 +00:00
} * /
let j = 0 ;
for ( var i = 0 ; i < alertList . length && j < 8 ; i ++ ) {
any = true ;
alertListNode . appendChild ( alertMapping [ alertList [ i ] ] ) ;
//outList += alertMapping[alertList[i]];
j ++ ;
2018-09-08 12:47:37 +00:00
}
2019-03-16 11:31:10 +00:00
if ( ! any ) alertListNode . innerHTML = "<div class='alertItem'>" + phraseBox [ "alerts" ] [ "alerts.no_alerts" ] + "</div>" ;
2018-09-08 12:47:37 +00:00
if ( alertCount != 0 ) {
alertCounterNode . textContent = alertCount ;
menuAlerts . classList . add ( "has_alerts" ) ;
2019-03-16 11:31:10 +00:00
let nTitle = "(" + alertCount + ") " + baseTitle ;
if ( document . title != nTitle ) document . title = nTitle ;
2018-09-08 12:47:37 +00:00
} else {
menuAlerts . classList . remove ( "has_alerts" ) ;
2019-03-16 11:31:10 +00:00
if ( document . title != baseTitle ) document . title = baseTitle ;
2018-09-08 12:47:37 +00:00
}
bindToAlerts ( ) ;
2018-11-30 03:02:20 +00:00
console . log ( "alertCount:" , alertCount )
runInitHook ( "after_update_alert_list" , alertCount ) ;
2018-09-08 12:47:37 +00:00
}
function setAlertError ( menuAlerts , msg ) {
let alertListNode = menuAlerts . getElementsByClassName ( "alertList" ) [ 0 ] ;
alertListNode . innerHTML = "<div class='alertItem'>" + msg + "</div>" ;
}
var alertsInitted = false ;
function loadAlerts ( menuAlerts ) {
if ( ! alertsInitted ) return ;
2017-03-03 16:28:49 +00:00
$ . ajax ( {
2017-11-06 16:24:45 +00:00
type : 'get' ,
dataType : 'json' ,
url : '/api/?action=get&module=alerts' ,
2018-05-14 08:56:56 +00:00
success : ( data ) => {
2017-11-06 16:24:45 +00:00
if ( "errmsg" in data ) {
2018-09-08 12:47:37 +00:00
setAlertError ( menuAlerts , data . errmsg )
2017-11-06 16:24:45 +00:00
return ;
}
2018-09-09 02:15:53 +00:00
alertList = [ ] ;
alertMapping = { } ;
2019-01-21 12:27:59 +00:00
for ( var i in data . msgs ) addAlert ( data . msgs [ i ] ) ;
2018-11-30 03:02:20 +00:00
console . log ( "data.msgCount:" , data . msgCount )
2017-11-06 16:24:45 +00:00
alertCount = data . msgCount ;
2018-09-08 12:47:37 +00:00
updateAlertList ( menuAlerts )
2017-11-06 16:24:45 +00:00
} ,
2018-05-14 08:56:56 +00:00
error : ( magic , theStatus , error ) => {
2018-09-19 06:09:03 +00:00
let errtxt = "Unable to get the alerts" ;
2017-11-06 16:24:45 +00:00
try {
var data = JSON . parse ( magic . responseText ) ;
if ( "errmsg" in data ) errtxt = data . errmsg ;
} catch ( err ) {
console . log ( magic . responseText ) ;
console . log ( err ) ;
}
console . log ( "error" , error ) ;
2018-09-08 12:47:37 +00:00
setAlertError ( menuAlerts , errtxt ) ;
2017-11-06 16:24:45 +00:00
}
} ) ;
2017-03-03 16:28:49 +00:00
}
2017-08-15 13:47:56 +00:00
function SplitN ( data , ch , n ) {
var out = [ ] ;
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
if ( data . length === 0 ) return out ;
2017-08-15 13:47:56 +00:00
var lastIndex = 0 ;
var j = 0 ;
var lastN = 1 ;
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
for ( let i = 0 ; i < data . length ; i ++ ) {
if ( data [ i ] === ch ) {
2017-08-15 13:47:56 +00:00
out [ j ++ ] = data . substring ( lastIndex , i ) ;
lastIndex = i ;
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
if ( lastN === n ) break ;
2017-08-15 13:47:56 +00:00
lastN ++ ;
2017-05-11 13:04:43 +00:00
}
}
2017-08-15 13:47:56 +00:00
if ( data . length > lastIndex ) out [ out . length - 1 ] += data . substring ( lastIndex ) ;
return out ;
}
2017-05-29 14:52:37 +00:00
2018-06-24 13:49:29 +00:00
function wsAlertEvent ( data ) {
2019-03-16 11:31:10 +00:00
console . log ( "wsAlertEvent:" , data )
2018-09-08 12:47:37 +00:00
addAlert ( data , true ) ;
2019-03-16 11:31:10 +00:00
alertCount ++ ;
let aTmp = alertList ;
alertList = [ alertList [ alertList . length - 1 ] ] ;
aTmp = aTmp . slice ( 0 , - 1 ) ;
for ( let i = 0 ; i < aTmp . length ; i ++ ) alertList . push ( aTmp [ i ] ) ;
//var alist = "";
//for (var i = 0; i < alertList.length; i++) alist += alertMapping[alertList[i]];
2018-06-24 13:49:29 +00:00
// TODO: Add support for other alert feeds like PM Alerts
var generalAlerts = document . getElementById ( "general_alerts" ) ;
2018-11-30 03:02:20 +00:00
// TODO: Make sure we update alertCount here
2019-03-16 11:31:10 +00:00
updateAlertList ( generalAlerts /*, alist*/ ) ;
2018-06-24 13:49:29 +00:00
}
2017-11-06 16:24:45 +00:00
function runWebSockets ( ) {
2018-06-24 13:49:29 +00:00
if ( window . location . protocol == "https:" ) {
2017-11-06 16:24:45 +00:00
conn = new WebSocket ( "wss://" + document . location . host + "/ws/" ) ;
2018-06-24 13:49:29 +00:00
} else conn = new WebSocket ( "ws://" + document . location . host + "/ws/" ) ;
2017-06-10 07:58:15 +00:00
2018-08-13 10:34:00 +00:00
conn . onerror = ( err ) => {
console . log ( err ) ;
}
2019-03-05 04:46:43 +00:00
// TODO: Sync alerts, topic list, etc.
2018-06-24 13:49:29 +00:00
conn . onopen = ( ) => {
2017-11-06 16:24:45 +00:00
console . log ( "The WebSockets connection was opened" ) ;
conn . send ( "page " + document . location . pathname + '\r' ) ;
// TODO: Don't ask again, if it's denied. We could have a setting in the UCP which automatically requests this when someone flips desktop notifications on
2019-03-16 11:31:10 +00:00
if ( me . User . ID > 0 ) Notification . requestPermission ( ) ;
2017-11-06 16:24:45 +00:00
}
2018-08-13 10:34:00 +00:00
2018-06-24 13:49:29 +00:00
conn . onclose = ( ) => {
2017-11-06 16:24:45 +00:00
conn = false ;
console . log ( "The WebSockets connection was closed" ) ;
2019-03-16 13:11:24 +00:00
let backoff = 1000 ;
2019-03-16 23:14:47 +00:00
if ( wsBackoff < 0 ) wsBackoff = 0 ;
else if ( wsBackoff > 12 ) backoff = 13000 ;
else if ( wsBackoff > 5 ) backoff = 7000 ;
wsBackoff ++ ;
2019-03-05 05:53:02 +00:00
setTimeout ( ( ) => {
var alertMenuList = document . getElementsByClassName ( "menu_alerts" ) ;
for ( var i = 0 ; i < alertMenuList . length ; i ++ ) {
loadAlerts ( alertMenuList [ i ] ) ;
}
runWebSockets ( ) ;
2019-03-16 13:11:24 +00:00
} , 60 * backoff ) ;
2019-03-16 23:14:47 +00:00
if ( wsBackoff > 0 ) {
if ( wsBackoff <= 5 ) setTimeout ( ( ) => wsBackoff -- , 60 * 4000 ) ;
else if ( wsBackoff <= 12 ) setTimeout ( ( ) => wsBackoff -- , 60 * 20000 ) ;
}
2017-11-06 16:24:45 +00:00
}
2018-08-13 10:34:00 +00:00
2018-06-24 13:49:29 +00:00
conn . onmessage = ( event ) => {
2017-11-06 16:24:45 +00:00
if ( event . data [ 0 ] == "{" ) {
2018-06-24 13:49:29 +00:00
console . log ( "json message" ) ;
let data = "" ;
2017-11-06 16:24:45 +00:00
try {
2018-06-24 13:49:29 +00:00
data = JSON . parse ( event . data ) ;
2017-11-06 16:24:45 +00:00
} catch ( err ) {
console . log ( err ) ;
2018-06-24 13:49:29 +00:00
return ;
2017-11-06 16:24:45 +00:00
}
2017-08-18 12:16:56 +00:00
2018-11-30 03:02:20 +00:00
if ( "msg" in data ) wsAlertEvent ( data ) ;
else if ( "event" in data ) {
2018-09-08 12:47:37 +00:00
if ( data . event == "dismiss-alert" ) {
2018-09-08 13:50:15 +00:00
Object . keys ( alertMapping ) . forEach ( ( key ) => {
2018-09-08 12:47:37 +00:00
if ( key == data . asid ) {
alertCount -- ;
2018-09-08 13:50:15 +00:00
let index = - 1 ;
for ( var i = 0 ; i < alertList . length ; i ++ ) {
2018-09-08 12:47:37 +00:00
if ( alertList [ i ] == key ) {
2018-09-08 13:50:15 +00:00
alertList [ i ] = 0 ;
index = i ;
2018-09-08 12:47:37 +00:00
}
}
2018-09-09 02:15:53 +00:00
if ( index == - 1 ) return ;
2018-09-08 13:50:15 +00:00
for ( var i = index ; ( i + 1 ) < alertList . length ; i ++ ) {
alertList [ i ] = alertList [ i + 1 ] ;
}
alertList . splice ( alertList . length - 1 , 1 ) ;
2018-09-08 12:47:37 +00:00
delete alertMapping [ key ] ;
2018-09-08 13:50:15 +00:00
// TODO: Add support for other alert feeds like PM Alerts
var generalAlerts = document . getElementById ( "general_alerts" ) ;
2018-12-27 05:42:41 +00:00
if ( alertList . length < 8 ) loadAlerts ( generalAlerts ) ;
else updateAlertList ( generalAlerts ) ;
2018-09-08 12:47:37 +00:00
}
} ) ;
}
2018-06-24 13:49:29 +00:00
} else if ( "Topics" in data ) {
console . log ( "topic in data" ) ;
console . log ( "data:" , data ) ;
let topic = data . Topics [ 0 ] ;
if ( topic === undefined ) {
console . log ( "empty topic list" ) ;
return ;
2017-06-10 07:58:15 +00:00
}
2018-06-30 03:40:50 +00:00
// TODO: Fix the data race where the function hasn't been loaded yet
2018-06-24 13:49:29 +00:00
let renTopic = Template _topics _topic ( topic ) ;
2018-06-30 03:40:50 +00:00
$ ( ".topic_row[data-tid='" + topic . ID + "']" ) . addClass ( "ajax_topic_dupe" ) ;
2018-06-24 13:49:29 +00:00
let node = $ ( renTopic ) ;
2018-06-30 03:40:50 +00:00
node . addClass ( "new_item hide_ajax_topic" ) ;
2018-06-24 13:49:29 +00:00
console . log ( "Prepending to topic list" ) ;
$ ( ".topic_list" ) . prepend ( node ) ;
2018-06-30 03:40:50 +00:00
moreTopicCount ++ ;
let moreTopicBlocks = document . getElementsByClassName ( "more_topic_block_initial" ) ;
for ( let i = 0 ; i < moreTopicBlocks . length ; i ++ ) {
let moreTopicBlock = moreTopicBlocks [ i ] ;
moreTopicBlock . classList . remove ( "more_topic_block_initial" ) ;
moreTopicBlock . classList . add ( "more_topic_block_active" ) ;
console . log ( "phraseBox:" , phraseBox ) ;
let msgBox = moreTopicBlock . getElementsByClassName ( "more_topics" ) [ 0 ] ;
msgBox . innerText = phraseBox [ "topic_list" ] [ "topic_list.changed_topics" ] . replace ( "%d" , moreTopicCount ) ;
}
2018-06-24 13:49:29 +00:00
} else {
console . log ( "unknown message" ) ;
console . log ( data ) ;
2017-06-10 07:58:15 +00:00
}
2017-11-06 16:24:45 +00:00
}
2017-06-10 07:58:15 +00:00
2017-11-06 16:24:45 +00:00
var messages = event . data . split ( '\r' ) ;
for ( var i = 0 ; i < messages . length ; i ++ ) {
2018-03-31 05:25:27 +00:00
let message = messages [ i ] ;
//console.log("Message: ",message);
2018-08-11 15:53:42 +00:00
let msgblocks = SplitN ( message , " " , 3 ) ;
if ( msgblocks . length < 3 ) continue ;
2018-03-31 05:25:27 +00:00
if ( message . startsWith ( "set " ) ) {
2019-01-21 12:27:59 +00:00
let oldInnerHTML = document . querySelector ( msgblocks [ 1 ] ) . innerHTML ;
if ( msgblocks [ 2 ] == oldInnerHTML ) continue ;
2017-11-06 16:24:45 +00:00
document . querySelector ( msgblocks [ 1 ] ) . innerHTML = msgblocks [ 2 ] ;
2018-03-31 05:25:27 +00:00
} else if ( message . startsWith ( "set-class " ) ) {
2019-01-21 12:27:59 +00:00
// Fix to stop the inspector from getting all jittery
let oldClassName = document . querySelector ( msgblocks [ 1 ] ) . className ;
if ( msgblocks [ 2 ] == oldClassName ) continue ;
2017-11-06 16:24:45 +00:00
document . querySelector ( msgblocks [ 1 ] ) . className = msgblocks [ 2 ] ;
2017-05-11 13:04:43 +00:00
}
}
}
2017-11-06 16:24:45 +00:00
}
2018-08-11 15:53:42 +00:00
( ( ) => {
2018-08-13 10:34:00 +00:00
addInitHook ( "pre_init" , ( ) => {
2019-03-16 11:31:10 +00:00
console . log ( "before notify on alert" )
2018-08-13 10:34:00 +00:00
// We can only get away with this because template_alert has no phrases, otherwise it too would have to be part of the "dance", I miss Go concurrency :(
2019-03-16 11:31:10 +00:00
notifyOnScriptW ( "template_alert" , ( e ) => {
if ( e != undefined ) console . log ( "failed alert? why?" , e )
} , ( ) => {
console . log ( "ha" )
if ( ! Template _alert ) throw ( "template function not found" ) ;
2018-11-30 03:02:20 +00:00
addInitHook ( "after_phrases" , ( ) => {
// TODO: The load part of loadAlerts could be done asynchronously while the update of the DOM could be deferred
$ ( document ) . ready ( ( ) => {
alertsInitted = true ;
var alertMenuList = document . getElementsByClassName ( "menu_alerts" ) ;
for ( var i = 0 ; i < alertMenuList . length ; i ++ ) {
loadAlerts ( alertMenuList [ i ] ) ;
}
if ( window [ "WebSocket" ] ) runWebSockets ( ) ;
} ) ;
2018-08-11 15:53:42 +00:00
} ) ;
2018-08-13 10:34:00 +00:00
} ) ;
2018-08-11 15:53:42 +00:00
2018-08-13 10:34:00 +00:00
$ ( document ) . ready ( mainInit ) ;
} ) ;
2018-08-11 15:53:42 +00:00
} ) ( ) ;
2019-02-10 05:52:26 +00:00
// TODO: Use these in .filter_item and pass back an item count from the backend to work with here
// Ported from common/parser.go
function PageOffset ( count , page , perPage ) {
let offset = 0 ;
let lastPage = LastPage ( count , perPage )
if ( page > 1 ) {
offset = ( perPage * page ) - perPage
} else if ( page == - 1 ) {
page = lastPage
offset = ( perPage * page ) - perPage
} else {
page = 1
}
// We don't want the offset to overflow the slices, if everything's in memory
2019-02-24 01:29:06 +00:00
//if(offset >= (count - 1)) offset = 0;
2019-02-10 05:52:26 +00:00
return { Offset : offset , Page : page , LastPage : lastPage }
}
function LastPage ( count , perPage ) {
return ( count / perPage ) + 1
}
function Paginate ( count , perPage , maxPages ) {
if ( count < perPage ) return [ 1 ] ;
let page = 0 ;
let out = [ ] ;
for ( let current = 0 ; current < count ; current += perPage ) {
page ++ ;
out . push ( page ) ;
if ( out . length >= maxPages ) break ;
}
return out ;
}
2018-08-11 15:53:42 +00:00
function mainInit ( ) {
2018-08-13 10:34:00 +00:00
runInitHook ( "start_init" ) ;
2017-05-29 14:52:37 +00:00
2018-06-30 03:40:50 +00:00
$ ( ".more_topics" ) . click ( ( event ) => {
event . preventDefault ( ) ;
let moreTopicBlocks = document . getElementsByClassName ( "more_topic_block_active" ) ;
for ( let i = 0 ; i < moreTopicBlocks . length ; i ++ ) {
let moreTopicBlock = moreTopicBlocks [ i ] ;
moreTopicBlock . classList . remove ( "more_topic_block_active" ) ;
moreTopicBlock . classList . add ( "more_topic_block_initial" ) ;
}
$ ( ".ajax_topic_dupe" ) . fadeOut ( "slow" , function ( ) {
$ ( this ) . remove ( ) ;
} ) ;
$ ( ".hide_ajax_topic" ) . removeClass ( "hide_ajax_topic" ) ; // TODO: Do Fade
moreTopicCount = 0 ;
} )
2018-03-31 05:25:27 +00:00
$ ( ".add_like" ) . click ( function ( event ) {
event . preventDefault ( ) ;
let target = this . closest ( "a" ) . getAttribute ( "href" ) ;
console . log ( "target: " , target ) ;
2018-09-08 12:47:37 +00:00
this . classList . remove ( "add_like" ) ;
this . classList . add ( "remove_like" ) ;
let controls = this . closest ( ".controls" ) ;
2018-03-31 05:25:27 +00:00
let hadLikes = controls . classList . contains ( "has_likes" ) ;
if ( ! hadLikes ) controls . classList . add ( "has_likes" ) ;
let likeCountNode = controls . getElementsByClassName ( "like_count" ) [ 0 ] ;
console . log ( "likeCountNode" , likeCountNode ) ;
likeCountNode . innerHTML = parseInt ( likeCountNode . innerHTML ) + 1 ;
2018-09-08 12:47:37 +00:00
let likeButton = this ;
2018-03-31 05:25:27 +00:00
$ . ajax ( {
url : target ,
type : "POST" ,
dataType : "json" ,
data : { isJs : 1 } ,
error : ajaxError ,
success : function ( data , status , xhr ) {
if ( "success" in data ) {
2019-02-10 05:52:26 +00:00
if ( data [ "success" ] == "1" ) return ;
2018-03-31 05:25:27 +00:00
}
// addNotice("Failed to add a like: {err}")
likeButton . classList . add ( "add_like" ) ;
likeButton . classList . remove ( "remove_like" ) ;
if ( ! hadLikes ) controls . classList . remove ( "has_likes" ) ;
likeCountNode . innerHTML = parseInt ( likeCountNode . innerHTML ) - 1 ;
console . log ( "data" , data ) ;
console . log ( "status" , status ) ;
console . log ( "xhr" , xhr ) ;
}
} ) ;
} ) ;
2018-09-26 07:46:30 +00:00
$ ( ".link_label" ) . click ( function ( event ) {
event . preventDefault ( ) ;
2019-02-23 06:29:19 +00:00
let linkSelect = $ ( '#' + $ ( this ) . attr ( "data-for" ) ) ;
2018-09-27 09:41:35 +00:00
if ( ! linkSelect . hasClass ( "link_opened" ) ) {
event . stopPropagation ( ) ;
linkSelect . addClass ( "link_opened" ) ;
}
2018-09-26 07:46:30 +00:00
} ) ;
2019-02-10 05:52:26 +00:00
function rebuildPaginator ( lastPage ) {
let urlParams = new URLSearchParams ( window . location . search ) ;
let page = urlParams . get ( 'page' ) ;
if ( page == "" ) page = 1 ;
let stopAtPage = lastPage ;
if ( stopAtPage > 5 ) stopAtPage = 5 ;
let pageList = [ ] ;
for ( let i = 0 ; i < stopAtPage ; i ++ ) pageList . push ( i + 1 ) ;
//$(".pageset").html(Template_paginator({PageList: pageList, Page: page, LastPage: lastPage}));
let ok = false ;
$ ( ".pageset" ) . each ( function ( ) {
this . outerHTML = Template _paginator ( { PageList : pageList , Page : page , LastPage : lastPage } ) ;
ok = true ;
} ) ;
2019-02-23 06:29:19 +00:00
if ( ! ok ) $ ( Template _paginator ( { PageList : pageList , Page : page , LastPage : lastPage } ) ) . insertAfter ( "#topic_list" ) ;
2019-02-10 05:52:26 +00:00
}
function rebindPaginator ( ) {
$ ( ".pageitem a" ) . unbind ( "click" ) ;
$ ( ".pageitem a" ) . click ( function ( ) {
event . preventDefault ( ) ;
// TODO: Take mostviewed into account
let url = "//" + window . location . host + window . location . pathname ;
let urlParams = new URLSearchParams ( window . location . search ) ;
urlParams . set ( "page" , new URLSearchParams ( this . getAttribute ( "href" ) ) . get ( "page" ) ) ;
let q = "?" ;
for ( let item of urlParams . entries ( ) ) q += item [ 0 ] + "=" + item [ 1 ] + "&" ;
if ( q . length > 1 ) q = q . slice ( 0 , - 1 ) ;
// TODO: Try to de-duplicate some of these fetch calls
fetch ( url + q + "&js=1" , { credentials : "same-origin" } )
2019-03-16 11:31:10 +00:00
. then ( ( resp ) => {
if ( ! resp . ok ) throw ( url + q + "&js=1 failed to load" ) ;
return resp . json ( ) ;
} ) . then ( ( data ) => {
2019-02-10 05:52:26 +00:00
if ( ! "Topics" in data ) throw ( "no Topics in data" ) ;
let topics = data [ "Topics" ] ;
2019-03-16 11:31:10 +00:00
console . log ( "ajax navigated to different page" ) ;
2019-02-10 05:52:26 +00:00
// TODO: Fix the data race where the function hasn't been loaded yet
let out = "" ;
for ( let i = 0 ; i < topics . length ; i ++ ) out += Template _topics _topic ( topics [ i ] ) ;
$ ( ".topic_list" ) . html ( out ) ;
let obj = { Title : document . title , Url : url + q } ;
history . pushState ( obj , obj . Title , obj . Url ) ;
rebuildPaginator ( data . LastPage ) ;
rebindPaginator ( ) ;
} ) . catch ( ( ex ) => {
console . log ( "Unable to get script '" + url + q + "&js=1" + "'" ) ;
console . log ( "ex: " , ex ) ;
console . trace ( ) ;
} ) ;
} ) ;
}
// TODO: Render a headless topics.html instead of the individual topic rows and a bit of JS glue
$ ( ".filter_item" ) . click ( function ( event ) {
if ( ! window . location . pathname . startsWith ( "/topics/" ) ) return
event . preventDefault ( ) ;
let that = this ;
let fid = this . getAttribute ( "data-fid" ) ;
// TODO: Take mostviewed into account
let url = "//" + window . location . host + "/topics/?fids=" + fid ;
fetch ( url + "&js=1" , { credentials : "same-origin" } )
2019-03-16 11:31:10 +00:00
. then ( ( resp ) => {
if ( ! resp . ok ) throw ( url + "&js=1 failed to load" ) ;
return resp . json ( ) ;
} ) . then ( ( data ) => {
console . log ( "data:" , data ) ;
2019-02-10 05:52:26 +00:00
if ( ! "Topics" in data ) throw ( "no Topics in data" ) ;
let topics = data [ "Topics" ] ;
2019-03-16 11:31:10 +00:00
console . log ( "ajax navigated to " + that . innerText ) ;
2019-02-10 05:52:26 +00:00
// TODO: Fix the data race where the function hasn't been loaded yet
let out = "" ;
for ( let i = 0 ; i < topics . length ; i ++ ) out += Template _topics _topic ( topics [ i ] ) ;
$ ( ".topic_list" ) . html ( out ) ;
2019-02-28 07:28:17 +00:00
//$(".topic_list").addClass("single_forum");
2019-02-10 05:52:26 +00:00
2019-03-16 11:31:10 +00:00
baseTitle = that . innerText ;
if ( alertCount > 0 ) document . title = "(" + alertCount + ") " + baseTitle ;
else document . title = baseTitle ;
2019-02-10 05:52:26 +00:00
let obj = { Title : document . title , Url : url } ;
history . pushState ( obj , obj . Title , obj . Url ) ;
rebuildPaginator ( data . LastPage )
rebindPaginator ( ) ;
$ ( ".filter_item" ) . each ( function ( ) {
this . classList . remove ( "filter_selected" ) ;
} ) ;
that . classList . add ( "filter_selected" ) ;
$ ( ".topic_list_title h1" ) . text ( that . innerText ) ;
} ) . catch ( ( ex ) => {
console . log ( "Unable to get script '" + url + "&js=1" + "'" ) ;
console . log ( "ex: " , ex ) ;
console . trace ( ) ;
} ) ;
} ) ;
if ( document . getElementById ( "topicsItemList" ) !== null ) rebindPaginator ( ) ;
if ( document . getElementById ( "forumItemList" ) !== null ) rebindPaginator ( ) ;
2019-02-23 06:29:19 +00:00
// TODO: Show a search button when JS is disabled?
$ ( ".widget_search_input" ) . keypress ( function ( e ) {
if ( e . keyCode != '13' ) return ;
event . preventDefault ( ) ;
// TODO: Take mostviewed into account
let url = "//" + window . location . host + window . location . pathname ;
let urlParams = new URLSearchParams ( window . location . search ) ;
urlParams . set ( "q" , this . value ) ;
let q = "?" ;
for ( let item of urlParams . entries ( ) ) q += item [ 0 ] + "=" + item [ 1 ] + "&" ;
if ( q . length > 1 ) q = q . slice ( 0 , - 1 ) ;
// TODO: Try to de-duplicate some of these fetch calls
fetch ( url + q + "&js=1" , { credentials : "same-origin" } )
2019-03-16 11:31:10 +00:00
. then ( ( resp ) => {
if ( ! resp . ok ) throw ( url + q + "&js=1 failed to load" ) ;
return resp . json ( ) ;
} ) . then ( ( data ) => {
2019-02-23 06:29:19 +00:00
if ( ! "Topics" in data ) throw ( "no Topics in data" ) ;
let topics = data [ "Topics" ] ;
2019-03-16 11:31:10 +00:00
console . log ( "ajax navigated to search page" ) ;
2019-02-23 06:29:19 +00:00
// TODO: Fix the data race where the function hasn't been loaded yet
let out = "" ;
for ( let i = 0 ; i < topics . length ; i ++ ) out += Template _topics _topic ( topics [ i ] ) ;
$ ( ".topic_list" ) . html ( out ) ;
2019-03-16 11:31:10 +00:00
baseTitle = phraseBox [ "topic_list" ] [ "topic_list.search_head" ] ;
2019-02-24 01:29:06 +00:00
$ ( ".topic_list_title h1" ) . text ( phraseBox [ "topic_list" ] [ "topic_list.search_head" ] ) ;
2019-03-16 11:31:10 +00:00
if ( alertCount > 0 ) document . title = "(" + alertCount + ") " + baseTitle ;
else document . title = baseTitle ;
2019-02-23 06:29:19 +00:00
let obj = { Title : document . title , Url : url + q } ;
history . pushState ( obj , obj . Title , obj . Url ) ;
rebuildPaginator ( data . LastPage ) ;
rebindPaginator ( ) ;
} ) . catch ( ( ex ) => {
console . log ( "Unable to get script '" + url + q + "&js=1" + "'" ) ;
console . log ( "ex: " , ex ) ;
console . trace ( ) ;
} ) ;
} ) ;
2018-03-31 05:25:27 +00:00
$ ( ".open_edit" ) . click ( ( event ) => {
2016-12-02 07:38:54 +00:00
event . preventDefault ( ) ;
2018-09-26 03:14:53 +00:00
$ ( '.hide_on_edit' ) . addClass ( "edit_opened" ) ;
$ ( '.show_on_edit' ) . addClass ( "edit_opened" ) ;
2018-12-27 05:42:41 +00:00
runHook ( "open_edit" ) ;
2016-12-02 07:38:54 +00:00
} ) ;
2017-05-29 14:52:37 +00:00
2017-01-31 05:13:38 +00:00
$ ( ".topic_item .submit_edit" ) . click ( function ( event ) {
2016-12-02 07:38:54 +00:00
event . preventDefault ( ) ;
2018-03-31 05:25:27 +00:00
let topicNameInput = $ ( ".topic_name_input" ) . val ( ) ;
$ ( ".topic_name" ) . html ( topicNameInput ) ;
$ ( ".topic_name" ) . attr ( topicNameInput ) ;
let topicContentInput = $ ( '.topic_content_input' ) . val ( ) ;
2018-12-28 02:08:35 +00:00
$ ( ".topic_content" ) . html ( quickParse ( topicContentInput ) ) ;
2018-03-31 05:25:27 +00:00
let topicStatusInput = $ ( '.topic_status_input' ) . val ( ) ;
$ ( ".topic_status_e:not(.open_edit)" ) . html ( topicStatusInput ) ;
2017-05-29 14:52:37 +00:00
2018-09-26 03:14:53 +00:00
$ ( '.hide_on_edit' ) . removeClass ( "edit_opened" ) ;
$ ( '.show_on_edit' ) . removeClass ( "edit_opened" ) ;
2018-12-27 05:42:41 +00:00
runHook ( "close_edit" ) ;
2017-05-29 14:52:37 +00:00
2016-12-02 07:38:54 +00:00
$ . ajax ( {
2018-12-28 11:13:06 +00:00
url : this . form . getAttribute ( "action" ) ,
2017-05-29 14:52:37 +00:00
type : "POST" ,
dataType : "json" ,
2016-12-02 07:38:54 +00:00
data : {
2017-10-16 07:32:58 +00:00
topic _name : topicNameInput ,
topic _status : topicStatusInput ,
topic _content : topicContentInput ,
2018-12-28 11:13:06 +00:00
js : 1
} ,
error : ajaxError ,
success : ( data , status , xhr ) => {
if ( "Content" in data ) $ ( ".topic_content" ) . html ( data [ "Content" ] ) ;
2017-05-29 14:52:37 +00:00
}
2016-12-02 07:38:54 +00:00
} ) ;
} ) ;
2017-05-29 14:52:37 +00:00
2018-01-28 14:30:24 +00:00
$ ( ".delete_item" ) . click ( function ( event ) {
2017-10-30 09:57:08 +00:00
postLink ( event ) ;
2017-10-16 07:32:58 +00:00
$ ( this ) . closest ( '.deletable_block' ) . remove ( ) ;
2016-12-02 07:38:54 +00:00
} ) ;
2017-05-29 14:52:37 +00:00
2018-12-28 02:08:35 +00:00
// Miniature implementation of the parser to avoid sending as much data back and forth
function quickParse ( msg ) {
msg = msg . replace ( ":)" , "😀" )
msg = msg . replace ( ":(" , "😞" )
msg = msg . replace ( ":D" , "😃" )
msg = msg . replace ( ":P" , "😛" )
msg = msg . replace ( ":O" , "😲" )
msg = msg . replace ( ":p" , "😛" )
msg = msg . replace ( ":o" , "😲" )
msg = msg . replace ( ";)" , "😉" )
msg = msg . replace ( "\n" , "<br>" )
return msg
}
2018-01-08 08:53:51 +00:00
$ ( ".edit_item" ) . click ( function ( event ) {
2016-12-02 07:38:54 +00:00
event . preventDefault ( ) ;
2018-12-27 09:12:30 +00:00
let blockParent = this . closest ( '.editable_parent' ) ;
2018-12-31 09:03:49 +00:00
$ ( blockParent ) . find ( '.hide_on_edit' ) . addClass ( "edit_opened" ) ;
$ ( blockParent ) . find ( '.show_on_edit' ) . addClass ( "edit_opened" ) ;
2018-12-27 09:12:30 +00:00
let srcNode = blockParent . querySelector ( ".edit_source" ) ;
let block = blockParent . querySelector ( '.editable_block' ) ;
block . classList . add ( "in_edit" ) ;
let source = "" ;
if ( srcNode != null ) source = srcNode . innerText ;
else source = block . innerHTML ;
// TODO: Add a client template for this
2018-12-28 02:08:35 +00:00
block . innerHTML = "<textarea style='width: 99%;' name='edit_item'>" + source + "</textarea><br><a href='" + this . closest ( 'a' ) . getAttribute ( "href" ) + "'><button class='submit_edit' type='submit'>Update</button></a>" ;
2019-03-04 07:47:38 +00:00
runHook ( "edit_item_pre_bind" ) ;
2017-05-29 14:52:37 +00:00
2018-01-08 08:53:51 +00:00
$ ( ".submit_edit" ) . click ( function ( event ) {
2016-12-02 07:38:54 +00:00
event . preventDefault ( ) ;
2018-12-31 09:03:49 +00:00
$ ( blockParent ) . find ( '.hide_on_edit' ) . removeClass ( "edit_opened" ) ;
$ ( blockParent ) . find ( '.show_on_edit' ) . removeClass ( "edit_opened" ) ;
2018-12-27 09:12:30 +00:00
block . classList . remove ( "in_edit" ) ;
let newContent = block . querySelector ( 'textarea' ) . value ;
2018-12-28 02:08:35 +00:00
block . innerHTML = quickParse ( newContent ) ;
2018-12-27 09:12:30 +00:00
if ( srcNode != null ) srcNode . innerText = newContent ;
2017-05-29 14:52:37 +00:00
2018-12-27 09:12:30 +00:00
let formAction = this . closest ( 'a' ) . getAttribute ( "href" ) ;
// TODO: Bounce the parsed post back and set innerHTML to it?
2018-12-28 02:08:35 +00:00
$ . ajax ( {
url : formAction ,
type : "POST" ,
dataType : "json" ,
data : { js : "1" , edit _item : newContent } ,
error : ajaxError ,
success : ( data , status , xhr ) => {
if ( "Content" in data ) block . innerHTML = data [ "Content" ] ;
}
2016-12-02 07:38:54 +00:00
} ) ;
} ) ;
} ) ;
2017-05-29 14:52:37 +00:00
2018-03-31 05:25:27 +00:00
$ ( ".edit_field" ) . click ( function ( event ) {
2016-12-06 10:26:48 +00:00
event . preventDefault ( ) ;
2017-10-16 07:32:58 +00:00
let blockParent = $ ( this ) . closest ( '.editable_parent' ) ;
let block = blockParent . find ( '.editable_block' ) . eq ( 0 ) ;
2016-12-06 10:26:48 +00:00
block . html ( "<input name='edit_field' value='" + block . text ( ) + "' type='text'/><a href='" + $ ( this ) . closest ( 'a' ) . attr ( "href" ) + "'><button class='submit_edit' type='submit'>Update</button></a>" ) ;
2017-05-29 14:52:37 +00:00
2017-10-21 00:27:47 +00:00
$ ( ".submit_edit" ) . click ( function ( event ) {
2016-12-06 10:26:48 +00:00
event . preventDefault ( ) ;
2017-10-16 07:32:58 +00:00
let blockParent = $ ( this ) . closest ( '.editable_parent' ) ;
let block = blockParent . find ( '.editable_block' ) . eq ( 0 ) ;
let newContent = block . find ( 'input' ) . eq ( 0 ) . val ( ) ;
2016-12-06 10:26:48 +00:00
block . html ( newContent ) ;
2017-05-29 14:52:37 +00:00
2017-10-16 07:32:58 +00:00
let formAction = $ ( this ) . closest ( 'a' ) . attr ( "href" ) ;
2016-12-06 10:26:48 +00:00
$ . ajax ( {
2018-08-11 15:53:42 +00:00
url : formAction + "?session=" + me . User . Session ,
2016-12-06 10:26:48 +00:00
type : "POST" ,
dataType : "json" ,
2017-10-30 09:57:08 +00:00
error : ajaxError ,
2017-10-21 00:27:47 +00:00
data : { isJs : "1" , edit _item : newContent }
2016-12-06 10:26:48 +00:00
} ) ;
} ) ;
} ) ;
2017-05-29 14:52:37 +00:00
2017-01-31 05:13:38 +00:00
$ ( ".edit_fields" ) . click ( function ( event )
{
event . preventDefault ( ) ;
2017-08-27 09:33:45 +00:00
if ( $ ( this ) . find ( "input" ) . length !== 0 ) return ;
2017-05-29 14:52:37 +00:00
//console.log("clicked .edit_fields");
2017-10-30 09:57:08 +00:00
var blockParent = $ ( this ) . closest ( '.editable_parent' ) ;
2018-09-02 05:43:17 +00:00
blockParent . find ( '.hide_on_edit' ) . addClass ( "edit_opened" ) ;
blockParent . find ( '.show_on_edit' ) . addClass ( "edit_opened" ) ;
2017-10-30 09:57:08 +00:00
blockParent . find ( '.editable_block' ) . show ( ) ;
blockParent . find ( '.editable_block' ) . each ( function ( ) {
var fieldName = this . getAttribute ( "data-field" ) ;
var fieldType = this . getAttribute ( "data-type" ) ;
2018-01-28 14:30:24 +00:00
if ( fieldType == "list" ) {
2017-10-30 09:57:08 +00:00
var fieldValue = this . getAttribute ( "data-value" ) ;
2018-02-22 02:27:17 +00:00
if ( fieldName in formVars ) var it = formVars [ fieldName ] ;
2017-01-31 05:13:38 +00:00
else var it = [ 'No' , 'Yes' ] ;
var itLen = it . length ;
var out = "" ;
2017-06-05 11:57:27 +00:00
for ( var i = 0 ; i < itLen ; i ++ ) {
2017-09-25 00:48:35 +00:00
var sel = "" ;
2017-10-30 09:57:08 +00:00
if ( fieldValue == i || fieldValue == it [ i ] ) {
2017-05-29 14:52:37 +00:00
sel = "selected " ;
2017-10-30 09:57:08 +00:00
this . classList . remove ( fieldName + '_' + it [ i ] ) ;
2017-05-29 14:52:37 +00:00
this . innerHTML = "" ;
2017-09-25 00:48:35 +00:00
}
2017-01-31 05:13:38 +00:00
out += "<option " + sel + "value='" + i + "'>" + it [ i ] + "</option>" ;
}
2017-10-30 09:57:08 +00:00
this . innerHTML = "<select data-field='" + fieldName + "' name='" + fieldName + "'>" + out + "</select>" ;
2017-01-31 05:13:38 +00:00
}
2017-10-30 09:57:08 +00:00
else if ( fieldType == "hidden" ) { }
else this . innerHTML = "<input name='" + fieldName + "' value='" + this . textContent + "' type='text'/>" ;
2017-01-31 05:13:38 +00:00
} ) ;
2017-05-29 14:52:37 +00:00
// Remove any handlers already attached to the submitter
$ ( ".submit_edit" ) . unbind ( "click" ) ;
2017-01-31 05:13:38 +00:00
$ ( ".submit_edit" ) . click ( function ( event )
{
event . preventDefault ( ) ;
2017-10-30 09:57:08 +00:00
var outData = { isJs : "1" }
var blockParent = $ ( this ) . closest ( '.editable_parent' ) ;
blockParent . find ( '.editable_block' ) . each ( function ( ) {
var fieldName = this . getAttribute ( "data-field" ) ;
var fieldType = this . getAttribute ( "data-type" ) ;
if ( fieldType == "list" ) {
2017-05-29 14:52:37 +00:00
var newContent = $ ( this ) . find ( 'select :selected' ) . text ( ) ;
2017-10-30 09:57:08 +00:00
this . classList . add ( fieldName + '_' + newContent ) ;
2017-05-29 14:52:37 +00:00
this . innerHTML = "" ;
2017-10-30 09:57:08 +00:00
} else if ( fieldType == "hidden" ) {
2017-06-05 11:57:27 +00:00
var newContent = $ ( this ) . val ( ) ;
2017-05-29 14:52:37 +00:00
} else {
var newContent = $ ( this ) . find ( 'input' ) . eq ( 0 ) . val ( ) ;
this . innerHTML = newContent ;
}
this . setAttribute ( "data-value" , newContent ) ;
2017-10-30 09:57:08 +00:00
outData [ fieldName ] = newContent ;
2017-01-31 05:13:38 +00:00
} ) ;
2017-05-29 14:52:37 +00:00
2017-10-30 09:57:08 +00:00
var formAction = $ ( this ) . closest ( 'a' ) . attr ( "href" ) ;
//console.log("Form Action:", formAction);
//console.log(outData);
2018-08-11 15:53:42 +00:00
$ . ajax ( { url : formAction + "?session=" + me . User . Session , type : "POST" , dataType : "json" , data : outData , error : ajaxError } ) ;
2018-09-02 05:43:17 +00:00
blockParent . find ( '.hide_on_edit' ) . removeClass ( "edit_opened" ) ;
blockParent . find ( '.show_on_edit' ) . removeClass ( "edit_opened" ) ;
2017-01-31 05:13:38 +00:00
} ) ;
} ) ;
2017-05-29 14:52:37 +00:00
2017-08-18 12:16:56 +00:00
// This one's for Tempra Conflux
2017-09-10 16:57:22 +00:00
// TODO: We might want to use pure JS here
2017-02-16 06:47:55 +00:00
$ ( ".ip_item" ) . each ( function ( ) {
var ip = this . textContent ;
2017-01-17 07:55:46 +00:00
if ( ip . length > 10 ) {
2017-02-16 06:47:55 +00:00
this . innerHTML = "Show IP" ;
2017-10-30 09:57:08 +00:00
this . onclick = function ( event ) {
2017-01-17 07:55:46 +00:00
event . preventDefault ( ) ;
2017-02-16 06:47:55 +00:00
this . textContent = ip ;
} ;
2017-01-17 07:55:46 +00:00
}
} ) ;
2017-05-29 14:52:37 +00:00
2018-03-31 05:25:27 +00:00
$ ( this ) . click ( ( ) => {
2017-03-03 16:28:49 +00:00
$ ( ".selectedAlert" ) . removeClass ( "selectedAlert" ) ;
2017-06-05 11:57:27 +00:00
$ ( "#back" ) . removeClass ( "alertActive" ) ;
2018-09-27 09:41:35 +00:00
$ ( ".link_select" ) . removeClass ( "link_opened" ) ;
2017-06-05 11:57:27 +00:00
} ) ;
2018-09-27 09:41:35 +00:00
2017-06-05 11:57:27 +00:00
$ ( ".alert_bell" ) . click ( function ( ) {
2017-10-30 09:57:08 +00:00
var menuAlerts = $ ( this ) . parent ( ) ;
if ( menuAlerts . hasClass ( "selectedAlert" ) ) {
2017-06-05 11:57:27 +00:00
event . stopPropagation ( ) ;
2017-10-30 09:57:08 +00:00
menuAlerts . removeClass ( "selectedAlert" ) ;
2017-06-05 11:57:27 +00:00
$ ( "#back" ) . removeClass ( "alertActive" ) ;
}
2017-03-03 16:28:49 +00:00
} ) ;
2017-03-01 11:36:50 +00:00
$ ( ".menu_alerts" ) . click ( function ( event ) {
2017-03-03 16:28:49 +00:00
event . stopPropagation ( ) ;
2017-03-01 11:36:50 +00:00
if ( $ ( this ) . hasClass ( "selectedAlert" ) ) return ;
2017-10-30 09:57:08 +00:00
if ( ! conn ) loadAlerts ( this ) ;
2017-06-05 11:57:27 +00:00
this . className += " selectedAlert" ;
document . getElementById ( "back" ) . className += " alertActive"
2017-03-01 11:36:50 +00:00
} ) ;
2018-09-27 09:41:35 +00:00
$ ( ".link_select" ) . click ( event => event . stopPropagation ( ) ) ;
2017-05-29 14:52:37 +00:00
2018-03-31 05:25:27 +00:00
$ ( "input,textarea,select,option" ) . keyup ( event => event . stopPropagation ( ) )
2017-06-16 10:41:30 +00:00
2018-02-05 10:29:13 +00:00
$ ( ".create_topic_link" ) . click ( ( event ) => {
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
event . preventDefault ( ) ;
$ ( ".topic_create_form" ) . show ( ) ;
} ) ;
2018-02-05 10:29:13 +00:00
$ ( ".topic_create_form .close_form" ) . click ( ( event ) => {
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
event . preventDefault ( ) ;
$ ( ".topic_create_form" ) . hide ( ) ;
} ) ;
2018-12-27 05:42:41 +00:00
function uploadFileHandler ( fileList , maxFiles = 5 , step1 = ( ) => { } , step2 = ( ) => { } ) {
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
let files = [ ] ;
2018-10-06 13:14:11 +00:00
for ( var i = 0 ; i < fileList . length && i < 5 ; i ++ ) {
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
files [ i ] = fileList [ i ] ;
2018-10-06 13:14:11 +00:00
}
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
2018-06-17 07:28:18 +00:00
let totalSize = 0 ;
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
for ( let i = 0 ; i < files . length ; i ++ ) {
console . log ( "files[" + i + "]" , files [ i ] ) ;
2018-06-17 07:28:18 +00:00
totalSize += files [ i ] [ "size" ] ;
2018-12-27 05:42:41 +00:00
}
if ( totalSize > me . Site . MaxRequestSize ) {
throw ( "You can't upload this much at once, max: " + me . Site . MaxRequestSize ) ;
}
2018-06-17 07:28:18 +00:00
2018-12-27 05:42:41 +00:00
for ( let i = 0 ; i < files . length ; i ++ ) {
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
let reader = new FileReader ( ) ;
2018-12-27 05:42:41 +00:00
reader . onload = ( e ) => {
let filename = files [ i ] [ "name" ] ;
step1 ( e , filename )
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
let reader = new FileReader ( ) ;
2018-12-27 05:42:41 +00:00
reader . onload = ( e2 ) => {
crypto . subtle . digest ( 'SHA-256' , e2 . target . result )
. then ( ( hash ) => {
2018-06-30 03:40:50 +00:00
const hashArray = Array . from ( new Uint8Array ( hash ) )
return hashArray . map ( b => ( '00' + b . toString ( 16 ) ) . slice ( - 2 ) ) . join ( '' )
2018-12-27 05:42:41 +00:00
} ) . then ( hash => step2 ( e , hash , filename ) ) ;
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
}
reader . readAsArrayBuffer ( files [ i ] ) ;
}
reader . readAsDataURL ( files [ i ] ) ;
}
2018-12-27 05:42:41 +00:00
}
// TODO: Surely, there's a prettier and more elegant way of doing this?
function getExt ( filename ) {
if ( ! filename . indexOf ( '.' > - 1 ) ) {
throw ( "This file doesn't have an extension" ) ;
}
return filename . split ( '.' ) . pop ( ) ;
}
// Attachment Manager
function uploadAttachHandler2 ( ) {
let fileDock = this . closest ( ".attach_edit_bay" ) ;
try {
uploadFileHandler ( this . files , 5 , ( ) => { } ,
( e , hash , filename ) => {
console . log ( "hash" , hash ) ;
let formData = new FormData ( ) ;
formData . append ( "session" , me . User . Session ) ;
for ( let i = 0 ; i < this . files . length ; i ++ ) {
formData . append ( "upload_files" , this . files [ i ] ) ;
}
let req = new XMLHttpRequest ( ) ;
req . addEventListener ( "load" , ( ) => {
let data = JSON . parse ( req . responseText ) ;
let fileItem = document . createElement ( "div" ) ;
let ext = getExt ( filename ) ;
// TODO: Check if this is actually an image, maybe push ImageFileExts to the client from the server in some sort of gen.js?
// TODO: Use client templates here
fileItem . className = "attach_item attach_image_holder" ;
fileItem . innerHTML = "<img src='" + e . target . result + "' height=24 width=24 /><span class='attach_item_path' aid='" + data [ hash + "." + ext ] + "' fullpath='//" + window . location . host + "/attachs/" + hash + "." + ext + "'>" + hash + "." + ext + "</span><button class='attach_item_select'>Select</button><button class='attach_item_copy'>Copy</button>" ;
fileDock . insertBefore ( fileItem , fileDock . querySelector ( ".attach_item_buttons" ) ) ;
$ ( ".attach_item_select" ) . unbind ( "click" ) ;
$ ( ".attach_item_copy" ) . unbind ( "click" ) ;
bindAttachItems ( )
} ) ;
2018-12-31 09:03:49 +00:00
req . open ( "POST" , "//" + window . location . host + "/" + fileDock . getAttribute ( "type" ) + "/attach/add/submit/" + fileDock . getAttribute ( "id" ) ) ;
2018-12-27 05:42:41 +00:00
req . send ( formData ) ;
} ) ;
} catch ( e ) {
// TODO: Use a notice instead
alert ( e ) ;
}
}
// Quick Topic / Quick Reply
function uploadAttachHandler ( ) {
try {
uploadFileHandler ( this . files , 5 , ( e , filename ) => {
// TODO: Use client templates here
let fileDock = document . getElementById ( "upload_file_dock" ) ;
let fileItem = document . createElement ( "label" ) ;
console . log ( "fileItem" , fileItem ) ;
let ext = getExt ( filename )
fileItem . innerText = "." + ext ;
fileItem . className = "formbutton uploadItem" ;
// TODO: Check if this is actually an image
fileItem . style . backgroundImage = "url(" + e . target . result + ")" ;
fileDock . appendChild ( fileItem ) ;
} , ( e , hash , filename ) => {
console . log ( "hash" , hash ) ;
let ext = getExt ( filename )
let content = document . getElementById ( "input_content" )
console . log ( "content.value" , content . value ) ;
let attachItem ;
if ( content . value == "" ) attachItem = "//" + window . location . host + "/attachs/" + hash + "." + ext ;
else attachItem = "\r\n//" + window . location . host + "/attachs/" + hash + "." + ext ;
content . value = content . value + attachItem ;
console . log ( "content.value" , content . value ) ;
// For custom / third party text editors
attachItemCallback ( attachItem ) ;
} ) ;
} catch ( e ) {
2018-06-30 03:40:50 +00:00
// TODO: Use a notice instead
2018-12-27 05:42:41 +00:00
alert ( e ) ;
2018-06-17 07:28:18 +00:00
}
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
}
2018-12-31 09:03:49 +00:00
let uploadFiles = document . getElementById ( "upload_files" ) ;
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
if ( uploadFiles != null ) {
2018-12-27 05:42:41 +00:00
uploadFiles . addEventListener ( "change" , uploadAttachHandler , false ) ;
}
2018-12-31 09:03:49 +00:00
let uploadFilesOp = document . getElementById ( "upload_files_op" ) ;
2018-12-27 05:42:41 +00:00
if ( uploadFilesOp != null ) {
uploadFilesOp . addEventListener ( "change" , uploadAttachHandler2 , false ) ;
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
}
2018-12-31 09:03:49 +00:00
let uploadFilesPost = document . getElementsByClassName ( "upload_files_post" ) ;
if ( uploadFilesPost != null ) {
for ( let i = 0 ; i < uploadFilesPost . length ; i ++ ) {
uploadFilesPost [ i ] . addEventListener ( "change" , uploadAttachHandler2 , false ) ;
}
}
2018-12-27 05:42:41 +00:00
function copyToClipboard ( str ) {
const el = document . createElement ( 'textarea' ) ;
el . value = str ;
el . setAttribute ( 'readonly' , '' ) ;
el . style . position = 'absolute' ;
el . style . left = '-9999px' ;
document . body . appendChild ( el ) ;
el . select ( ) ;
document . execCommand ( 'copy' ) ;
document . body . removeChild ( el ) ;
}
function bindAttachItems ( ) {
$ ( ".attach_item_select" ) . click ( function ( ) {
let hold = $ ( this ) . closest ( ".attach_item" ) ;
if ( hold . hasClass ( "attach_item_selected" ) ) {
hold . removeClass ( "attach_item_selected" ) ;
} else {
hold . addClass ( "attach_item_selected" ) ;
}
} ) ;
$ ( ".attach_item_copy" ) . click ( function ( ) {
let hold = $ ( this ) . closest ( ".attach_item" ) ;
let pathNode = hold . find ( ".attach_item_path" ) ;
copyToClipboard ( pathNode . attr ( "fullPath" ) ) ;
} ) ;
}
bindAttachItems ( ) ;
$ ( ".attach_item_delete" ) . click ( function ( ) {
let formData = new URLSearchParams ( ) ;
formData . append ( "session" , me . User . Session ) ;
let aidList = "" ;
let elems = document . getElementsByClassName ( "attach_item_selected" ) ;
if ( elems == null ) return ;
for ( let i = 0 ; i < elems . length ; i ++ ) {
let pathNode = elems [ i ] . querySelector ( ".attach_item_path" ) ;
console . log ( "pathNode" , pathNode ) ;
aidList += pathNode . getAttribute ( "aid" ) + "," ;
elems [ i ] . remove ( ) ;
}
if ( aidList . length > 0 ) aidList = aidList . slice ( 0 , - 1 ) ;
console . log ( "aidList" , aidList )
formData . append ( "aids" , aidList ) ;
let req = new XMLHttpRequest ( ) ;
let fileDock = this . closest ( ".attach_edit_bay" ) ;
2018-12-31 09:03:49 +00:00
req . open ( "POST" , "//" + window . location . host + "/" + fileDock . getAttribute ( "type" ) + "/attach/remove/submit/" + fileDock . getAttribute ( "id" ) , true ) ;
2018-12-27 05:42:41 +00:00
req . send ( formData ) ;
} ) ;
2017-10-30 09:57:08 +00:00
2018-05-27 09:36:35 +00:00
$ ( ".moderate_link" ) . click ( ( event ) => {
2017-10-30 09:57:08 +00:00
event . preventDefault ( ) ;
$ ( ".pre_opt" ) . removeClass ( "auto_hide" ) ;
2018-05-27 09:36:35 +00:00
$ ( ".moderate_link" ) . addClass ( "moderate_open" ) ;
2017-10-30 09:57:08 +00:00
$ ( ".topic_row" ) . each ( function ( ) {
$ ( this ) . click ( function ( ) {
selectedTopics . push ( parseInt ( $ ( this ) . attr ( "data-tid" ) , 10 ) ) ;
if ( selectedTopics . length == 1 ) {
2018-12-27 05:42:41 +00:00
var msg = "What do you want to do with this topic?" ;
2017-10-30 09:57:08 +00:00
} else {
2018-12-27 05:42:41 +00:00
var msg = "What do you want to do with these " + selectedTopics . length + " topics?" ;
2017-10-30 09:57:08 +00:00
}
2018-12-27 05:42:41 +00:00
$ ( ".mod_floater_head span" ) . html ( msg ) ;
2017-10-30 09:57:08 +00:00
$ ( this ) . addClass ( "topic_selected" ) ;
$ ( ".mod_floater" ) . removeClass ( "auto_hide" ) ;
} ) ;
} ) ;
2018-01-14 12:03:20 +00:00
2018-01-15 08:24:18 +00:00
let bulkActionSender = function ( action , selectedTopics , fragBit ) {
2018-08-11 15:53:42 +00:00
let url = "/topic/" + action + "/submit/" + fragBit + "?session=" + me . User . Session ;
2018-01-14 12:03:20 +00:00
$ . ajax ( {
url : url ,
type : "POST" ,
data : JSON . stringify ( selectedTopics ) ,
contentType : "application/json" ,
error : ajaxError ,
2018-02-05 10:29:13 +00:00
success : ( ) => {
2018-01-14 12:03:20 +00:00
window . location . reload ( ) ;
}
} ) ;
} ;
2017-10-30 09:57:08 +00:00
$ ( ".mod_floater_submit" ) . click ( function ( event ) {
event . preventDefault ( ) ;
let selectNode = this . form . querySelector ( ".mod_floater_options" ) ;
let optionNode = selectNode . options [ selectNode . selectedIndex ] ;
let action = optionNode . getAttribute ( "val" ) ;
2018-01-11 08:03:17 +00:00
// Handle these specially
switch ( action ) {
case "move" :
console . log ( "move action" ) ;
2018-01-14 12:03:20 +00:00
let modTopicMover = $ ( "#mod_topic_mover" ) ;
2018-01-11 08:03:17 +00:00
$ ( "#mod_topic_mover" ) . removeClass ( "auto_hide" ) ;
2018-01-14 12:03:20 +00:00
$ ( "#mod_topic_mover .pane_row" ) . click ( function ( ) {
modTopicMover . find ( ".pane_row" ) . removeClass ( "pane_selected" ) ;
let fid = this . getAttribute ( "data-fid" ) ;
if ( fid == null ) {
return ;
}
this . classList . add ( "pane_selected" ) ;
console . log ( "fid: " + fid ) ;
2018-01-15 08:24:18 +00:00
forumToMoveTo = fid ;
$ ( "#mover_submit" ) . click ( function ( event ) {
event . preventDefault ( ) ;
bulkActionSender ( "move" , selectedTopics , forumToMoveTo ) ;
} ) ;
2018-01-14 12:03:20 +00:00
} ) ;
2018-01-11 08:03:17 +00:00
return ;
}
2017-10-30 09:57:08 +00:00
2018-01-15 08:24:18 +00:00
bulkActionSender ( action , selectedTopics , "" ) ;
2017-10-30 09:57:08 +00:00
} ) ;
} ) ;
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
2017-09-10 16:57:22 +00:00
$ ( "#themeSelectorSelect" ) . change ( function ( ) {
console . log ( "Changing the theme to " + this . options [ this . selectedIndex ] . getAttribute ( "val" ) ) ;
$ . ajax ( {
2018-08-11 15:53:42 +00:00
url : this . form . getAttribute ( "action" ) + "?session=" + me . User . Session ,
2017-09-10 16:57:22 +00:00
type : "POST" ,
dataType : "json" ,
data : { "newTheme" : this . options [ this . selectedIndex ] . getAttribute ( "val" ) , isJs : "1" } ,
2017-10-30 09:57:08 +00:00
error : ajaxError ,
2017-09-10 16:57:22 +00:00
success : function ( data , status , xhr ) {
console . log ( "Theme successfully switched" ) ;
2017-10-30 09:57:08 +00:00
console . log ( "data" , data ) ;
console . log ( "status" , status ) ;
console . log ( "xhr" , xhr ) ;
2017-09-10 16:57:22 +00:00
window . location . reload ( ) ;
}
} ) ;
} ) ;
2018-01-08 08:53:51 +00:00
// The time range selector for the time graphs in the Control Panel
$ ( ".timeRangeSelector" ) . change ( function ( ) {
console . log ( "Changed the time range to " + this . options [ this . selectedIndex ] . getAttribute ( "val" ) ) ;
window . location = this . form . getAttribute ( "action" ) + "?timeRange=" + this . options [ this . selectedIndex ] . getAttribute ( "val" ) ; // Do a redirect as a form submission refuses to work properly
} ) ;
2018-01-10 03:32:48 +00:00
$ ( ".unix_to_24_hour_time" ) . each ( function ( ) {
let unixTime = this . innerText ;
let date = new Date ( unixTime * 1000 ) ;
console . log ( "date: " , date ) ;
let minutes = "0" + date . getMinutes ( ) ;
let formattedTime = date . getHours ( ) + ":" + minutes . substr ( - 2 ) ;
console . log ( "formattedTime:" , formattedTime ) ;
2019-02-24 01:29:06 +00:00
this . innerText = formattedTime ;
} ) ;
$ ( ".unix_to_date" ) . each ( function ( ) {
// TODO: Localise this
let monthList = [ 'Jan' , 'Feb' , 'Mar' , 'Apr' , 'May' , 'Jun' , 'Jul' , 'Aug' , 'Sep' , 'Oct' , 'Nov' , 'Dec' ] ;
let date = new Date ( this . innerText * 1000 ) ;
console . log ( "date: " , date ) ;
let day = "0" + date . getDate ( ) ;
let formattedTime = monthList [ date . getMonth ( ) ] + " " + day . substr ( - 2 ) + " " + date . getFullYear ( ) ;
console . log ( "formattedTime:" , formattedTime ) ;
2018-01-10 03:32:48 +00:00
this . innerText = formattedTime ;
} ) ;
2017-06-16 10:41:30 +00:00
this . onkeyup = function ( event ) {
2017-02-16 06:47:55 +00:00
if ( event . which == 37 ) this . querySelectorAll ( "#prevFloat a" ) [ 0 ] . click ( ) ;
if ( event . which == 39 ) this . querySelectorAll ( "#nextFloat a" ) [ 0 ] . click ( ) ;
} ;
2018-01-08 08:53:51 +00:00
2018-01-26 05:53:34 +00:00
function addPollInput ( ) {
console . log ( "clicked on pollinputinput" ) ;
let dataPollInput = $ ( this ) . parent ( ) . attr ( "data-pollinput" ) ;
console . log ( "dataPollInput: " , dataPollInput ) ;
if ( dataPollInput == undefined ) return ;
if ( dataPollInput != ( pollInputIndex - 1 ) ) return ;
2018-02-03 05:47:14 +00:00
$ ( ".poll_content_row .formitem" ) . append ( "<div class='pollinput' data-pollinput='" + pollInputIndex + "'><input type='checkbox' disabled /><label class='pollinputlabel'></label><input form='quick_post_form' name='pollinputitem[" + pollInputIndex + "]' class='pollinputinput' type='text' placeholder='Add new poll option' /></div>" ) ;
2018-01-26 05:53:34 +00:00
pollInputIndex ++ ;
console . log ( "new pollInputIndex: " , pollInputIndex ) ;
$ ( ".pollinputinput" ) . off ( "click" ) ;
$ ( ".pollinputinput" ) . click ( addPollInput ) ;
}
var pollInputIndex = 1 ;
2018-02-05 10:29:13 +00:00
$ ( "#add_poll_button" ) . click ( ( event ) => {
2018-01-25 04:57:33 +00:00
event . preventDefault ( ) ;
$ ( ".poll_content_row" ) . removeClass ( "auto_hide" ) ;
2018-01-26 05:53:34 +00:00
$ ( "#has_poll_input" ) . val ( "1" ) ;
$ ( ".pollinputinput" ) . click ( addPollInput ) ;
2018-01-25 04:57:33 +00:00
} ) ;
2018-01-28 14:30:24 +00:00
2018-02-03 05:47:14 +00:00
//id="poll_results_{{.Poll.ID}}" class="poll_results auto_hide"
2018-01-28 14:30:24 +00:00
$ ( ".poll_results_button" ) . click ( function ( ) {
let pollID = $ ( this ) . attr ( "data-poll-id" ) ;
$ ( "#poll_results_" + pollID + " .user_content" ) . html ( "<div id='poll_results_chart_" + pollID + "'></div>" ) ;
2018-02-03 05:47:14 +00:00
$ ( "#poll_results_" + pollID ) . removeClass ( "auto_hide" ) ;
2018-01-28 14:30:24 +00:00
fetch ( "/poll/results/" + pollID , {
credentials : 'same-origin'
} ) . then ( ( response ) => response . text ( ) ) . catch ( ( error ) => console . error ( "Error:" , error ) ) . then ( ( rawData ) => {
// TODO: Make sure the received data is actually a list of integers
let data = JSON . parse ( rawData ) ;
console . log ( "rawData: " , rawData ) ;
console . log ( "series: " , data ) ;
Chartist . Pie ( '#poll_results_chart_' + pollID , {
series : data ,
} , {
2018-02-03 05:47:14 +00:00
height : '120px' ,
2018-01-28 14:30:24 +00:00
} ) ;
} )
} ) ;
2018-02-22 02:27:17 +00:00
2018-08-13 10:34:00 +00:00
runInitHook ( "end_init" ) ;
2018-08-11 15:53:42 +00:00
} ;