Merge branch 'v3'
Conflicts: builds/crx/script.js src/General/Config.coffee src/General/Settings.coffee src/Miscellaneous/ExpandComment.coffee src/Monitoring/ThreadWatcher.coffee
This commit is contained in:
commit
6c2c9b281a
12
CHANGELOG.md
12
CHANGELOG.md
@ -16,6 +16,18 @@
|
|||||||
**MayhemYDG**:
|
**MayhemYDG**:
|
||||||
- **New feature**: `Show Dice Roll` (with @carboncopy)
|
- **New feature**: `Show Dice Roll` (with @carboncopy)
|
||||||
- Shows dice that were entered into the email field on /tg/.
|
- Shows dice that were entered into the email field on /tg/.
|
||||||
|
- **Thread Watcher** improvements:
|
||||||
|
- It is now possible to open all watched threads via the `Open all threads` button in the Thread Watcher's menu.
|
||||||
|
- Added the `Current Board` setting to switch between showing watched threads from the current board or all boards, disabled by default.
|
||||||
|
- About dead (404'd) threads:
|
||||||
|
- Dead threads will be typographically indicated with a strikethrough.
|
||||||
|
- Dead threads will directly link to the corresponding archive when available.
|
||||||
|
- A button to prune all 404'd threads from the list is now available.
|
||||||
|
- Added the `Auto Prune` setting to automatically prune 404'd threads, disabled by default.
|
||||||
|
- The current thread is now highlighted in the list of watched threads.
|
||||||
|
- Watching the current thread can be done in the Header's menu too.
|
||||||
|
- Removed the `Check for Updates` setting:
|
||||||
|
- Your browser/userscript manager should handle updates itself automatically.
|
||||||
- Fix impossibility to create new threads when in dead threads.
|
- Fix impossibility to create new threads when in dead threads.
|
||||||
- Fix flag filtering on /sp/ and /int/.
|
- Fix flag filtering on /sp/ and /int/.
|
||||||
- Update archives. (with @woxxy and @proplex)
|
- Update archives. (with @woxxy and @proplex)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
929
css/style.css
Normal file
929
css/style.css
Normal file
@ -0,0 +1,929 @@
|
|||||||
|
/* General */
|
||||||
|
.dialog {
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
|
||||||
|
border: 1px solid;
|
||||||
|
display: block;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.field {
|
||||||
|
background-color: #FFF;
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #333;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 13px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 2px 4px 3px;
|
||||||
|
outline: none;
|
||||||
|
transition: color .25s, border-color .25s, -webkit-flex .25s;
|
||||||
|
transition: color .25s, border-color .25s, flex .25s;
|
||||||
|
}
|
||||||
|
.field::-moz-placeholder,
|
||||||
|
.field:hover::-moz-placeholder {
|
||||||
|
color: #AAA !important;
|
||||||
|
}
|
||||||
|
.field:hover {
|
||||||
|
border-color: #999;
|
||||||
|
}
|
||||||
|
.field:hover, .field:focus {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.field[disabled] {
|
||||||
|
background-color: #F2F2F2;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
.move {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
label, .watcher-toggler {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
a[href="javascript:;"] {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4chan style fixes */
|
||||||
|
.opContainer, .op {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
.post {
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fixed, z-index */
|
||||||
|
#overlay,
|
||||||
|
#qp, #ihover,
|
||||||
|
#updater, #thread-stats,
|
||||||
|
#navlinks, #header,
|
||||||
|
#qr {
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
#overlay {
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
#notifications {
|
||||||
|
z-index: 70;
|
||||||
|
}
|
||||||
|
#qp, #ihover {
|
||||||
|
z-index: 60;
|
||||||
|
}
|
||||||
|
#menu {
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
#navlinks, #updater, #thread-stats {
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
#qr {
|
||||||
|
z-index: 30;
|
||||||
|
}
|
||||||
|
#thread-watcher:hover {
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
#header {
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
#thread-watcher {
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
:root.top-header body {
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
:root.bottom-header body {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
}
|
||||||
|
:root.fourchan-x #navtopright,
|
||||||
|
:root.fourchan-x #navbotright,
|
||||||
|
:root.fourchan-x:not(.show-original-top-board-list) #boardNavDesktop,
|
||||||
|
:root.fourchan-x:not(.show-original-bot-board-list) #boardNavDesktopFoot {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
#header {
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
#header.top {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
#header.bottom {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
#header-bar {
|
||||||
|
border-width: 0;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
padding: 3px 4px 4px;
|
||||||
|
position: relative;
|
||||||
|
transition: all .1s .05s ease-in-out;
|
||||||
|
}
|
||||||
|
#header.top #header-bar {
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
#header.bottom #header-bar {
|
||||||
|
box-shadow: 0 -1px 2px rgba(0, 0, 0, .15);
|
||||||
|
border-top-width: 1px;
|
||||||
|
}
|
||||||
|
#header.bottom .menu-button i {
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: 6px solid;
|
||||||
|
}
|
||||||
|
#board-list {
|
||||||
|
-webkit-flex: 1;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#header-bar.autohide:not(:hover) {
|
||||||
|
box-shadow: none;
|
||||||
|
transition: all .8s .6s cubic-bezier(.55, .055, .675, .19);
|
||||||
|
}
|
||||||
|
#header.top #header-bar.autohide:not(:hover) {
|
||||||
|
margin-bottom: -1em;
|
||||||
|
-webkit-transform: translateY(-100%);
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
#header.bottom #header-bar.autohide:not(:hover) {
|
||||||
|
-webkit-transform: translateY(100%);
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
#toggle-header-bar {
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 10px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
#header.top #toggle-header-bar {
|
||||||
|
cursor: n-resize;
|
||||||
|
bottom: -8px;
|
||||||
|
}
|
||||||
|
#header.bottom #toggle-header-bar {
|
||||||
|
cursor: s-resize;
|
||||||
|
top: -8px;
|
||||||
|
}
|
||||||
|
#header-bar.autohide:not(:hover) #toggle-header-bar,
|
||||||
|
#toggle-header-bar:hover {
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
#header.top #header-bar.autohide:not(:hover) #toggle-header-bar,
|
||||||
|
#header.top #toggle-header-bar:hover {
|
||||||
|
bottom: -16px;
|
||||||
|
}
|
||||||
|
#header.bottom #header-bar.autohide:not(:hover) #toggle-header-bar,
|
||||||
|
#header.bottom #toggle-header-bar:hover {
|
||||||
|
top: -16px;
|
||||||
|
}
|
||||||
|
#header.top #header-bar.autohide #toggle-header-bar {
|
||||||
|
cursor: s-resize;
|
||||||
|
}
|
||||||
|
#header.bottom #header-bar.autohide #toggle-header-bar {
|
||||||
|
cursor: n-resize;
|
||||||
|
}
|
||||||
|
#header-bar a:not(.entry) {
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
#shortcuts:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.shortcut:not(:last-child)::after {
|
||||||
|
content: " / ";
|
||||||
|
}
|
||||||
|
.brackets-wrap::before {
|
||||||
|
content: "\\00a0[";
|
||||||
|
}
|
||||||
|
.brackets-wrap::after {
|
||||||
|
content: "]\\00a0";
|
||||||
|
}
|
||||||
|
.expand-all-shortcut {
|
||||||
|
opacity: .35;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Notifications */
|
||||||
|
#notifications {
|
||||||
|
height: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#header.bottom #notifications {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.notification {
|
||||||
|
color: #FFF;
|
||||||
|
font-weight: 700;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, .5);
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
|
||||||
|
border-radius: 2px;
|
||||||
|
margin: 1px auto;
|
||||||
|
width: 500px;
|
||||||
|
max-width: 100%;
|
||||||
|
position: relative;
|
||||||
|
transition: all .25s ease-in-out;
|
||||||
|
}
|
||||||
|
.notification.error {
|
||||||
|
background-color: hsla(0, 100%, 38%, .9);
|
||||||
|
}
|
||||||
|
.notification.warning {
|
||||||
|
background-color: hsla(36, 100%, 38%, .9);
|
||||||
|
}
|
||||||
|
.notification.info {
|
||||||
|
background-color: hsla(200, 100%, 38%, .9);
|
||||||
|
}
|
||||||
|
.notification.success {
|
||||||
|
background-color: hsla(104, 100%, 38%, .9);
|
||||||
|
}
|
||||||
|
.notification a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.notification > .close {
|
||||||
|
padding: 6px;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 6px 20px;
|
||||||
|
max-height: 200px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Settings */
|
||||||
|
:root.fourchan-x body {
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
#overlay {
|
||||||
|
background-color: rgba(0, 0, 0, .5);
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#fourchanx-settings {
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
box-shadow: 0 0 15px rgba(0, 0, 0, .15);
|
||||||
|
height: 600px;
|
||||||
|
max-height: 100%;
|
||||||
|
width: 900px;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: auto;
|
||||||
|
padding: 3px;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
-webkit-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
#fourchanx-settings > nav {
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
padding: 2px 2px 0;
|
||||||
|
}
|
||||||
|
#fourchanx-settings > nav a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
#fourchanx-settings > nav a.close {
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
.sections-list {
|
||||||
|
-webkit-flex: 1;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.tab-selected {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.section-container {
|
||||||
|
-webkit-flex: 1;
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.section-container > section {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.section-sauce ul,
|
||||||
|
.section-rice ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
.section-sauce li,
|
||||||
|
.section-rice li {
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
.section-main label {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.section-filter ul,
|
||||||
|
.section-qr ul {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.section-filter li,
|
||||||
|
.section-qr li {
|
||||||
|
margin: 10px 40px;
|
||||||
|
}
|
||||||
|
.section-filter textarea {
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
.section-qr textarea {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
.section-sauce textarea {
|
||||||
|
height: 350px;
|
||||||
|
}
|
||||||
|
.section-rice .field[name="boardnav"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.section-rice textarea {
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
.section-archives table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.section-archives th:not(:first-child) {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
.section-archives td {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.section-archives select {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
.section-keybinds .field {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
#fourchanx-settings fieldset {
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
#fourchanx-settings legend {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
#fourchanx-settings textarea {
|
||||||
|
font-family: monospace;
|
||||||
|
min-width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
#fourchanx-settings code {
|
||||||
|
color: #000;
|
||||||
|
background-color: #FFF;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
.unscroll {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Announcement Hiding */
|
||||||
|
:root.hide-announcement #globalMessage,
|
||||||
|
:root.hide-announcement-enabled #toggleMsgBtn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
a.hide-announcement {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unread */
|
||||||
|
#unread-line {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Thread Updater */
|
||||||
|
#updater:not(:hover) {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
#updater > .move {
|
||||||
|
padding: 0 3px;
|
||||||
|
}
|
||||||
|
#updater > div:last-child {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#updater input[type=number] {
|
||||||
|
width: 4em;
|
||||||
|
}
|
||||||
|
#updater:not(:hover) > div:not(.move) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#updater input[type="button"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.new {
|
||||||
|
color: limegreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Thread Watcher */
|
||||||
|
#thread-watcher {
|
||||||
|
max-width: 200px;
|
||||||
|
min-width: 150px;
|
||||||
|
padding: 3px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
#thread-watcher > div:first-child {
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
#thread-watcher .move {
|
||||||
|
-webkit-flex: 1;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
#watcher-status:not(:empty)::before {
|
||||||
|
content: "(";
|
||||||
|
}
|
||||||
|
#watcher-status:not(:empty)::after {
|
||||||
|
content: ")";
|
||||||
|
}
|
||||||
|
#watched-threads:not(:hover) {
|
||||||
|
max-height: 150px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#watched-threads div {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#watched-threads .current {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
#watched-threads a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
#watched-threads .dead-thread a[title] {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Thread Stats */
|
||||||
|
#thread-stats {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quote */
|
||||||
|
.deadlink {
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
.backlink.deadlink:not(.forwardlink),
|
||||||
|
.quotelink.deadlink:not(.forwardlink) {
|
||||||
|
text-decoration: underline !important;
|
||||||
|
}
|
||||||
|
.inlined {
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
#qp input, .forwarded {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.quotelink.forwardlink,
|
||||||
|
.backlink.forwardlink {
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px dashed;
|
||||||
|
}
|
||||||
|
.filtered {
|
||||||
|
text-decoration: underline line-through;
|
||||||
|
}
|
||||||
|
.inline {
|
||||||
|
border: 1px solid;
|
||||||
|
display: table;
|
||||||
|
margin: 2px 0;
|
||||||
|
}
|
||||||
|
.inline .post {
|
||||||
|
border: 0 !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
display: table !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 1px 2px !important;
|
||||||
|
}
|
||||||
|
#qp > .opContainer::after {
|
||||||
|
content: '';
|
||||||
|
clear: both;
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
#qp .post {
|
||||||
|
border: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 2px 2px 5px;
|
||||||
|
}
|
||||||
|
#qp img {
|
||||||
|
max-height: 80vh;
|
||||||
|
max-width: 50vw;
|
||||||
|
}
|
||||||
|
.qphl {
|
||||||
|
outline: 2px solid rgba(216, 94, 49, .7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* File */
|
||||||
|
.fileText:hover .fntrunc,
|
||||||
|
.fileText:not(:hover) .fnfull,
|
||||||
|
.expanded-image > .post > .file > .fileThumb > img[data-md5],
|
||||||
|
:not(.expanded-image) > .post > .file > .fileThumb > .full-image {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.expanding {
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
.expanded-image {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.expanded-image > .op > .file::after {
|
||||||
|
content: '';
|
||||||
|
clear: both;
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
:root.fit-height .full-image {
|
||||||
|
max-height: 100vh;
|
||||||
|
}
|
||||||
|
:root.fit-width .full-image {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
:root.gecko.fit-width .full-image {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#ihover {
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-height: 100%;
|
||||||
|
max-width: 75%;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Index/Reply Navigation */
|
||||||
|
#navlinks {
|
||||||
|
font-size: 16px;
|
||||||
|
top: 25px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Filter */
|
||||||
|
.opContainer.filter-highlight {
|
||||||
|
box-shadow: inset 5px 0 rgba(255, 0, 0, .5);
|
||||||
|
}
|
||||||
|
.filter-highlight > .reply {
|
||||||
|
box-shadow: -5px 0 rgba(255, 0, 0, .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Thread & Reply Hiding */
|
||||||
|
.hide-thread-button,
|
||||||
|
.hide-reply-button {
|
||||||
|
float: left;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
.stub ~ * {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.stub input {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* QR */
|
||||||
|
:root.hide-original-post-form #postForm,
|
||||||
|
:root.hide-original-post-form .postingMode,
|
||||||
|
:root.hide-original-post-form #togglePostForm,
|
||||||
|
#qr.autohide:not(.has-focus):not(:hover) > form {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#qr select, #dump-button, .remove, .captcha-img {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
#qr > div {
|
||||||
|
min-width: 300px;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
#qr .move {
|
||||||
|
-webkit-align-self: stretch;
|
||||||
|
align-self: stretch;
|
||||||
|
-webkit-flex: 1;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
#qr select {
|
||||||
|
margin: 0;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
#qr option {
|
||||||
|
color: #000;
|
||||||
|
background-color: #F7F7F7;
|
||||||
|
}
|
||||||
|
#qr .close {
|
||||||
|
padding: 0 3px;
|
||||||
|
}
|
||||||
|
#qr > form {
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
-webkit-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.persona {
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.persona .field {
|
||||||
|
-webkit-flex: 1;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.persona .field:hover,
|
||||||
|
.persona .field:focus {
|
||||||
|
-webkit-flex: 3;
|
||||||
|
flex: 3;
|
||||||
|
}
|
||||||
|
#dump-button {
|
||||||
|
background: linear-gradient(#EEE, #CCC);
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
margin: 0;
|
||||||
|
padding: 2px 4px 3px;
|
||||||
|
outline: none;
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
#dump-button:hover,
|
||||||
|
#dump-button:focus {
|
||||||
|
background: linear-gradient(#FFF, #DDD);
|
||||||
|
}
|
||||||
|
#dump-button:active,
|
||||||
|
.dump #dump-button:not(:hover):not(:focus) {
|
||||||
|
background: linear-gradient(#CCC, #DDD);
|
||||||
|
}
|
||||||
|
:root.gecko #dump-button {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
#qr:not(.dump) #dump-list-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#dump-list-container {
|
||||||
|
height: 100px;
|
||||||
|
position: relative;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
#dump-list {
|
||||||
|
counter-reset: qrpreviews;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#dump-list:hover {
|
||||||
|
bottom: -12px;
|
||||||
|
overflow-x: auto;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
#dump-list::-webkit-scrollbar {
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
#dump-list::-webkit-scrollbar-thumb {
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
.qr-preview {
|
||||||
|
background-position: 50% 20%;
|
||||||
|
background-size: cover;
|
||||||
|
border: 1px solid #808080;
|
||||||
|
color: #FFF !important;
|
||||||
|
font-size: 12px;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: move;
|
||||||
|
display: inline-block;
|
||||||
|
height: 92px;
|
||||||
|
width: 92px;
|
||||||
|
margin: 4px;
|
||||||
|
padding: 2px;
|
||||||
|
opacity: .6;
|
||||||
|
outline: none;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
text-shadow: 0 1px 1px #000;
|
||||||
|
transition: opacity .25s ease-in-out;
|
||||||
|
vertical-align: top;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
.qr-preview:hover,
|
||||||
|
.qr-preview:focus {
|
||||||
|
opacity: .9;
|
||||||
|
color: #FFF !important;
|
||||||
|
}
|
||||||
|
.qr-preview#selected {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.qr-preview::before {
|
||||||
|
counter-increment: qrpreviews;
|
||||||
|
content: counter(qrpreviews);
|
||||||
|
font-weight: 700;
|
||||||
|
text-shadow: 0 0 3px #000, 0 0 5px #000;
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
right: 3px;
|
||||||
|
}
|
||||||
|
.qr-preview.drag {
|
||||||
|
border-color: red;
|
||||||
|
border-style: dashed;
|
||||||
|
}
|
||||||
|
.qr-preview.over {
|
||||||
|
border-color: #FFF;
|
||||||
|
border-style: dashed;
|
||||||
|
}
|
||||||
|
.remove {
|
||||||
|
color: #E00 !important;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
.remove:hover::after {
|
||||||
|
content: ' Remove';
|
||||||
|
}
|
||||||
|
.qr-preview > label {
|
||||||
|
background: rgba(0, 0, 0, .5);
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.qr-preview > label > input {
|
||||||
|
margin: 1px 0;
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
#add-post {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 30px;
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
line-height: 1;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
#qr textarea {
|
||||||
|
min-height: 160px;
|
||||||
|
min-width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#qr.has-captcha textarea {
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
.textarea {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#char-count {
|
||||||
|
color: #000;
|
||||||
|
background: hsla(0, 0%, 100%, .5);
|
||||||
|
font-size: 8pt;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 1px;
|
||||||
|
right: 1px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
#char-count.warning {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.captcha-img {
|
||||||
|
background: #FFF;
|
||||||
|
outline: 1px solid #CCC;
|
||||||
|
outline-offset: -1px;
|
||||||
|
}
|
||||||
|
.captcha-img > img {
|
||||||
|
display: block;
|
||||||
|
height: 57px;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
#file-n-submit > input {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#file-n-submit.has-file #qr-no-file {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
#file-n-submit:not(.has-file) #qr-filename,
|
||||||
|
#file-n-submit:not(.has-file) #qr-file-spoiler,
|
||||||
|
#file-n-submit:not(.has-file) #qr-filerm {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#file-n-submit {
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
-webkit-flex-direction: row;
|
||||||
|
flex-direction: row;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
#qr-no-file, #qr-filename-container {
|
||||||
|
-webkit-flex: 1;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
#qr-filename-container {
|
||||||
|
cursor: default;
|
||||||
|
position: relative;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
#qr-filename {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#qr-filerm {
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
#file-n-submit > #qr-file-spoiler {
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
#file-n-submit input[type='submit'] {
|
||||||
|
min-width: 40px;
|
||||||
|
-webkit-order: 1;
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu */
|
||||||
|
.menu-button {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.menu-button i {
|
||||||
|
border-top: 6px solid;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
border-left: 4px solid transparent;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 2px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
#menu {
|
||||||
|
border-bottom: 0;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
margin: 2px 0;
|
||||||
|
-webkit-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.entry {
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
padding: 3px 7px;
|
||||||
|
position: relative;
|
||||||
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.entry.disabled {
|
||||||
|
color: graytext !important;
|
||||||
|
}
|
||||||
|
.entry.has-submenu {
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
.has-submenu::after {
|
||||||
|
content: '';
|
||||||
|
border-left: 6px solid;
|
||||||
|
border-top: 4px solid transparent;
|
||||||
|
border-bottom: 4px solid transparent;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 4px;
|
||||||
|
position: absolute;
|
||||||
|
right: 3px;
|
||||||
|
}
|
||||||
|
.has-submenu:not(.focused) > .submenu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.submenu {
|
||||||
|
border-bottom: 0;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
-webkit-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
margin: -1px 0;
|
||||||
|
}
|
||||||
|
.entry input {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
@ -50,8 +50,8 @@
|
|||||||
"http": true,
|
"http": true,
|
||||||
"https": true,
|
"https": true,
|
||||||
"software": "foolfuuka",
|
"software": "foolfuuka",
|
||||||
"boards": ["adv", "asp", "cm", "e", "i", "lgbt", "n", "o", "p", "pol", "s", "s4s", "t", "trv", "y"],
|
"boards": ["adv", "asp", "cm", "d", "e", "i", "lgbt", "n", "o", "p", "pol", "s", "s4s", "t", "trv", "y"],
|
||||||
"files": ["adv", "asp", "cm", "e", "i", "lgbt", "n", "o", "p", "s", "s4s", "t", "trv", "y"]
|
"files": ["cm", "d", "e", "i", "n", "o", "p", "s", "trv", "y"]
|
||||||
}, {
|
}, {
|
||||||
"uid": 12,
|
"uid": 12,
|
||||||
"name": "fap archive",
|
"name": "fap archive",
|
||||||
|
|||||||
@ -50,8 +50,8 @@ Redirect =
|
|||||||
http: true
|
http: true
|
||||||
https: true
|
https: true
|
||||||
software: 'foolfuuka'
|
software: 'foolfuuka'
|
||||||
boards: ['adv', 'asp', 'cm', 'i', 'lgbt', 'n', 'o', 'p', 's4s', 't', 'trv']
|
boards: ['adv', 'asp', 'cm', 'd', 'e', 'i', 'lgbt', 'n', 'o', 'p', 'pol', 's', 's4s', 't', 'trv', 'y']
|
||||||
files: ['adv', 'asp', 'cm', 'i', 'lgbt', 'n', 'o', 'p', 's4s', 't', 'trv']
|
files: ['cm', 'd', 'e', 'i', 'n', 'o', 'p', 's', 'trv', 'y']
|
||||||
|
|
||||||
'Foolz Beta':
|
'Foolz Beta':
|
||||||
domain: 'beta.foolz.us'
|
domain: 'beta.foolz.us'
|
||||||
|
|||||||
@ -27,7 +27,7 @@ Build =
|
|||||||
date: data.now
|
date: data.now
|
||||||
dateUTC: data.time
|
dateUTC: data.time
|
||||||
comment: data.com
|
comment: data.com
|
||||||
capReps: data.capcode_replies
|
capcodeReplies: data.capcode_replies
|
||||||
# thread status
|
# thread status
|
||||||
isSticky: !!data.sticky
|
isSticky: !!data.sticky
|
||||||
isClosed: !!data.closed
|
isClosed: !!data.closed
|
||||||
@ -59,7 +59,7 @@ Build =
|
|||||||
postID, threadID, boardID
|
postID, threadID, boardID
|
||||||
name, capcode, tripcode, uniqueID, email, subject, flagCode, flagName, date, dateUTC
|
name, capcode, tripcode, uniqueID, email, subject, flagCode, flagName, date, dateUTC
|
||||||
isSticky, isClosed
|
isSticky, isClosed
|
||||||
comment, capReps
|
comment, capcodeReplies
|
||||||
file
|
file
|
||||||
} = o
|
} = o
|
||||||
isOP = postID is threadID
|
isOP = postID is threadID
|
||||||
@ -191,26 +191,6 @@ Build =
|
|||||||
else
|
else
|
||||||
''
|
''
|
||||||
|
|
||||||
capcodeReplies = ''
|
|
||||||
if capReps
|
|
||||||
generateCapcodeReplies = (capcodeType, array) ->
|
|
||||||
"<span class=smaller><span class=bold>#{
|
|
||||||
switch capcodeType
|
|
||||||
when 'admin'
|
|
||||||
'Administrator'
|
|
||||||
when 'mod'
|
|
||||||
'Moderator'
|
|
||||||
when 'developer'
|
|
||||||
'Developer'
|
|
||||||
} Repl#{if array.length > 1 then 'ies' else 'y'}:</span> #{
|
|
||||||
array.map (ID) ->
|
|
||||||
"<a href='/#{boardID}/res/#{threadID}#p#{ID}' class=quotelink>>>#{ID}</a>"
|
|
||||||
.join ' '
|
|
||||||
}</span><br>"
|
|
||||||
for capcodeType, array of capReps
|
|
||||||
capcodeReplies += generateCapcodeReplies capcodeType, array
|
|
||||||
capcodeReplies = "<br><br><span class=capcodeReplies>#{capcodeReplies}</span>"
|
|
||||||
|
|
||||||
container = $.el 'div',
|
container = $.el 'div',
|
||||||
id: "pc#{postID}"
|
id: "pc#{postID}"
|
||||||
className: "postContainer #{if isOP then 'op' else 'reply'}Container"
|
className: "postContainer #{if isOP then 'op' else 'reply'}Container"
|
||||||
@ -221,4 +201,36 @@ Build =
|
|||||||
continue if href[0] is '/' # Cross-board quote, or board link
|
continue if href[0] is '/' # Cross-board quote, or board link
|
||||||
quote.href = "/#{boardID}/res/#{href}" # Fix pathnames
|
quote.href = "/#{boardID}/res/#{href}" # Fix pathnames
|
||||||
|
|
||||||
|
Build.capcodeReplies {boardID, threadID, root: container, capcodeReplies}
|
||||||
|
|
||||||
container
|
container
|
||||||
|
|
||||||
|
capcodeReplies: ({boardID, threadID, bq, root, capcodeReplies}) ->
|
||||||
|
return unless capcodeReplies
|
||||||
|
|
||||||
|
generateCapcodeReplies = (capcodeType, array) ->
|
||||||
|
"<span class=smaller><span class=bold>#{
|
||||||
|
switch capcodeType
|
||||||
|
when 'admin'
|
||||||
|
'Administrator'
|
||||||
|
when 'mod'
|
||||||
|
'Moderator'
|
||||||
|
when 'developer'
|
||||||
|
'Developer'
|
||||||
|
} Repl#{if array.length > 1 then 'ies' else 'y'}:</span> #{
|
||||||
|
array.map (ID) ->
|
||||||
|
"<a href='/#{boardID}/res/#{threadID}#p#{ID}' class=quotelink>>>#{ID}</a>"
|
||||||
|
.join ' '
|
||||||
|
}</span><br>"
|
||||||
|
html = []
|
||||||
|
for capcodeType, array of capcodeReplies
|
||||||
|
html.push generateCapcodeReplies capcodeType, array
|
||||||
|
|
||||||
|
bq or= $ 'blockquote', root
|
||||||
|
$.add bq, [
|
||||||
|
$.el 'br'
|
||||||
|
$.el 'br'
|
||||||
|
$.el 'span',
|
||||||
|
className: 'capcodeReplies'
|
||||||
|
innerHTML: html.join ''
|
||||||
|
]
|
||||||
|
|||||||
@ -53,12 +53,6 @@ Config =
|
|||||||
true
|
true
|
||||||
'Show dice that were entered into the email field.'
|
'Show dice that were entered into the email field.'
|
||||||
]
|
]
|
||||||
<% if (type !== 'crx') { %>
|
|
||||||
'Check for Updates': [
|
|
||||||
true
|
|
||||||
'Check for updated versions of <%= meta.name %>.'
|
|
||||||
]
|
|
||||||
<% } %>
|
|
||||||
'Color User IDs': [
|
'Color User IDs': [
|
||||||
false
|
false
|
||||||
'Assign unique colors to user IDs on boards that use them'
|
'Assign unique colors to user IDs on boards that use them'
|
||||||
@ -239,14 +233,6 @@ Config =
|
|||||||
true
|
true
|
||||||
'Bookmark threads.'
|
'Bookmark threads.'
|
||||||
]
|
]
|
||||||
'Auto Watch': [
|
|
||||||
true
|
|
||||||
'Automatically watch threads you start.'
|
|
||||||
]
|
|
||||||
'Auto Watch Reply': [
|
|
||||||
false
|
|
||||||
'Automatically watch threads you reply to.'
|
|
||||||
]
|
|
||||||
|
|
||||||
'Posting':
|
'Posting':
|
||||||
'Header Shortcut': [
|
'Header Shortcut': [
|
||||||
@ -754,6 +740,24 @@ Config =
|
|||||||
['before', 'after']
|
['before', 'after']
|
||||||
]
|
]
|
||||||
|
|
||||||
|
threadWatcher:
|
||||||
|
'Current Board': [
|
||||||
|
false
|
||||||
|
'Only show watched threads from the current board.'
|
||||||
|
]
|
||||||
|
'Auto Watch': [
|
||||||
|
true
|
||||||
|
'Automatically watch threads you start.'
|
||||||
|
]
|
||||||
|
'Auto Watch Reply': [
|
||||||
|
false
|
||||||
|
'Automatically watch threads you reply to.'
|
||||||
|
]
|
||||||
|
'Auto Prune': [
|
||||||
|
false
|
||||||
|
'Automatically prune 404\'d threads.'
|
||||||
|
]
|
||||||
|
|
||||||
filter:
|
filter:
|
||||||
name: """
|
name: """
|
||||||
# Filter any namefags:
|
# Filter any namefags:
|
||||||
|
|||||||
@ -79,7 +79,6 @@ Header =
|
|||||||
fourchannav = $.id 'boardNavDesktop'
|
fourchannav = $.id 'boardNavDesktop'
|
||||||
if a = $ "a[href*='/#{g.BOARD}/']", fourchannav
|
if a = $ "a[href*='/#{g.BOARD}/']", fourchannav
|
||||||
a.className = 'current'
|
a.className = 'current'
|
||||||
|
|
||||||
boardList = $.el 'span',
|
boardList = $.el 'span',
|
||||||
id: 'board-list'
|
id: 'board-list'
|
||||||
innerHTML: "<span id=custom-board-list></span><span id=full-board-list hidden><span class='hide-board-list-container brackets-wrap'><a href=javascript:; class='hide-board-list-button'> - </a></span> #{fourchannav.innerHTML}</span>"
|
innerHTML: "<span id=custom-board-list></span><span id=full-board-list hidden><span class='hide-board-list-container brackets-wrap'><a href=javascript:; class='hide-board-list-button'> - </a></span> #{fourchannav.innerHTML}</span>"
|
||||||
|
|||||||
@ -163,6 +163,7 @@ Main =
|
|||||||
'Thread Updater': ThreadUpdater
|
'Thread Updater': ThreadUpdater
|
||||||
'Thread Stats': ThreadStats
|
'Thread Stats': ThreadStats
|
||||||
'Thread Watcher': ThreadWatcher
|
'Thread Watcher': ThreadWatcher
|
||||||
|
'Thread Watcher (Menu)': ThreadWatcher.menu
|
||||||
'Index Navigation': Nav
|
'Index Navigation': Nav
|
||||||
'Keybinds': Keybinds
|
'Keybinds': Keybinds
|
||||||
'Show Dice Roll': Dice
|
'Show Dice Roll': Dice
|
||||||
@ -204,9 +205,6 @@ Main =
|
|||||||
Main.callbackNodes Thread, threads
|
Main.callbackNodes Thread, threads
|
||||||
Main.callbackNodesDB Post, posts, ->
|
Main.callbackNodesDB Post, posts, ->
|
||||||
$.event '4chanXInitFinished'
|
$.event '4chanXInitFinished'
|
||||||
<% if (type !== 'crx') { %>
|
|
||||||
Main.checkUpdate()
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
if styleSelector = $.id 'styleSelector'
|
if styleSelector = $.id 'styleSelector'
|
||||||
passLink = $.el 'a',
|
passLink = $.el 'a',
|
||||||
@ -226,9 +224,6 @@ Main =
|
|||||||
new Notification 'warning', 'Cookies need to be enabled on 4chan for <%= meta.name %> to properly function.', 30
|
new Notification 'warning', 'Cookies need to be enabled on 4chan for <%= meta.name %> to properly function.', 30
|
||||||
|
|
||||||
$.event '4chanXInitFinished'
|
$.event '4chanXInitFinished'
|
||||||
<% if (type !== 'crx') { %>
|
|
||||||
Main.checkUpdate()
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
callbackNodes: (klass, nodes) ->
|
callbackNodes: (klass, nodes) ->
|
||||||
# get the nodes' length only once
|
# get the nodes' length only once
|
||||||
@ -302,27 +297,6 @@ Main =
|
|||||||
obj.callback.isAddon = true
|
obj.callback.isAddon = true
|
||||||
Klass::callbacks.push obj.callback
|
Klass::callbacks.push obj.callback
|
||||||
|
|
||||||
<% if (type !== 'crx') { %>
|
|
||||||
message: (e) ->
|
|
||||||
{version} = e.data
|
|
||||||
if version and version isnt g.VERSION
|
|
||||||
el = $.el 'span',
|
|
||||||
innerHTML: "Update: <%= meta.name %> v#{version} is out, get it <a href=<%= meta.page %> target=_blank>here</a>."
|
|
||||||
new Notification 'info', el, 120
|
|
||||||
|
|
||||||
checkUpdate: ->
|
|
||||||
return unless Conf['Check for Updates'] and Main.isThisPageLegit()
|
|
||||||
now = Date.now()
|
|
||||||
$.get 'lastchecked', 0, ({lastchecked}) ->
|
|
||||||
if (lastchecked > now - $.DAY)
|
|
||||||
return
|
|
||||||
$.ready ->
|
|
||||||
$.on window, 'message', Main.message
|
|
||||||
$.set 'lastchecked', now
|
|
||||||
$.add d.head, $.el 'script',
|
|
||||||
src: '<%= meta.repo %>raw/<%= meta.mainBranch %>/latest.js'
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
handleErrors: (errors) ->
|
handleErrors: (errors) ->
|
||||||
unless errors instanceof Array
|
unless errors instanceof Array
|
||||||
error = errors
|
error = errors
|
||||||
|
|||||||
@ -29,7 +29,8 @@ Settings =
|
|||||||
else
|
else
|
||||||
$.on d, '4chanXInitFinished', Settings.open
|
$.on d, '4chanXInitFinished', Settings.open
|
||||||
$.set
|
$.set
|
||||||
lastchecked: Date.now()
|
archives: Conf['archives']
|
||||||
|
lastarchivecheck: now
|
||||||
previousversion: g.VERSION
|
previousversion: g.VERSION
|
||||||
|
|
||||||
Settings.addSection 'Style', Settings.style
|
Settings.addSection 'Style', Settings.style
|
||||||
@ -173,7 +174,6 @@ Settings =
|
|||||||
data =
|
data =
|
||||||
version: g.VERSION
|
version: g.VERSION
|
||||||
date: now
|
date: now
|
||||||
Conf['WatchedThreads'] = {}
|
|
||||||
for db in DataBoards
|
for db in DataBoards
|
||||||
Conf[db] = boards: {}
|
Conf[db] = boards: {}
|
||||||
# Make sure to export the most recent data.
|
# Make sure to export the most recent data.
|
||||||
@ -220,7 +220,9 @@ Settings =
|
|||||||
reader.readAsText file
|
reader.readAsText file
|
||||||
|
|
||||||
loadSettings: (data) ->
|
loadSettings: (data) ->
|
||||||
version = data.version.split '.'
|
if data.Conf['WatchedThreads']
|
||||||
|
data.Conf['watchedThreads'] = boards: ThreadWatcher.convert data.Conf['WatchedThreads']
|
||||||
|
delete data.Conf['WatchedThreads']
|
||||||
$.set data.Conf
|
$.set data.Conf
|
||||||
|
|
||||||
convertSettings: (data, map) ->
|
convertSettings: (data, map) ->
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
#navtopright .exlinksOptionsLink::after,
|
#navtopright .exlinksOptionsLink::after,
|
||||||
#main-menu,
|
#main-menu,
|
||||||
body > div.navLinks > a:first-of-type::after,
|
body > div.navLinks > a:first-of-type::after,
|
||||||
.slideout-watcher #watcher::after,
|
.slideout-watcher #thread-watcher::after,
|
||||||
.announcements-slideout #globalMessage::after,
|
.announcements-slideout #globalMessage::after,
|
||||||
#boardNavDesktopFoot::after,
|
#boardNavDesktopFoot::after,
|
||||||
#img-controls,
|
#img-controls,
|
||||||
@ -26,7 +26,7 @@ body::after {
|
|||||||
.invisible-icons #navtopright .exlinksOptionsLink::after,
|
.invisible-icons #navtopright .exlinksOptionsLink::after,
|
||||||
.invisible-icons #main-menu,
|
.invisible-icons #main-menu,
|
||||||
.invisible-icons body > div.navLinks > a:first-of-type::after,
|
.invisible-icons body > div.navLinks > a:first-of-type::after,
|
||||||
.invisible-icons.slideout-watcher #watcher::after,
|
.invisible-icons.slideout-watcher #thread-watcher::after,
|
||||||
.invisible-icons.announcements-slideout #globalMessage::after,
|
.invisible-icons.announcements-slideout #globalMessage::after,
|
||||||
.invisible-icons #boardNavDesktopFoot::after,
|
.invisible-icons #boardNavDesktopFoot::after,
|
||||||
.invisible-icons #img-controls,
|
.invisible-icons #img-controls,
|
||||||
@ -36,7 +36,7 @@ body::after {
|
|||||||
}
|
}
|
||||||
#navtopright .exlinksOptionsLink,
|
#navtopright .exlinksOptionsLink,
|
||||||
body > div.navLinks > a:first-of-type,
|
body > div.navLinks > a:first-of-type,
|
||||||
#{if Conf['Slideout Watcher'] then '#watcher,' else ''}
|
#{if Conf['Slideout Watcher'] then '#thread-watcher,' else ''}
|
||||||
#{if Conf['Announcements'] is 'slideout' then '#globalMessage,' else ''}
|
#{if Conf['Announcements'] is 'slideout' then '#globalMessage,' else ''}
|
||||||
#boardNavDesktopFoot,
|
#boardNavDesktopFoot,
|
||||||
#catalog {
|
#catalog {
|
||||||
@ -44,7 +44,7 @@ body > div.navLinks > a:first-of-type,
|
|||||||
}
|
}
|
||||||
#navtopright .exlinksOptionsLink:hover,
|
#navtopright .exlinksOptionsLink:hover,
|
||||||
body > div.navLinks > a:first-of-type:hover,
|
body > div.navLinks > a:first-of-type:hover,
|
||||||
.slideout-watcher #watcher:hover,
|
.slideout-watcher #thread-watcher:hover,
|
||||||
.announcements-slideout #globalMessage:hover,
|
.announcements-slideout #globalMessage:hover,
|
||||||
#boardNavDesktopFoot:hover,
|
#boardNavDesktopFoot:hover,
|
||||||
#img-controls,
|
#img-controls,
|
||||||
@ -60,7 +60,7 @@ body > div.navLinks > a:first-of-type::after {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-position: 0 -15px;
|
background-position: 0 -15px;
|
||||||
}
|
}
|
||||||
.slideout-watcher #watcher::after {
|
.slideout-watcher #thread-watcher::after {
|
||||||
background-position: 0 -30px;
|
background-position: 0 -30px;
|
||||||
}
|
}
|
||||||
.announcements-slideout #globalMessage::after {
|
.announcements-slideout #globalMessage::after {
|
||||||
@ -90,7 +90,7 @@ body > div.navLinks > a:first-of-type::after {
|
|||||||
#main-menu:hover,
|
#main-menu:hover,
|
||||||
#navtopright .exlinksOptionsLink:hover::after,
|
#navtopright .exlinksOptionsLink:hover::after,
|
||||||
#qr #qrtab,
|
#qr #qrtab,
|
||||||
.slideout-watcher #watcher:hover::after,
|
.slideout-watcher #thread-watcher:hover::after,
|
||||||
.thumbnail#selected,
|
.thumbnail#selected,
|
||||||
div.navLinks > a:first-of-type:hover::after,
|
div.navLinks > a:first-of-type:hover::after,
|
||||||
#catalog:hover::after,
|
#catalog:hover::after,
|
||||||
|
|||||||
@ -14,7 +14,7 @@ body::after {
|
|||||||
#{align}: #{position[i++]}px;
|
#{align}: #{position[i++]}px;
|
||||||
}
|
}
|
||||||
/* Watcher */
|
/* Watcher */
|
||||||
.slideout-watcher #watcher::after {
|
.slideout-watcher #thread-watcher::after {
|
||||||
#{align}: #{position[i++]}px;
|
#{align}: #{position[i++]}px;
|
||||||
}
|
}
|
||||||
/* ExLinks */
|
/* ExLinks */
|
||||||
@ -55,7 +55,7 @@ body::after {
|
|||||||
#boardNavDesktopFoot::after,
|
#boardNavDesktopFoot::after,
|
||||||
#navtopright .exlinksOptionsLink::after,
|
#navtopright .exlinksOptionsLink::after,
|
||||||
#main-menu,
|
#main-menu,
|
||||||
.slideout-watcher #watcher::after,
|
.slideout-watcher #thread-watcher::after,
|
||||||
.announcements-slideout #globalMessage::after,
|
.announcements-slideout #globalMessage::after,
|
||||||
#img-controls,
|
#img-controls,
|
||||||
#fappeTyme,
|
#fappeTyme,
|
||||||
@ -64,7 +64,7 @@ div.navLinks > a:first-of-type::after,
|
|||||||
top: 1px !important;
|
top: 1px !important;
|
||||||
}
|
}
|
||||||
.slideout-watcher #globalMessage,
|
.slideout-watcher #globalMessage,
|
||||||
.slideout-watcher #watcher,
|
.slideout-watcher #thread-watcher,
|
||||||
#boardNavDesktopFoot {
|
#boardNavDesktopFoot {
|
||||||
top: 16px !important;
|
top: 16px !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,8 +20,8 @@ body::after {
|
|||||||
top: #{position[i++]}px;
|
top: #{position[i++]}px;
|
||||||
}
|
}
|
||||||
/* Watcher */
|
/* Watcher */
|
||||||
.slideout-watcher #watcher,
|
.slideout-watcher #thread-watcher,
|
||||||
.slideout-watcher #watcher::after {
|
.slideout-watcher #thread-watcher::after {
|
||||||
top: #{position[i++]}px !important;
|
top: #{position[i++]}px !important;
|
||||||
}
|
}
|
||||||
/* ExLinks */
|
/* ExLinks */
|
||||||
@ -58,21 +58,21 @@ body::after {
|
|||||||
#globalMessage::after,
|
#globalMessage::after,
|
||||||
#img-controls,
|
#img-controls,
|
||||||
#fappeTyme,
|
#fappeTyme,
|
||||||
.slideout-watcher #watcher::after,
|
.slideout-watcher #thread-watcher::after,
|
||||||
#catalog::after,
|
#catalog::after,
|
||||||
div.navLinks > a:first-of-type::after {
|
div.navLinks > a:first-of-type::after {
|
||||||
#{align}: 3px !important;
|
#{align}: 3px !important;
|
||||||
}
|
}
|
||||||
#boardNavDesktopFoot,
|
#boardNavDesktopFoot,
|
||||||
#globalMessage,
|
#globalMessage,
|
||||||
.slideout-watcher #watcher.dialog {
|
.slideout-watcher #thread-watcher.dialog {
|
||||||
<%= sizing %>: border-box;
|
<%= sizing %>: border-box;
|
||||||
width: 232px !important;
|
width: 232px !important;
|
||||||
#{align}: 18px !important;
|
#{align}: 18px !important;
|
||||||
}
|
}
|
||||||
.sidebar-large #boardNavDesktopFoot,
|
.sidebar-large #boardNavDesktopFoot,
|
||||||
.sidebar-large #globalMessage,
|
.sidebar-large #globalMessage,
|
||||||
.sidebar-large #watcher {
|
.sidebar-large #thread-watcher {
|
||||||
width: 288px !important;
|
width: 288px !important;
|
||||||
}
|
}
|
||||||
.fourchan-ss-navigation.fixed.top #header-bar,
|
.fourchan-ss-navigation.fixed.top #header-bar,
|
||||||
|
|||||||
@ -482,7 +482,7 @@ th {
|
|||||||
.icons-4chan-ss #navtopright .exlinksOptionsLink::after,
|
.icons-4chan-ss #navtopright .exlinksOptionsLink::after,
|
||||||
.icons-4chan-ss #main-menu,
|
.icons-4chan-ss #main-menu,
|
||||||
.icons-4chan-ss .navLinks > a:first-of-type::after,
|
.icons-4chan-ss .navLinks > a:first-of-type::after,
|
||||||
.icons-4chan-ss #watcher::after,
|
.icons-4chan-ss #thread-watcher::after,
|
||||||
.icons-4chan-ss #globalMessage::after,
|
.icons-4chan-ss #globalMessage::after,
|
||||||
.icons-4chan-ss #boardNavDesktopFoot::after,
|
.icons-4chan-ss #boardNavDesktopFoot::after,
|
||||||
.icons-4chan-ss #img-controls,
|
.icons-4chan-ss #img-controls,
|
||||||
@ -493,7 +493,7 @@ th {
|
|||||||
.icons-oneechan #navtopright .exlinksOptionsLink::after,
|
.icons-oneechan #navtopright .exlinksOptionsLink::after,
|
||||||
.icons-oneechan #main-menu,
|
.icons-oneechan #main-menu,
|
||||||
.icons-oneechan .navLinks > a:first-of-type::after,
|
.icons-oneechan .navLinks > a:first-of-type::after,
|
||||||
.icons-oneechan #watcher::after,
|
.icons-oneechan #thread-watcher::after,
|
||||||
.icons-oneechan #globalMessage::after,
|
.icons-oneechan #globalMessage::after,
|
||||||
.icons-oneechan #boardNavDesktopFoot::after,
|
.icons-oneechan #boardNavDesktopFoot::after,
|
||||||
.icons-oneechan #img-controls,
|
.icons-oneechan #img-controls,
|
||||||
@ -751,53 +751,53 @@ th {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
/* Watcher */
|
/* Watcher */
|
||||||
#watcher {
|
#thread-watcher {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 14;
|
z-index: 14;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
}
|
}
|
||||||
#watcher {
|
#thread-watcher {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
#watcher:not(:hover) {
|
#thread-watcher:not(:hover) {
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.rounded-edges #watcher {
|
.rounded-edges #thread-watcher {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
#watcher > div {
|
#thread-watcher > div {
|
||||||
max-height: 1.3em;
|
max-height: 1.3em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.slideout-watcher #watcher {
|
.slideout-watcher #thread-watcher {
|
||||||
<%= sizing %>: border-box;
|
<%= sizing %>: border-box;
|
||||||
width: 248px;
|
width: 248px;
|
||||||
}
|
}
|
||||||
.slideout-watcher.sidebar-large #boardNavDesktopFoot {
|
.slideout-watcher.sidebar-large #boardNavDesktopFoot {
|
||||||
width: 299px;
|
width: 299px;
|
||||||
}
|
}
|
||||||
.slideout-watcher.sidebar-location-right #watcher {
|
.slideout-watcher.sidebar-location-right #thread-watcher {
|
||||||
left: auto !important;
|
left: auto !important;
|
||||||
right: 2px !important;
|
right: 2px !important;
|
||||||
}
|
}
|
||||||
.slideout-watcher.sidebar-location-left #watcher {
|
.slideout-watcher.sidebar-location-left #thread-watcher {
|
||||||
right: auto !important;
|
right: auto !important;
|
||||||
left: 2px !important;
|
left: 2px !important;
|
||||||
}
|
}
|
||||||
.slideout-watcher #watcher .move {
|
.slideout-watcher #thread-watcher .move {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
.slideout-watcher.underline-links #watcher .move {
|
.slideout-watcher.underline-links #thread-watcher .move {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
.slideout-watcher #watcher > div {
|
.slideout-watcher #thread-watcher > div {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.slideout-watcher #watcher:hover {
|
.slideout-watcher #thread-watcher:hover {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.slideout-watcher #watcher:not(:hover) {
|
.slideout-watcher #thread-watcher:not(:hover) {
|
||||||
height: 0;
|
height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 0 none;
|
border: 0 none;
|
||||||
|
|||||||
@ -97,10 +97,10 @@ a {
|
|||||||
#qr {
|
#qr {
|
||||||
z-index: 30;
|
z-index: 30;
|
||||||
}
|
}
|
||||||
#watcher {
|
#thread-watcher {
|
||||||
z-index: 8;
|
z-index: 8;
|
||||||
}
|
}
|
||||||
:root.fixed-watcher #watcher {
|
:root.fixed-watcher #thread-watcher {
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
}
|
}
|
||||||
.fixed #header-bar {
|
.fixed #header-bar {
|
||||||
@ -465,10 +465,10 @@ a.hide-announcement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Thread Watcher */
|
/* Thread Watcher */
|
||||||
#watcher {
|
#thread-watcher {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
#watcher {
|
#thread-watcher {
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@ -476,27 +476,27 @@ a.hide-announcement {
|
|||||||
max-height: 92%;
|
max-height: 92%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
:root.fixed-watcher #watcher {
|
:root.fixed-watcher #thread-watcher {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
}
|
}
|
||||||
:root:not(.fixed-watcher) #watcher:not(:hover) {
|
:root:not(.fixed-watcher) #thread-watcher:not(:hover) {
|
||||||
max-height: 210px;
|
max-height: 210px;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
#watcher > .move {
|
#thread-watcher > .move {
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
}
|
}
|
||||||
#watcher > div {
|
#thread-watcher > div {
|
||||||
max-width: 250px;
|
max-width: 250px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-left: 3px;
|
padding-left: 3px;
|
||||||
padding-right: 3px;
|
padding-right: 3px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
#watcher a {
|
#thread-watcher a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
#watcher .move>.close {
|
#thread-watcher .move>.close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
|
|||||||
@ -165,8 +165,8 @@ textarea.field:focus {
|
|||||||
#menu,
|
#menu,
|
||||||
#selectrice,
|
#selectrice,
|
||||||
#themeConf,
|
#themeConf,
|
||||||
#watcher,
|
#thread-watcher,
|
||||||
#watcher:hover,
|
#thread-watcher:hover,
|
||||||
.announcements-slideout #globalMessage,
|
.announcements-slideout #globalMessage,
|
||||||
.dialog,
|
.dialog,
|
||||||
.post-form-style-float #qr,
|
.post-form-style-float #qr,
|
||||||
@ -343,7 +343,7 @@ a .name {
|
|||||||
#navtopright .exlinksOptionsLink::after,
|
#navtopright .exlinksOptionsLink::after,
|
||||||
#main-menu,
|
#main-menu,
|
||||||
.navLinks > a:first-of-type::after,
|
.navLinks > a:first-of-type::after,
|
||||||
#watcher::after,
|
#thread-watcher::after,
|
||||||
#globalMessage::after,
|
#globalMessage::after,
|
||||||
#boardNavDesktopFoot::after,
|
#boardNavDesktopFoot::after,
|
||||||
#img-controls,
|
#img-controls,
|
||||||
|
|||||||
@ -54,6 +54,6 @@
|
|||||||
|
|
||||||
#{if isOP then '' else fileHTML}
|
#{if isOP then '' else fileHTML}
|
||||||
|
|
||||||
<blockquote class=postMessage id=m#{postID}>#{comment or ''}#{capcodeReplies}</blockquote>#{" "}
|
<blockquote class=postMessage id=m#{postID}>#{comment or ''}</blockquote>#{" "}
|
||||||
|
|
||||||
</div>"""
|
</div>"""
|
||||||
5
src/General/html/Monitoring/ThreadWatcher.html
Normal file
5
src/General/html/Monitoring/ThreadWatcher.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<div>
|
||||||
|
<span class="move">Thread Watcher <span id="watcher-status"></span></span>
|
||||||
|
<a class="menu-button brackets-wrap" href="javascript:;"><i class=drop-marker></i></a>
|
||||||
|
</div>
|
||||||
|
<div id="watched-threads"></div>
|
||||||
@ -54,18 +54,23 @@ $.extend = (object, properties) ->
|
|||||||
object[key] = val
|
object[key] = val
|
||||||
return
|
return
|
||||||
|
|
||||||
$.ajax = (url, options, extra={}) ->
|
$.ajax = do ->
|
||||||
{type, headers, upCallbacks, form, sync} = extra
|
# Status Code 304: Not modified
|
||||||
r = new XMLHttpRequest()
|
# With the `If-Modified-Since` header we only receive the HTTP headers and no body for 304 responses.
|
||||||
r.overrideMimeType 'text/html'
|
# This saves a lot of bandwidth and CPU time for both the users and the servers.
|
||||||
type or= form and 'post' or 'get'
|
lastModified = {}
|
||||||
r.open type, url, !sync
|
(url, options, extra={}) ->
|
||||||
for key, val of headers
|
{type, whenModified, upCallbacks, form, sync} = extra
|
||||||
r.setRequestHeader key, val
|
r = new XMLHttpRequest()
|
||||||
$.extend r, options
|
type or= form and 'post' or 'get'
|
||||||
$.extend r.upload, upCallbacks
|
r.open type, url, !sync
|
||||||
r.send form
|
if whenModified
|
||||||
r
|
r.setRequestHeader 'If-Modified-Since', lastModified[url] or '0'
|
||||||
|
$.on r, 'load', -> lastModified[url] = r.getResponseHeader 'Last-Modified'
|
||||||
|
$.extend r, options
|
||||||
|
$.extend r.upload, upCallbacks
|
||||||
|
r.send form
|
||||||
|
r
|
||||||
|
|
||||||
$.cache = do ->
|
$.cache = do ->
|
||||||
reqs = {}
|
reqs = {}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
DataBoards = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts']
|
DataBoards = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads']
|
||||||
|
|
||||||
class DataBoard
|
class DataBoard
|
||||||
constructor: (@key, sync) ->
|
constructor: (@key, sync, dontClean) ->
|
||||||
@data = Conf[key]
|
@data = Conf[key]
|
||||||
$.sync key, @onSync.bind @
|
$.sync key, @onSync.bind @
|
||||||
@clean()
|
@clean() unless dontClean
|
||||||
return unless sync
|
return unless sync
|
||||||
# Chrome also fires the onChanged callback on the current tab,
|
# Chrome also fires the onChanged callback on the current tab,
|
||||||
# so we only start syncing when we're ready.
|
# so we only start syncing when we're ready.
|
||||||
@ -13,6 +13,9 @@ class DataBoard
|
|||||||
@sync = sync
|
@sync = sync
|
||||||
$.on d, '4chanXInitFinished', init
|
$.on d, '4chanXInitFinished', init
|
||||||
|
|
||||||
|
save: ->
|
||||||
|
$.set @key, @data
|
||||||
|
|
||||||
delete: ({boardID, threadID, postID}) ->
|
delete: ({boardID, threadID, postID}) ->
|
||||||
if postID
|
if postID
|
||||||
delete @data.boards[boardID][threadID][postID]
|
delete @data.boards[boardID][threadID][postID]
|
||||||
@ -22,7 +25,7 @@ class DataBoard
|
|||||||
@deleteIfEmpty {boardID}
|
@deleteIfEmpty {boardID}
|
||||||
else
|
else
|
||||||
delete @data.boards[boardID]
|
delete @data.boards[boardID]
|
||||||
$.set @key, @data
|
@save()
|
||||||
|
|
||||||
deleteIfEmpty: ({boardID, threadID}) ->
|
deleteIfEmpty: ({boardID, threadID}) ->
|
||||||
if threadID
|
if threadID
|
||||||
@ -39,7 +42,7 @@ class DataBoard
|
|||||||
(@data.boards[boardID] or= {})[threadID] = val
|
(@data.boards[boardID] or= {})[threadID] = val
|
||||||
else
|
else
|
||||||
@data.boards[boardID] = val
|
@data.boards[boardID] = val
|
||||||
$.set @key, @data
|
@save()
|
||||||
|
|
||||||
get: ({boardID, threadID, postID, defaultValue}) ->
|
get: ({boardID, threadID, postID, defaultValue}) ->
|
||||||
if board = @data.boards[boardID]
|
if board = @data.boards[boardID]
|
||||||
@ -67,8 +70,7 @@ class DataBoard
|
|||||||
@data.lastChecked = now
|
@data.lastChecked = now
|
||||||
for boardID of @data.boards
|
for boardID of @data.boards
|
||||||
@ajaxClean boardID
|
@ajaxClean boardID
|
||||||
|
@save()
|
||||||
$.set @key, @data
|
|
||||||
|
|
||||||
ajaxClean: (boardID) ->
|
ajaxClean: (boardID) ->
|
||||||
$.cache "//api.4chan.org/#{boardID}/threads.json", (e) =>
|
$.cache "//api.4chan.org/#{boardID}/threads.json", (e) =>
|
||||||
@ -84,7 +86,7 @@ class DataBoard
|
|||||||
threads[thread.no] = board[thread.no]
|
threads[thread.no] = board[thread.no]
|
||||||
@data.boards[boardID] = threads
|
@data.boards[boardID] = threads
|
||||||
@deleteIfEmpty {boardID}
|
@deleteIfEmpty {boardID}
|
||||||
$.set @key, @data
|
@save()
|
||||||
|
|
||||||
onSync: (data) ->
|
onSync: (data) ->
|
||||||
@data = data or boards: {}
|
@data = data or boards: {}
|
||||||
|
|||||||
@ -1,6 +1,16 @@
|
|||||||
Polyfill =
|
Polyfill =
|
||||||
init: ->
|
init: ->
|
||||||
|
Polyfill.toBlob()
|
||||||
Polyfill.visibility()
|
Polyfill.visibility()
|
||||||
|
toBlob: ->
|
||||||
|
HTMLCanvasElement::toBlob or= (cb) ->
|
||||||
|
data = atob @toDataURL()[22..]
|
||||||
|
# DataUrl to Binary code from Aeosynth's 4chan X repo
|
||||||
|
l = data.length
|
||||||
|
ui8a = new Uint8Array l
|
||||||
|
for i in [0...l]
|
||||||
|
ui8a[i] = data.charCodeAt i
|
||||||
|
cb new Blob [ui8a], type: 'image/png'
|
||||||
visibility: ->
|
visibility: ->
|
||||||
# page visibility API
|
# page visibility API
|
||||||
return unless 'webkitHidden' of document
|
return unless 'webkitHidden' of document
|
||||||
|
|||||||
@ -173,8 +173,8 @@ ImageExpand =
|
|||||||
|
|
||||||
{createSubEntry} = ImageExpand.menu
|
{createSubEntry} = ImageExpand.menu
|
||||||
subEntries = []
|
subEntries = []
|
||||||
for key, conf of Config.imageExpansion
|
for name, conf of Config.imageExpansion
|
||||||
subEntries.push createSubEntry key, conf
|
subEntries.push createSubEntry name, conf[1]
|
||||||
|
|
||||||
$.event 'AddMenuEntry',
|
$.event 'AddMenuEntry',
|
||||||
type: 'header'
|
type: 'header'
|
||||||
@ -182,17 +182,16 @@ ImageExpand =
|
|||||||
order: 105
|
order: 105
|
||||||
subEntries: subEntries
|
subEntries: subEntries
|
||||||
|
|
||||||
createSubEntry: (type, config) ->
|
createSubEntry: (name, desc) ->
|
||||||
label = $.el 'label',
|
label = $.el 'label',
|
||||||
innerHTML: "<input type=checkbox name='#{type}'> #{type}"
|
innerHTML: "<input type=checkbox name='#{name}'> #{name}"
|
||||||
|
title: desc
|
||||||
input = label.firstElementChild
|
input = label.firstElementChild
|
||||||
if type in ['Fit width', 'Fit height']
|
if name in ['Fit width', 'Fit height']
|
||||||
$.on input, 'change', ImageExpand.cb.setFitness
|
$.on input, 'change', ImageExpand.cb.setFitness
|
||||||
if config
|
input.checked = Conf[name]
|
||||||
label.title = config[1]
|
$.event 'change', null, input
|
||||||
input.checked = Conf[type]
|
$.on input, 'change', $.cb.checked
|
||||||
$.event 'change', null, input
|
|
||||||
$.on input, 'change', $.cb.checked
|
|
||||||
el: label
|
el: label
|
||||||
|
|
||||||
menuToggle: (e) ->
|
menuToggle: (e) ->
|
||||||
|
|||||||
@ -4,23 +4,20 @@ Linkify =
|
|||||||
|
|
||||||
@regString = if Conf['Allow False Positives']
|
@regString = if Conf['Allow False Positives']
|
||||||
///(
|
///(
|
||||||
\b(
|
[-a-z]+://
|
||||||
[-a-z]+://
|
|
|
||||||
|
|
[a-z]{3,}\.[-a-z0-9]+\.[a-z]
|
||||||
[a-z]{3,}\.[-a-z0-9]+\.[a-z]
|
|
|
||||||
|
|
[-a-z0-9]+\.[a-z]
|
||||||
[-a-z0-9]+\.[a-z]
|
|
|
||||||
|
|
[\d]+\.[\d]+\.[\d]+\.[\d]+/
|
||||||
[\d]+\.[\d]+\.[\d]+\.[\d]+/
|
|
|
||||||
|
|
[a-z]{3,}:[a-z0-9?]
|
||||||
[a-z]{3,}:[a-z0-9?]
|
|
|
||||||
|
|
[^\s@]+@[a-z0-9.-]+\.[a-z0-9]
|
||||||
[^\s@]+@[a-z0-9.-]+\.[a-z0-9]
|
)///i
|
||||||
)
|
|
||||||
[^\s'"]+
|
|
||||||
)///gi
|
|
||||||
else
|
else
|
||||||
/(((magnet|mailto)\:|(www\.)|(news|(ht|f)tp(s?))\:\/\/){1}\S+)/gi
|
/(((magnet|mailto)\:|(www\.)|(news|(ht|f)tp(s?))\:\/\/){1})/i
|
||||||
|
|
||||||
if Conf['Comment Expansion']
|
if Conf['Comment Expansion']
|
||||||
ExpandComment.callbacks.push @node
|
ExpandComment.callbacks.push @node
|
||||||
@ -43,16 +40,44 @@ Linkify =
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
test = /[^\s'"]+/g
|
||||||
|
space = /[\s'"]/
|
||||||
|
|
||||||
snapshot = $.X './/br|.//text()', @nodes.comment
|
snapshot = $.X './/br|.//text()', @nodes.comment
|
||||||
i = 0
|
i = 0
|
||||||
while node = snapshot.snapshotItem i++
|
while node = snapshot.snapshotItem i++
|
||||||
|
links = []
|
||||||
|
{data} = node
|
||||||
|
continue if node.parentElement.nodeName is "A" or not data
|
||||||
|
|
||||||
continue if node.parentElement.nodeName is "A"
|
while result = test.exec data
|
||||||
links = []
|
{index} = result
|
||||||
|
endNode = node
|
||||||
|
if (length = index + result[0].length) is data.length
|
||||||
|
|
||||||
if Linkify.regString.test node.data
|
while (saved = snapshot.snapshotItem i++)
|
||||||
Linkify.regString.lastIndex = 0
|
break if saved.nodeName is 'BR'
|
||||||
Linkify.gatherLinks snapshot, @, node, links, i
|
|
||||||
|
endNode = saved
|
||||||
|
{length} = saved.data
|
||||||
|
|
||||||
|
if end = space.exec saved.data
|
||||||
|
length = end.index
|
||||||
|
i--
|
||||||
|
break
|
||||||
|
|
||||||
|
if length is endNode.data.length then test.lastIndex = 0
|
||||||
|
range = Linkify.makeRange node, endNode, index, length
|
||||||
|
if link = Linkify.regString.exec text = range.toString()
|
||||||
|
if lIndex = link.index
|
||||||
|
range.setStart node, lIndex + index
|
||||||
|
links.push [range, text]
|
||||||
|
break
|
||||||
|
|
||||||
|
else
|
||||||
|
if link = Linkify.regString.exec result[0]
|
||||||
|
range = Linkify.makeRange node, node, link.index, link.length
|
||||||
|
links.push [range, link]
|
||||||
|
|
||||||
for range in links.reverse()
|
for range in links.reverse()
|
||||||
@nodes.links.push Linkify.makeLink range, @
|
@nodes.links.push Linkify.makeLink range, @
|
||||||
@ -68,66 +93,29 @@ Linkify =
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
gatherLinks: (snapshot, post, node, links, i) ->
|
makeRange: (startNode, endNode, startOffset, endOffset) ->
|
||||||
{data} = node
|
range = document.createRange();
|
||||||
len = data.length
|
range.setStart startNode, startOffset
|
||||||
|
range.setEnd endNode, endOffset
|
||||||
while (match = Linkify.regString.exec data)
|
|
||||||
{index} = match
|
|
||||||
link = match[0]
|
|
||||||
len2 = index + link.length
|
|
||||||
|
|
||||||
break if len is len2
|
|
||||||
|
|
||||||
range = document.createRange();
|
|
||||||
range.setStart node, index
|
|
||||||
range.setEnd node, len2
|
|
||||||
links.push range
|
|
||||||
|
|
||||||
Linkify.regString.lastIndex = 0
|
|
||||||
|
|
||||||
if match
|
|
||||||
links.push Linkify.seek snapshot, post, node, links, match, i
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
seek: (snapshot, post, node, links, match, i) ->
|
|
||||||
link = match[0]
|
|
||||||
range = document.createRange()
|
|
||||||
range.setStart node, match.index
|
|
||||||
|
|
||||||
while (next = snapshot.snapshotItem i++) and next.nodeName isnt 'BR'
|
|
||||||
node = next
|
|
||||||
data = node.data
|
|
||||||
if result = /[\s'"]/.exec data
|
|
||||||
{index} = result
|
|
||||||
range.setEnd node, index
|
|
||||||
Linkify.regString.lastIndex = index
|
|
||||||
Linkify.gatherLinks snapshot, post, node, links, i
|
|
||||||
return range
|
|
||||||
|
|
||||||
if range.collapsed
|
|
||||||
range.setEnd node, node.data.length
|
|
||||||
|
|
||||||
range
|
range
|
||||||
|
|
||||||
makeLink: (range) ->
|
makeLink: ([range, text]) ->
|
||||||
link = range.toString()
|
text
|
||||||
link =
|
text =
|
||||||
if link.contains ':'
|
if text.contains ':'
|
||||||
link
|
text
|
||||||
else (
|
else (
|
||||||
if link.contains '@'
|
if text.contains '@'
|
||||||
'mailto:'
|
'mailto:'
|
||||||
else
|
else
|
||||||
'http://'
|
'http://'
|
||||||
) + link
|
) + text
|
||||||
|
|
||||||
a = $.el 'a',
|
a = $.el 'a',
|
||||||
className: 'linkify'
|
className: 'linkify'
|
||||||
rel: 'nofollow noreferrer'
|
rel: 'nofollow noreferrer'
|
||||||
target: '_blank'
|
target: '_blank'
|
||||||
href: link
|
href: text
|
||||||
$.add a, range.extractContents()
|
$.add a, range.extractContents()
|
||||||
range.insertNode a
|
range.insertNode a
|
||||||
a
|
a
|
||||||
|
|||||||
@ -19,9 +19,8 @@ ExpandComment =
|
|||||||
|
|
||||||
cb: (e) ->
|
cb: (e) ->
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
post = Get.postFromNode @
|
ExpandComment.expand Get.postFromNode @
|
||||||
ExpandComment.expand post
|
|
||||||
|
|
||||||
expand: (post) ->
|
expand: (post) ->
|
||||||
if post.nodes.longComment and !post.nodes.longComment.parentNode
|
if post.nodes.longComment and !post.nodes.longComment.parentNode
|
||||||
$.replace post.nodes.shortComment, post.nodes.longComment
|
$.replace post.nodes.shortComment, post.nodes.longComment
|
||||||
@ -61,6 +60,11 @@ ExpandComment =
|
|||||||
href = quote.getAttribute 'href'
|
href = quote.getAttribute 'href'
|
||||||
continue if href[0] is '/' # Cross-board quote, or board link
|
continue if href[0] is '/' # Cross-board quote, or board link
|
||||||
quote.href = "/#{post.board}/res/#{href}" # Fix pathnames
|
quote.href = "/#{post.board}/res/#{href}" # Fix pathnames
|
||||||
|
Build.capcodeReplies
|
||||||
|
boardID: post.board.ID
|
||||||
|
threadID: post.thread.ID
|
||||||
|
bq: clone
|
||||||
|
capcodeReplies: postObj.capcode_replies
|
||||||
post.nodes.shortComment = comment
|
post.nodes.shortComment = comment
|
||||||
$.replace comment, clone
|
$.replace comment, clone
|
||||||
post.nodes.comment = post.nodes.longComment = clone
|
post.nodes.comment = post.nodes.longComment = clone
|
||||||
|
|||||||
@ -18,7 +18,6 @@ ThreadStats =
|
|||||||
@postCountEl = $ '#post-count', sc
|
@postCountEl = $ '#post-count', sc
|
||||||
@fileCountEl = $ '#file-count', sc
|
@fileCountEl = $ '#file-count', sc
|
||||||
@pageCountEl = $ '#page-count', sc
|
@pageCountEl = $ '#page-count', sc
|
||||||
@lastModified = '0'
|
|
||||||
|
|
||||||
Thread::callbacks.push
|
Thread::callbacks.push
|
||||||
name: 'Thread Stats'
|
name: 'Thread Stats'
|
||||||
@ -55,12 +54,10 @@ ThreadStats =
|
|||||||
return
|
return
|
||||||
setTimeout ThreadStats.fetchPage, 2 * $.MINUTE
|
setTimeout ThreadStats.fetchPage, 2 * $.MINUTE
|
||||||
$.ajax "//api.4chan.org/#{ThreadStats.thread.board}/threads.json", onload: ThreadStats.onThreadsLoad,
|
$.ajax "//api.4chan.org/#{ThreadStats.thread.board}/threads.json", onload: ThreadStats.onThreadsLoad,
|
||||||
headers: 'If-Modified-Since': ThreadStats.lastModified
|
whenModified: true
|
||||||
|
|
||||||
onThreadsLoad: ->
|
onThreadsLoad: ->
|
||||||
return if !Conf["Page Count in Stats"]
|
return unless Conf["Page Count in Stats"] and @status is 200
|
||||||
ThreadStats.lastModified = @getResponseHeader 'Last-Modified'
|
|
||||||
return if @status isnt 200
|
|
||||||
pages = JSON.parse @response
|
pages = JSON.parse @response
|
||||||
for page in pages
|
for page in pages
|
||||||
for thread in page.threads
|
for thread in page.threads
|
||||||
|
|||||||
@ -64,7 +64,6 @@ ThreadUpdater =
|
|||||||
ThreadUpdater.root = @OP.nodes.root.parentNode
|
ThreadUpdater.root = @OP.nodes.root.parentNode
|
||||||
ThreadUpdater.lastPost = +ThreadUpdater.root.lastElementChild.id.match(/\d+/)[0]
|
ThreadUpdater.lastPost = +ThreadUpdater.root.lastElementChild.id.match(/\d+/)[0]
|
||||||
ThreadUpdater.outdateCount = 0
|
ThreadUpdater.outdateCount = 0
|
||||||
ThreadUpdater.lastModified = '0'
|
|
||||||
|
|
||||||
ThreadUpdater.cb.interval.call $.el 'input', value: Conf['Interval']
|
ThreadUpdater.cb.interval.call $.el 'input', value: Conf['Interval']
|
||||||
|
|
||||||
@ -137,9 +136,7 @@ ThreadUpdater =
|
|||||||
when 200
|
when 200
|
||||||
g.DEAD = false
|
g.DEAD = false
|
||||||
ThreadUpdater.parse JSON.parse(req.response).posts
|
ThreadUpdater.parse JSON.parse(req.response).posts
|
||||||
ThreadUpdater.lastModified = req.getResponseHeader 'Last-Modified'
|
ThreadUpdater.set 'timer', ThreadUpdater.getInterval()
|
||||||
if Conf['Auto Update']
|
|
||||||
ThreadUpdater.set 'timer', ThreadUpdater.getInterval()
|
|
||||||
when 404
|
when 404
|
||||||
g.DEAD = true
|
g.DEAD = true
|
||||||
ThreadUpdater.set 'timer', null
|
ThreadUpdater.set 'timer', null
|
||||||
@ -150,14 +147,8 @@ ThreadUpdater =
|
|||||||
404: true
|
404: true
|
||||||
thread: ThreadUpdater.thread
|
thread: ThreadUpdater.thread
|
||||||
else
|
else
|
||||||
if Conf['Auto Update']
|
ThreadUpdater.outdateCount++
|
||||||
ThreadUpdater.outdateCount++
|
ThreadUpdater.set 'timer', ThreadUpdater.getInterval()
|
||||||
ThreadUpdater.set 'timer', ThreadUpdater.getInterval()
|
|
||||||
###
|
|
||||||
Status Code 304: Not modified
|
|
||||||
By sending the `If-Modified-Since` header we get a proper status code, and no response.
|
|
||||||
This saves bandwidth for both the user and the servers and avoid unnecessary computation.
|
|
||||||
###
|
|
||||||
[text, klass] = if req.status is 304
|
[text, klass] = if req.status is 304
|
||||||
[null, null]
|
[null, null]
|
||||||
else
|
else
|
||||||
@ -219,7 +210,7 @@ ThreadUpdater =
|
|||||||
ThreadUpdater.req.abort()
|
ThreadUpdater.req.abort()
|
||||||
url = "//api.4chan.org/#{ThreadUpdater.thread.board}/res/#{ThreadUpdater.thread}.json"
|
url = "//api.4chan.org/#{ThreadUpdater.thread.board}/res/#{ThreadUpdater.thread}.json"
|
||||||
ThreadUpdater.req = $.ajax url, onloadend: ThreadUpdater.cb.load,
|
ThreadUpdater.req = $.ajax url, onloadend: ThreadUpdater.cb.load,
|
||||||
headers: 'If-Modified-Since': ThreadUpdater.lastModified
|
whenModified: true
|
||||||
|
|
||||||
updateThreadStatus: (title, OP) ->
|
updateThreadStatus: (title, OP) ->
|
||||||
titleLC = title.toLowerCase()
|
titleLC = title.toLowerCase()
|
||||||
|
|||||||
@ -2,98 +2,282 @@ ThreadWatcher =
|
|||||||
init: ->
|
init: ->
|
||||||
return unless Conf['Thread Watcher']
|
return unless Conf['Thread Watcher']
|
||||||
|
|
||||||
@dialog = UI.dialog 'watcher', 'top: 50px; left: 0px;',
|
|
||||||
'<div class=move>Thread Watcher</div>'
|
@db = new DataBoard 'watchedThreads', @refresh, true
|
||||||
|
@dialog = UI.dialog 'thread-watcher', 'top: 50px; left: 0px;', """
|
||||||
|
<%= grunt.file.read('src/General/html/Monitoring/ThreadWatcher.html').replace(/>\s+</g, '><').trim() %>
|
||||||
|
"""
|
||||||
|
@status = $ '#watcher-status', @dialog
|
||||||
|
@list = @dialog.lastElementChild
|
||||||
|
|
||||||
$.on d, 'QRPostSuccessful', @cb.post
|
$.on d, 'QRPostSuccessful', @cb.post
|
||||||
$.sync 'WatchedThreads', @refresh
|
$.on d, 'ThreadUpdate', @cb.threadUpdate if g.VIEW is 'thread'
|
||||||
|
$.on d, '4chanXInitFinished', @ready
|
||||||
|
|
||||||
$.ready ->
|
now = Date.now()
|
||||||
ThreadWatcher.refresh()
|
if (@db.data.lastChecked or 0) < now - 2 * $.HOUR
|
||||||
$.add d.body, ThreadWatcher.dialog
|
@db.data.lastChecked = now
|
||||||
|
ThreadWatcher.fetchAllStatus()
|
||||||
|
@db.save()
|
||||||
|
|
||||||
|
# XXX tmp conversion from old to new format
|
||||||
|
$.get 'WatchedThreads', null, ({WatchedThreads}) ->
|
||||||
|
return unless WatchedThreads
|
||||||
|
for boardID, threads of ThreadWatcher.convert WatchedThreads
|
||||||
|
for threadID, data of threads
|
||||||
|
ThreadWatcher.db.set {boardID, threadID, val: data}
|
||||||
|
$.delete 'WatchedThreads'
|
||||||
|
|
||||||
Thread::callbacks.push
|
Thread::callbacks.push
|
||||||
name: 'Thread Watcher'
|
name: 'Thread Watcher'
|
||||||
cb: @node
|
cb: @node
|
||||||
|
|
||||||
node: ->
|
node: ->
|
||||||
favicon = $.el 'a',
|
toggler = $.el 'img',
|
||||||
className: 'watch-thread-link'
|
className: 'watcher-toggler'
|
||||||
href: 'javascript:;'
|
$.on toggler, 'click', ThreadWatcher.cb.toggle
|
||||||
$.on favicon, 'click', ThreadWatcher.cb.toggle
|
$.before $('input', @OP.nodes.post), toggler
|
||||||
$.before $('input', @OP.nodes.post), favicon
|
|
||||||
return if g.VIEW isnt 'thread'
|
ready: ->
|
||||||
$.get 'AutoWatch', 0, (item) =>
|
$.off d, '4chanXInitFinished', ThreadWatcher.ready
|
||||||
return if item['AutoWatch'] isnt @ID
|
return unless Main.isThisPageLegit()
|
||||||
ThreadWatcher.watch @
|
ThreadWatcher.refresh()
|
||||||
|
$.add d.body, ThreadWatcher.dialog
|
||||||
|
|
||||||
|
return unless Conf['Auto Watch']
|
||||||
|
$.get 'AutoWatch', 0, ({AutoWatch}) ->
|
||||||
|
return unless thread = g.BOARD.threads[AutoWatch]
|
||||||
|
ThreadWatcher.add thread
|
||||||
$.delete 'AutoWatch'
|
$.delete 'AutoWatch'
|
||||||
|
|
||||||
refresh: (watched) ->
|
|
||||||
unless watched
|
|
||||||
$.get 'WatchedThreads', {}, (item) ->
|
|
||||||
ThreadWatcher.refresh item['WatchedThreads']
|
|
||||||
return
|
|
||||||
nodes = [$('.move', ThreadWatcher.dialog)]
|
|
||||||
for board of watched
|
|
||||||
for id, props of watched[board]
|
|
||||||
x = $.el 'a',
|
|
||||||
textContent: '✖'
|
|
||||||
className: 'close'
|
|
||||||
href: 'javascript:;'
|
|
||||||
$.on x, 'click', ThreadWatcher.cb.x
|
|
||||||
link = $.el 'a', props
|
|
||||||
link.title = link.textContent
|
|
||||||
|
|
||||||
div = $.el 'div'
|
|
||||||
$.add div, [x, $.tn(' '), link]
|
|
||||||
nodes.push div
|
|
||||||
|
|
||||||
$.rmAll ThreadWatcher.dialog
|
|
||||||
$.add ThreadWatcher.dialog, nodes
|
|
||||||
|
|
||||||
watched = watched[g.BOARD] or {}
|
|
||||||
for ID, thread of g.BOARD.threads
|
|
||||||
favicon = $ '.watch-thread-link', thread.OP.nodes.post
|
|
||||||
if ID of watched
|
|
||||||
$.addClass favicon, 'watched'
|
|
||||||
else
|
|
||||||
$.rmClass favicon, 'watched'
|
|
||||||
return
|
|
||||||
|
|
||||||
cb:
|
cb:
|
||||||
|
openAll: ->
|
||||||
|
return if $.hasClass @, 'disabled'
|
||||||
|
for a in $$ 'a[title]', ThreadWatcher.list
|
||||||
|
$.open a.href
|
||||||
|
$.event 'CloseMenu'
|
||||||
|
checkThreads: ->
|
||||||
|
return if $.hasClass @, 'disabled'
|
||||||
|
ThreadWatcher.fetchAllStatus()
|
||||||
|
pruneDeads: ->
|
||||||
|
return if $.hasClass @, 'disabled'
|
||||||
|
for {boardID, threadID, data} in ThreadWatcher.getAll() when data.isDead
|
||||||
|
delete ThreadWatcher.db.data.boards[boardID][threadID]
|
||||||
|
ThreadWatcher.db.deleteIfEmpty {boardID}
|
||||||
|
ThreadWatcher.db.save()
|
||||||
|
ThreadWatcher.refresh()
|
||||||
|
$.event 'CloseMenu'
|
||||||
toggle: ->
|
toggle: ->
|
||||||
ThreadWatcher.toggle Get.postFromNode(@).thread
|
ThreadWatcher.toggle Get.postFromNode(@).thread
|
||||||
x: ->
|
rm: ->
|
||||||
thread = @nextElementSibling.pathname.split '/'
|
[boardID, threadID] = @parentNode.dataset.fullID.split '.'
|
||||||
ThreadWatcher.unwatch thread[1], thread[3]
|
ThreadWatcher.rm boardID, +threadID
|
||||||
post: (e) ->
|
post: (e) ->
|
||||||
{board, postID, threadID} = e.detail
|
{board, postID, threadID} = e.detail
|
||||||
if postID is threadID
|
if postID is threadID
|
||||||
if Conf['Auto Watch']
|
if Conf['Auto Watch']
|
||||||
$.set 'AutoWatch', threadID
|
$.set 'AutoWatch', threadID
|
||||||
else if Conf['Auto Watch Reply']
|
else if Conf['Auto Watch Reply']
|
||||||
ThreadWatcher.watch board.threads[threadID]
|
ThreadWatcher.add board.threads[threadID]
|
||||||
|
threadUpdate: (e) ->
|
||||||
|
{thread} = e.detail
|
||||||
|
return unless e.detail[404] and ThreadWatcher.db.get {boardID: thread.board.ID, threadID: thread.ID}
|
||||||
|
# Update 404 status.
|
||||||
|
ThreadWatcher.add thread
|
||||||
|
|
||||||
|
fetchCount:
|
||||||
|
fetched: 0
|
||||||
|
fetching: 0
|
||||||
|
fetchAllStatus: ->
|
||||||
|
ThreadWatcher.status.textContent = '...'
|
||||||
|
for thread in ThreadWatcher.getAll()
|
||||||
|
ThreadWatcher.fetchStatus thread
|
||||||
|
return
|
||||||
|
fetchStatus: ({boardID, threadID, data}) ->
|
||||||
|
return if data.isDead
|
||||||
|
{fetchCount} = ThreadWatcher
|
||||||
|
fetchCount.fetching++
|
||||||
|
$.ajax "//api.4chan.org/#{boardID}/res/#{threadID}.json",
|
||||||
|
onloadend: ->
|
||||||
|
fetchCount.fetched++
|
||||||
|
if fetchCount.fetched is fetchCount.fetching
|
||||||
|
fetchCount.fetched = 0
|
||||||
|
fetchCount.fetching = 0
|
||||||
|
status = ''
|
||||||
|
else
|
||||||
|
status = "#{Math.round fetchCount.fetched / fetchCount.fetching * 100}%"
|
||||||
|
ThreadWatcher.status.textContent = status
|
||||||
|
return if @status isnt 404
|
||||||
|
if Conf['Auto Prune']
|
||||||
|
ThreadWatcher.rm boardID, threadID
|
||||||
|
else
|
||||||
|
data.isDead = true
|
||||||
|
ThreadWatcher.db.set {boardID, threadID, val: data}
|
||||||
|
ThreadWatcher.refresh()
|
||||||
|
,
|
||||||
|
type: 'head'
|
||||||
|
|
||||||
|
getAll: ->
|
||||||
|
all = []
|
||||||
|
for boardID, threads of ThreadWatcher.db.data.boards
|
||||||
|
if Conf['Current Board'] and boardID isnt g.BOARD.ID
|
||||||
|
continue
|
||||||
|
for threadID, data of threads
|
||||||
|
all.push {boardID, threadID, data}
|
||||||
|
all
|
||||||
|
|
||||||
|
makeLine: (boardID, threadID, data) ->
|
||||||
|
x = $.el 'a',
|
||||||
|
textContent: '✖'
|
||||||
|
href: 'javascript:;'
|
||||||
|
$.on x, 'click', ThreadWatcher.cb.rm
|
||||||
|
|
||||||
|
if data.isDead
|
||||||
|
href = Redirect.to 'thread', {boardID, threadID}
|
||||||
|
link = $.el 'a',
|
||||||
|
href: href or "/#{boardID}/res/#{threadID}"
|
||||||
|
textContent: data.excerpt
|
||||||
|
title: data.excerpt
|
||||||
|
|
||||||
|
div = $.el 'div'
|
||||||
|
fullID = "#{boardID}.#{threadID}"
|
||||||
|
div.dataset.fullID = fullID
|
||||||
|
$.addClass div, 'current' if g.VIEW is 'thread' and fullID is "#{g.BOARD}.#{g.THREADID}"
|
||||||
|
$.addClass div, 'dead-thread' if data.isDead
|
||||||
|
$.add div, [x, $.tn(' '), link]
|
||||||
|
div
|
||||||
|
refresh: ->
|
||||||
|
nodes = []
|
||||||
|
for {boardID, threadID, data} in ThreadWatcher.getAll()
|
||||||
|
nodes.push ThreadWatcher.makeLine boardID, threadID, data
|
||||||
|
|
||||||
|
{list} = ThreadWatcher
|
||||||
|
$.rmAll list
|
||||||
|
$.add list, nodes
|
||||||
|
|
||||||
|
for threadID, thread of g.BOARD.threads
|
||||||
|
toggler = $ '.watcher-toggler', thread.OP.nodes.post
|
||||||
|
toggler.src = if ThreadWatcher.db.get {boardID: thread.board.ID, threadID}
|
||||||
|
Favicon.default
|
||||||
|
else
|
||||||
|
Favicon.empty
|
||||||
|
|
||||||
|
for refresher in ThreadWatcher.menu.refreshers
|
||||||
|
refresher()
|
||||||
|
return
|
||||||
|
|
||||||
toggle: (thread) ->
|
toggle: (thread) ->
|
||||||
unless $.hasClass $('.watch-thread-link', thread.OP.nodes.post), 'watched'
|
boardID = thread.board.ID
|
||||||
ThreadWatcher.watch thread
|
threadID = thread.ID
|
||||||
|
if ThreadWatcher.db.get {boardID, threadID}
|
||||||
|
ThreadWatcher.rm boardID, threadID
|
||||||
else
|
else
|
||||||
ThreadWatcher.unwatch thread.board, thread.ID
|
ThreadWatcher.add thread
|
||||||
|
add: (thread) ->
|
||||||
|
data = {}
|
||||||
|
boardID = thread.board.ID
|
||||||
|
threadID = thread.ID
|
||||||
|
if thread.isDead
|
||||||
|
if Conf['Auto Prune'] and ThreadWatcher.db.get {boardID, threadID}
|
||||||
|
ThreadWatcher.rm boardID, threadID
|
||||||
|
return
|
||||||
|
data.isDead = true
|
||||||
|
data.excerpt = Get.threadExcerpt thread
|
||||||
|
ThreadWatcher.db.set {boardID, threadID, val: data}
|
||||||
|
ThreadWatcher.refresh()
|
||||||
|
rm: (boardID, threadID) ->
|
||||||
|
ThreadWatcher.db.delete {boardID, threadID}
|
||||||
|
ThreadWatcher.refresh()
|
||||||
|
|
||||||
unwatch: (board, threadID) ->
|
convert: (oldFormat) ->
|
||||||
$.get 'WatchedThreads', {}, (item) ->
|
newFormat = {}
|
||||||
watched = item['WatchedThreads']
|
for boardID, threads of oldFormat
|
||||||
delete watched[board][threadID]
|
for threadID, data of threads
|
||||||
delete watched[board] unless Object.keys(watched[board]).length
|
(newFormat[boardID] or= {})[threadID] = excerpt: data.textContent
|
||||||
ThreadWatcher.refresh watched
|
newFormat
|
||||||
$.set 'WatchedThreads', watched
|
|
||||||
|
|
||||||
watch: (thread) ->
|
menu:
|
||||||
$.get 'WatchedThreads', {}, (item) ->
|
refreshers: []
|
||||||
watched = item['WatchedThreads']
|
init: ->
|
||||||
watched[thread.board] or= {}
|
return if !Conf['Thread Watcher']
|
||||||
watched[thread.board][thread] =
|
menu = new UI.Menu 'thread watcher'
|
||||||
href: "/#{thread.board}/res/#{thread}"
|
$.on $('.menu-button', ThreadWatcher.dialog), 'click', (e) ->
|
||||||
textContent: Get.threadExcerpt thread
|
menu.toggle e, @, ThreadWatcher
|
||||||
ThreadWatcher.refresh watched
|
@addHeaderMenuEntry()
|
||||||
$.set 'WatchedThreads', watched
|
@addMenuEntries()
|
||||||
|
|
||||||
|
addHeaderMenuEntry: ->
|
||||||
|
return if g.VIEW isnt 'thread'
|
||||||
|
entryEl = $.el 'a',
|
||||||
|
href: 'javascript:;'
|
||||||
|
$.event 'AddMenuEntry',
|
||||||
|
type: 'header'
|
||||||
|
el: entryEl
|
||||||
|
order: 60
|
||||||
|
$.on entryEl, 'click', -> ThreadWatcher.toggle g.threads["#{g.BOARD}.#{g.THREADID}"]
|
||||||
|
@refreshers.push ->
|
||||||
|
[addClass, rmClass, text] = if $ '.current', ThreadWatcher.list
|
||||||
|
['unwatch-thread', 'watch-thread', 'Unwatch thread']
|
||||||
|
else
|
||||||
|
['watch-thread', 'unwatch-thread', 'Watch thread']
|
||||||
|
$.addClass entryEl, addClass
|
||||||
|
$.rmClass entryEl, rmClass
|
||||||
|
entryEl.textContent = text
|
||||||
|
|
||||||
|
addMenuEntries: ->
|
||||||
|
entries = []
|
||||||
|
|
||||||
|
# `Open all` entry
|
||||||
|
entries.push
|
||||||
|
cb: ThreadWatcher.cb.openAll
|
||||||
|
entry:
|
||||||
|
type: 'thread watcher'
|
||||||
|
el: $.el 'a',
|
||||||
|
textContent: 'Open all threads'
|
||||||
|
refresh: -> (if ThreadWatcher.list.firstElementChild then $.rmClass else $.addClass) @el, 'disabled'
|
||||||
|
|
||||||
|
# `Check 404'd threads` entry
|
||||||
|
entries.push
|
||||||
|
cb: ThreadWatcher.cb.checkThreads
|
||||||
|
entry:
|
||||||
|
type: 'thread watcher'
|
||||||
|
el: $.el 'a',
|
||||||
|
textContent: 'Check 404\'d threads'
|
||||||
|
refresh: -> (if $('div:not(.dead-thread)', ThreadWatcher.list) then $.rmClass else $.addClass) @el, 'disabled'
|
||||||
|
|
||||||
|
# `Prune 404'd threads` entry
|
||||||
|
entries.push
|
||||||
|
cb: ThreadWatcher.cb.pruneDeads
|
||||||
|
entry:
|
||||||
|
type: 'thread watcher'
|
||||||
|
el: $.el 'a',
|
||||||
|
textContent: 'Prune 404\'d threads'
|
||||||
|
refresh: -> (if $('.dead-thread', ThreadWatcher.list) then $.rmClass else $.addClass) @el, 'disabled'
|
||||||
|
|
||||||
|
# `Settings` entries:
|
||||||
|
subEntries = []
|
||||||
|
for name, conf of Config.threadWatcher
|
||||||
|
subEntries.push @createSubEntry name, conf[1]
|
||||||
|
entries.push
|
||||||
|
entry:
|
||||||
|
type: 'thread watcher'
|
||||||
|
el: $.el 'span',
|
||||||
|
textContent: 'Settings'
|
||||||
|
subEntries: subEntries
|
||||||
|
|
||||||
|
for {entry, cb, refresh} in entries
|
||||||
|
entry.el.href = 'javascript:;' if entry.el.nodeName is 'A'
|
||||||
|
$.on entry.el, 'click', cb if cb
|
||||||
|
@refreshers.push refresh.bind entry if refresh
|
||||||
|
$.event 'AddMenuEntry', entry
|
||||||
|
createSubEntry: (name, desc) ->
|
||||||
|
entry =
|
||||||
|
type: 'thread watcher'
|
||||||
|
el: $.el 'label',
|
||||||
|
innerHTML: "<input type=checkbox name='#{name}'> #{name}"
|
||||||
|
title: desc
|
||||||
|
input = entry.el.firstElementChild
|
||||||
|
input.checked = Conf[name]
|
||||||
|
$.on input, 'change', $.cb.checked
|
||||||
|
$.on input, 'change', ThreadWatcher.refresh if name is 'Current Board'
|
||||||
|
entry
|
||||||
|
|||||||
@ -665,21 +665,9 @@ QR =
|
|||||||
cv.width = img.width = width
|
cv.width = img.width = width
|
||||||
cv.getContext('2d').drawImage img, 0, 0, width, height
|
cv.getContext('2d').drawImage img, 0, 0, width, height
|
||||||
URL.revokeObjectURL fileURL
|
URL.revokeObjectURL fileURL
|
||||||
applyBlob = (blob) =>
|
cv.toBlob (blob) =>
|
||||||
@URL = URL.createObjectURL blob
|
@URL = URL.createObjectURL blob
|
||||||
@nodes.el.style.backgroundImage = "url(#{@URL})"
|
@nodes.el.style.backgroundImage = "url(#{@URL})"
|
||||||
if cv.toBlob
|
|
||||||
cv.toBlob applyBlob
|
|
||||||
return
|
|
||||||
data = atob cv.toDataURL().split(',')[1]
|
|
||||||
|
|
||||||
# DataUrl to Binary code from Aeosynth's 4chan X repo
|
|
||||||
l = data.length
|
|
||||||
ui8a = new Uint8Array l
|
|
||||||
for i in [0...l]
|
|
||||||
ui8a[i] = data.charCodeAt i
|
|
||||||
|
|
||||||
applyBlob new Blob [ui8a], type: 'image/png'
|
|
||||||
|
|
||||||
fileURL = URL.createObjectURL @file
|
fileURL = URL.createObjectURL @file
|
||||||
img.src = fileURL
|
img.src = fileURL
|
||||||
|
|||||||
@ -16,7 +16,6 @@ QuoteYou =
|
|||||||
cb: @node
|
cb: @node
|
||||||
|
|
||||||
node: ->
|
node: ->
|
||||||
# Stop there if it's a clone.
|
|
||||||
return if @isClone
|
return if @isClone
|
||||||
|
|
||||||
if @info.yours
|
if @info.yours
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user