Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
S
scp-browser
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
5
Issues
5
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Eliot Berriot
scp-browser
Commits
b2e5588a
Verified
Commit
b2e5588a
authored
Oct 21, 2017
by
Eliot Berriot
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'release/0.3.5'
parents
353ab726
14b2df74
Pipeline
#219
passed with stages
in 4 minutes and 20 seconds
Changes
12
Pipelines
3
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
480 additions
and
99 deletions
+480
-99
convert_data.py
convert_data.py
+7
-4
package-lock.json
package-lock.json
+19
-31
package.json
package.json
+1
-0
src/App.vue
src/App.vue
+12
-1
src/components/About.vue
src/components/About.vue
+98
-0
src/components/Browse.vue
src/components/Browse.vue
+86
-21
src/components/Detail.vue
src/components/Detail.vue
+12
-12
src/components/Pagination.vue
src/components/Pagination.vue
+53
-0
src/components/ScpLink.vue
src/components/ScpLink.vue
+25
-0
src/components/ScpParagraph.vue
src/components/ScpParagraph.vue
+33
-0
src/router/index.js
src/router/index.js
+16
-1
src/scp-data.js
src/scp-data.js
+118
-29
No files found.
convert_data.py
View file @
b2e5588a
...
...
@@ -212,19 +212,21 @@ def get_text_fields(e):
]
def
get_field
(
raw_name
):
content
=
raw_name
.
replace
(
':'
,
''
)
.
strip
()
.
lower
()
content
=
raw_name
.
replace
(
':'
,
''
)
search_content
=
content
.
strip
()
.
lower
()
for
data
in
known_fields
:
if
data
.
get
(
'startswith'
,
True
):
words
=
content
.
split
(
' '
)
words
=
search_
content
.
split
(
' '
)
match
=
any
((
content
.
startswith
(
matcher
)
or
words
[
-
1
]
.
lower
()
.
startswith
(
matcher
)
search_
content
.
startswith
(
matcher
)
or
words
[
-
1
]
.
lower
()
.
startswith
(
matcher
)
for
matcher
in
data
[
'matchers'
]))
else
:
match
=
any
((
content
==
matcher
for
matcher
in
data
[
'matchers'
]))
match
=
any
((
search_
content
==
matcher
for
matcher
in
data
[
'matchers'
]))
if
match
:
return
{
'name'
:
data
[
'name'
],
'code'
:
data
[
'code'
],
'title'
:
content
,
'keep'
:
data
.
get
(
'keep'
,
True
),
'html'
:
data
.
get
(
'html'
,
False
),
}
...
...
@@ -243,6 +245,7 @@ def get_text_fields(e):
cf
=
{
'name'
:
field_data
[
'name'
],
'code'
:
field_data
[
'code'
],
'title'
:
field_data
[
'title'
],
'paragraphs'
:
[]
}
fields
.
append
(
cf
)
...
...
package-lock.json
View file @
b2e5588a
...
...
@@ -88,6 +88,7 @@
"version"
:
"5.2.3"
,
"resolved"
:
"https://registry.npmjs.org/ajv/-/ajv-5.2.3.tgz"
,
"integrity"
:
"sha1-wG9Zh3jETGsWGrr+NGa4GtGBTtI="
,
"dev"
:
true
,
"requires"
:
{
"co"
:
"4.6.0"
,
"fast-deep-equal"
:
"1.0.0"
,
...
...
@@ -1201,7 +1202,8 @@
"big.js"
:
{
"version"
:
"3.2.0"
,
"resolved"
:
"https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz"
,
"integrity"
:
"sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q=="
"integrity"
:
"sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q=="
,
"dev"
:
true
},
"binary-extensions"
:
{
"version"
:
"1.10.0"
,
...
...
@@ -1726,7 +1728,8 @@
"co"
:
{
"version"
:
"4.6.0"
,
"resolved"
:
"https://registry.npmjs.org/co/-/co-4.6.0.tgz"
,
"integrity"
:
"sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
"integrity"
:
"sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
,
"dev"
:
true
},
"coa"
:
{
"version"
:
"1.0.4"
,
...
...
@@ -2780,7 +2783,8 @@
"emojis-list"
:
{
"version"
:
"2.1.0"
,
"resolved"
:
"https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz"
,
"integrity"
:
"sha1-TapNnbAPmBmIDHn6RXrlsJof04k="
"integrity"
:
"sha1-TapNnbAPmBmIDHn6RXrlsJof04k="
,
"dev"
:
true
},
"encodeurl"
:
{
"version"
:
"1.0.1"
,
...
...
@@ -3665,7 +3669,8 @@
"fast-deep-equal"
:
{
"version"
:
"1.0.0"
,
"resolved"
:
"https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz"
,
"integrity"
:
"sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8="
"integrity"
:
"sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8="
,
"dev"
:
true
},
"fast-levenshtein"
:
{
"version"
:
"2.0.6"
,
...
...
@@ -5012,12 +5017,14 @@
"json-schema-traverse"
:
{
"version"
:
"0.3.1"
,
"resolved"
:
"https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz"
,
"integrity"
:
"sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
"integrity"
:
"sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
,
"dev"
:
true
},
"json-stable-stringify"
:
{
"version"
:
"1.0.1"
,
"resolved"
:
"https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz"
,
"integrity"
:
"sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8="
,
"dev"
:
true
,
"requires"
:
{
"jsonify"
:
"0.0.0"
}
...
...
@@ -5037,7 +5044,8 @@
"json5"
:
{
"version"
:
"0.5.1"
,
"resolved"
:
"https://registry.npmjs.org/json5/-/json5-0.5.1.tgz"
,
"integrity"
:
"sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE="
"integrity"
:
"sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE="
,
"dev"
:
true
},
"jsonfile"
:
{
"version"
:
"4.0.0"
,
...
...
@@ -5051,7 +5059,8 @@
"jsonify"
:
{
"version"
:
"0.0.0"
,
"resolved"
:
"https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz"
,
"integrity"
:
"sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM="
"integrity"
:
"sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM="
,
"dev"
:
true
},
"jsonpointer"
:
{
"version"
:
"4.0.1"
,
...
...
@@ -5394,6 +5403,7 @@
"version"
:
"1.1.0"
,
"resolved"
:
"https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz"
,
"integrity"
:
"sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0="
,
"dev"
:
true
,
"requires"
:
{
"big.js"
:
"3.2.0"
,
"emojis-list"
:
"2.1.0"
,
...
...
@@ -5413,8 +5423,7 @@
"lodash"
:
{
"version"
:
"4.17.4"
,
"resolved"
:
"https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz"
,
"integrity"
:
"sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
,
"dev"
:
true
"integrity"
:
"sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
},
"lodash._arraycopy"
:
{
"version"
:
"3.0.0"
,
...
...
@@ -9666,6 +9675,7 @@
"version"
:
"0.3.0"
,
"resolved"
:
"https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz"
,
"integrity"
:
"sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8="
,
"dev"
:
true
,
"requires"
:
{
"ajv"
:
"5.2.3"
}
...
...
@@ -9789,11 +9799,6 @@
"integrity"
:
"sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
,
"dev"
:
true
},
"simple-web-worker"
:
{
"version"
:
"1.2.0"
,
"resolved"
:
"https://registry.npmjs.org/simple-web-worker/-/simple-web-worker-1.2.0.tgz"
,
"integrity"
:
"sha1-Le/9CZiXa4JLFBonzlBNMdDZkQg="
},
"sinon"
:
{
"version"
:
"4.0.1"
,
"resolved"
:
"https://registry.npmjs.org/sinon/-/sinon-4.0.1.tgz"
,
...
...
@@ -10840,14 +10845,6 @@
"integrity"
:
"sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg=="
,
"dev"
:
true
},
"vue-worker"
:
{
"version"
:
"1.2.1"
,
"resolved"
:
"https://registry.npmjs.org/vue-worker/-/vue-worker-1.2.1.tgz"
,
"integrity"
:
"sha1-3q4UuYqdidqrsg5/xhkGQoSbbZM="
,
"requires"
:
{
"simple-web-worker"
:
"1.2.0"
}
},
"watchpack"
:
{
"version"
:
"1.4.0"
,
"resolved"
:
"https://registry.npmjs.org/watchpack/-/watchpack-1.4.0.tgz"
,
...
...
@@ -11194,15 +11191,6 @@
"integrity"
:
"sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
,
"dev"
:
true
},
"worker-loader"
:
{
"version"
:
"1.0.0"
,
"resolved"
:
"https://registry.npmjs.org/worker-loader/-/worker-loader-1.0.0.tgz"
,
"integrity"
:
"sha512-dUwgs4Rdi1qG3VciM1+EPgAoO8m9USpCXxE3xmpWrnHJSMKGkzpCUNeYLjBRgYcSkf2A5xnXpR450Wqtu+pq0w=="
,
"requires"
:
{
"loader-utils"
:
"1.1.0"
,
"schema-utils"
:
"0.3.0"
}
},
"wrap-ansi"
:
{
"version"
:
"2.1.0"
,
"resolved"
:
"https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz"
,
...
...
package.json
View file @
b2e5588a
...
...
@@ -17,6 +17,7 @@
"dependencies"
:
{
"
compressjs
"
:
"
^1.0.3
"
,
"
jquery
"
:
"
^3.2.1
"
,
"
lodash
"
:
"
^4.17.4
"
,
"
lz-string
"
:
"
^1.4.4
"
,
"
semantic-ui-css
"
:
"
^2.2.12
"
,
"
vue
"
:
"
^2.4.2
"
,
...
...
src/App.vue
View file @
b2e5588a
...
...
@@ -3,9 +3,18 @@
<div
class=
"ui secondary pointing menu"
>
<div
class=
"ui container"
>
<router-link
class=
"ui item"
active-class=
"active"
to=
"/browse"
>
Browse
</router-link>
<router-link
class=
"ui item"
active-class=
"active"
to=
"/about"
>
About
</router-link>
</div>
</div>
<router-view/>
<div
class=
"ui vertical footer segment form-page"
>
<div
class=
"ui divider"
></div>
<div
class=
"ui text container"
>
Eliot Berriot, no rights reserved. All SCP data is extracteed from
<a
href=
"http://scp-wiki.net/"
>
scp-wiki.net
</a>
and published under the
<a
href=
"http://creativecommons.org/licenses/by-sa/3.0/"
>
Creative Commons Attribution-ShareAlike 3.0 License
</a>
</div>
</div>
</div>
</
template
>
...
...
@@ -21,7 +30,9 @@ export default {
#app
{
padding
:
1rem
;
}
#app
>
.menu
{
margin-bottom
:
2rem
;
}
.ellipsis
{
text-overflow
:
ellipsis
;
overflow
:
hidden
;
...
...
src/components/About.vue
0 → 100644
View file @
b2e5588a
<
template
>
<div
class=
"ui text container"
>
<h1
class=
"ui header"
>
About this project
</h1>
<p>
I worked for many years as a Foundation agent, and I can say without a
doubt I saw some really, really scary stuff during my assignments.
Procedure 110-Montauk isn't even the worst.
<p>
During one mission involving SCP-███, I had access to a full backup
of Foundation files. The Foundation executives are not especially known
for showing mercy in case of unauthorized accesses to highly classified
data.
</p>
<p>
In order to survive, I used my brand new knowledge of two Keter-class
objects to trigger a containment breach and escape.
</p>
<p>
This has been three months since I escaped. I had the time to read most of those files,
and I don't think they should remain secret anymore: people have the right
to know what atrocities the Foundation is dealing with, and commiting
to "protect" them.
</p>
<p>
Today, I'm releasing those files. Hopefully, humanity is now strong
enough to face the truth. They'll probably find me soon though, so I don't
really care anymore.
</p>
<p>
By the way, it's not a prank. It's real.
<em>
Everything
</em>
is real.
</p>
<strong>
Chuck
</strong>
<div
class=
"ui divider"
></div>
<p>
In case you don't believe the previous explanation (I can't blame you),
here is another one.
</p>
<p>
This is simply a pet project. I really like those SCP stories, and I found
it hard sometimes to browse the wiki to find
<router-link
:to=
"
{path: '/browse', query: {objectClass: 'neutralized'}}">
all the neutralized objects
</router-link>
,
<router-link
:to=
"
{path: '/browse', query: {objectClass: 'keter', sort: '-comments'}}">
the most commented Keters
</router-link>
or
<router-link
:to=
"
{path: '/browse', query: {objectClass: 'thaumiel'}}">
all those fancy Thaumiels.
</router-link>
</p>
<p>
Using SCP browser, you can filter SCPs, read them and navigate between them.
The project itself is not intended to replace scp-wiki.net at all, as
the wiki is the unique and up-to-date data source. Simply think of this as another
interface to browse the same data.
</p>
<p>
Maintaining this is also a way for me to discover new technologies and
programming libraries. The source code behind this is open ad free for anyone
to reuse or hack on.
</p>
<p>
Thank you for reading this, here are a few useful links:
</p>
<ul
class=
"ui list"
>
<li>
<a
href=
"https://code.eliotberriot.com/eliotberriot/scp-browser/"
>
Project repository and issue tracker
</a>
</li>
<li>
<a
href=
"https://scp.eliotberriot.com/data.json"
>
Data extracted from scp-wiki.net, as a JSON file
</a>
</li>
<li>
<a
href=
"mailto:contact@eliotberriot.com"
>
contact@eliotberriot.com
</a>
is my email, if you need to contact me for anything regarding this project
</li>
<li>
I'm
<a
href=
"https://mastodon.eliotberriot.com/@eliotberriot"
>
@eliotberriot@mastodon.eliotberriotcom
</a>
on Mastodon. Feel free to say hi :)
</li>
</ul>
</div>
</
template
>
<
script
>
export
default
{}
</
script
>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<
style
scoped
>
</
style
>
src/components/Browse.vue
View file @
b2e5588a
<
template
>
<div>
<data-loader
:loading=
"scpData.loading"
></data-loader>
<div
v-if=
"!scpData.loading"
class=
"ui grid"
>
<div
class=
"
three wide
column"
>
<div
v-if=
"!scpData.loading"
class=
"ui
stackable
grid"
>
<div
class=
"
four wide tablet
column"
>
<h2>
Filters
</h2>
<div
class=
"ui form"
>
<button
class=
"ui fluid basic icon button"
@
click=
"scpData.initFilters()"
>
...
...
@@ -28,13 +28,14 @@
</div>
<div
class=
"ui segment"
>
<h3
class=
"ui header"
>
Search
</h3>
<div
class=
"ui icon input"
>
<div
class=
"ui
fluid
icon input"
>
<i
class=
"search icon"
></i>
<input
type=
"text"
id=
"search"
placeholder=
"666, Red ice, clown, shadow..."
v-model=
"scpData.filtersById.search.query"
>
@
input=
"updateSearch"
:value=
"scpData.filtersById.search.value"
>
</div>
</div>
<div
class=
'ui segment'
>
...
...
@@ -44,12 +45,12 @@
type=
"checkbox"
@
change=
"toggleAll('objectClass')"
id=
"objectclass-toggle-all"
:checked=
"scpData.filtersById.objectClass.choices.length === scpData.filtersById.objectClass.
selected
.length"
>
:checked=
"scpData.filtersById.objectClass.choices.length === scpData.filtersById.objectClass.
value
.length"
>
<label
for=
"objectclass-toggle-all"
>
Select all
</label>
</div>
<div
class=
"choices"
>
<div
class=
"inline field"
v-for=
"choice in scpData.filtersById.objectClass.choices"
>
<input
type=
"checkbox"
:id=
"'objectclass' + choice.id"
:value=
"choice.id"
v-model=
"scpData.filtersById.objectClass.
selected
"
>
<input
type=
"checkbox"
:id=
"'objectclass' + choice.id"
:value=
"choice.id"
v-model=
"scpData.filtersById.objectClass.
value
"
>
<span
:class=
"['ui', scpData.getObjectClassColor(choice.id), 'empty', 'circular', 'label']"
></span>
<label
:for=
"'objectclass' + choice.id"
>
{{
choice
.
label
}}
</label>
</div>
...
...
@@ -57,7 +58,7 @@
</div>
<div
class=
'ui segment'
>
<h3
class=
"ui header"
>
Tags
</h3>
<select
v-model=
"scpData.filtersById.tags.
selected
"
class=
"ui fluid tags dropdown"
multiple
>
<select
v-model=
"scpData.filtersById.tags.
value
"
class=
"ui fluid tags dropdown"
multiple
>
<option
v-for=
"tag in scpData.filtersById.tags.choices"
:value=
"tag.id"
>
{{
tag
.
label
}}
</option>
</select>
</div>
...
...
@@ -66,14 +67,21 @@
</div>
<div
class=
"twelve wide column"
>
<h1>
{{
filteredScps
.
length
}}
SCPs matching your criteria
</h1>
<pagination
v-if=
"totalPages > 1"
:current=
"scpData.page"
:total=
"totalPages"
@
page-selected=
"selectPage"
></pagination>
<div
class=
"ui hidden divider"
></div>
<div
class=
"ui link stackable fluid cards"
>
<div
v-for=
"scp in filteredScps.slice(0, 100)"
class=
"card"
>
<div
v-if=
"scp.images[0]"
class=
"ui small image"
v-lazy:background-image=
"scp.images[0].src"
></div>
<div
v-else
class=
"ui small image no-image"
></div>
<div
v-for=
"scp in filteredScps.slice((scpData.page - 1) * scpData.pageSize, scpData.page * scpData.pageSize)"
class=
"card"
>
<router-link
v-if=
"scp.images[0]"
tag=
"div"
:to=
"
{name: 'detail', params: {id: scp.id }}"
class="ui small image" v-lazy:background-image="scp.images[0].src">
</router-link>
</router-link>
<router-link
v-else
class=
"ui small image no-image"
:to=
"
{name: 'detail', params: {id: scp.id }}">
</router-link>
<div
class=
"content"
>
<
div
class=
"header ellipsis
"
>
<
router-link
class=
"header ellipsis"
:to=
"
{name: 'detail', params: {id: scp.id }}
">
#
{{
scp
.
id
}}
-
{{
scp
.
name
}}
</
div
>
</
router-link
>
<div
class=
"meta"
>
<span
:class=
"['ui', scpData.getObjectClassColor(scp.object_class), 'tiny', 'label']"
>
{{
scpData
.
data
.
object_classes
[
scp
.
object_class
].
label
}}
...
...
@@ -102,6 +110,8 @@
</div>
</div>
</div>
<div
class=
"ui hidden divider"
></div>
<pagination
v-if=
"totalPages > 1"
:current=
"scpData.page"
:total=
"totalPages"
@
page-selected=
"selectPage"
></pagination>
</div>
</div>
</div>
...
...
@@ -111,10 +121,16 @@
import
scpData
from
'
@/scp-data
'
import
$
from
'
jquery
'
import
DataLoader
from
'
./DataLoader
'
import
Pagination
from
'
./Pagination
'
import
_
from
'
lodash
'
export
default
{
props
:
{
initialFilters
:
{
type
:
Object
,
required
:
false
}
},
components
:
{
DataLoader
DataLoader
,
Pagination
},
data
()
{
let
d
=
{
...
...
@@ -123,26 +139,65 @@ export default {
return
d
},
mounted
:
function
()
{
this
.
scpData
.
load
()
$
(
this
.
$el
).
find
(
'
.ui.checkbox
'
).
checkbox
()
$
(
this
.
$el
).
find
(
'
.ui.dropdown
'
).
dropdown
()
let
self
=
this
let
existing
=
this
.
scpData
.
currentFilters
()
let
filters
=
this
.
scpData
.
filtersFromQs
(
this
.
initialFilters
)
this
.
scpData
.
load
(
function
()
{
self
.
$nextTick
(
function
()
{
$
(
self
.
$el
).
find
(
'
.ui.dropdown
'
).
dropdown
()
$
(
self
.
$el
).
find
(
'
.ui.checkbox
'
).
checkbox
()
})
self
.
updateQs
()
},
Object
.
assign
(
existing
,
filters
))
},
methods
:
{
toggleAll
:
function
(
filter
)
{
let
f
=
this
.
scpData
.
filtersById
[
filter
]
if
(
f
.
selected
.
length
===
f
.
choices
.
length
)
{
f
.
selected
=
[]
if
(
f
.
value
.
length
===
f
.
choices
.
length
)
{
f
.
value
=
[]
}
else
{
f
.
selected
=
f
.
choices
.
map
(
e
=>
{
return
e
.
id
})
f
.
value
=
f
.
choices
.
map
(
e
=>
{
return
e
.
id
})
}
}
},
updateQs
:
function
()
{
let
query
=
{}
this
.
scpData
.
filters
.
forEach
(
f
=>
{
let
q
=
f
.
toQs
(
f
.
value
)
if
(
q
.
length
>
0
)
{
query
[
f
.
id
]
=
q
}
})
if
(
this
.
scpData
.
sortAscending
)
{
query
.
sort
=
this
.
scpData
.
sortField
}
else
{
query
.
sort
=
'
-
'
+
this
.
scpData
.
sortField
}
query
.
page
=
this
.
scpData
.
page
this
.
$router
.
push
({
query
})
},
selectPage
:
function
(
page
,
scroll
)
{
this
.
scpData
.
page
=
page
window
.
scrollTo
(
0
,
0
)
},
updateSearch
:
_
.
debounce
(
function
(
e
)
{
this
.
scpData
.
filtersById
.
search
.
value
=
e
.
target
.
value
},
250
)
},
computed
:
{
filteredScps
:
function
()
{
return
this
.
scpData
.
filteredScps
()
},
selectedTags
:
function
()
{
return
this
.
scpData
.
filtersById
.
tags
.
selected
return
this
.
scpData
.
filtersById
.
tags
.
value
},
filtersValues
:
function
()
{
return
this
.
scpData
.
filters
.
map
(
e
=>
{
return
e
.
value
})
},
sort
:
function
()
{
return
[
this
.
scpData
.
sortField
,
this
.
scpData
.
sortAscending
]
},
totalPages
:
function
()
{
return
this
.
filteredScps
.
length
/
this
.
scpData
.
pageSize
}
},
watch
:
{
...
...
@@ -150,6 +205,16 @@ export default {
if
(
v
.
length
===
0
)
{
$
(
this
.
$el
).
find
(
'
.ui.dropdown.tags
'
).
dropdown
(
'
set exactly
'
,
[])
}
},
filtersValues
:
function
()
{
this
.
scpData
.
page
=
1
this
.
updateQs
()
},
sort
:
function
()
{
this
.
updateQs
()
},
'
scpData.page
'
:
function
()
{
this
.
updateQs
()
}
}
}
...
...
src/components/Detail.vue
View file @
b2e5588a
...
...
@@ -3,8 +3,8 @@
<data-loader
:loading=
"scpData.loading"
></data-loader>
<div
v-if=
"scp"
class=
"ui stackable grid"
>
<div
class=
"four wide column"
>
<div
class=
"ui vertical menu"
>
<router-link
to=
"/browse
"
class=
"item"
>
<div
class=
"ui vertical
fluid
menu"
>
<router-link
:to=
"
{path: '/browse', query: this.scpData.toQs()}
" class="item">
<i
class=
"block list icon"
></i>
Back to list
</router-link>
...
...
@@ -63,13 +63,12 @@
<div
class=
"eight wide column"
>
<div
class=
"ui fluid text container"
>
<h1>
SCP #
{{
scp
.
id
}}
-
{{
scp
.
name
}}
</h1>
<template
v-if=
"scpData.getTextField(scp, 'procedures')"
>
<h2
class=
"ui header"
>
Special Containment Procedure
</h2>
<p
v-for=
"paragraph in scpData.getTextField(scp, 'procedures')"
>
{{
paragraph
}}
</p>
</
template
>
<
template
v-if=
"scpData.getTextField(scp, 'description')"
>
<h2
class=
"ui header"
>
Description
</h2>
<p
v-for=
"paragraph in scpData.getTextField(scp, 'description')"
>
{{
paragraph
}}
</p>
<template
v-for=
"textField in scp.text_fields"
>
<h2>
{{
textField
.
title
}}
</h2>
<scp-paragraph
v-for=
"(paragraph, i) in textField.paragraphs"
:key=
"scp.id + textField.code + i"
:paragraph=
"paragraph"
></scp-paragraph>
</
template
>
</div>
</div>
...
...
@@ -85,6 +84,7 @@
<
script
>
import
scpData
from
'
@/scp-data
'
import
ScpParagraph
from
'
./ScpParagraph
'
import
DataLoader
from
'
./DataLoader
'
export
default
{
...
...
@@ -92,7 +92,8 @@ export default {
id
:
{
required
:
true
}
},
components
:
{
DataLoader
DataLoader
,
ScpParagraph
},
beforeRouteUpdate
(
to
,
from
,
next
)
{
this
.
realId
=
parseInt
(
to
.
params
.
id
,
'
10
'
)
...
...
@@ -106,7 +107,7 @@ export default {
return
d
},
mounted
:
function
()
{
this
.
scpData
.
load
()
this
.
scpData
.
load
(
function
()
{},
null
)
},
methods
:
{
browse
:
function
(
filterData
)
{
...
...
@@ -158,5 +159,4 @@ export default {
<!-- Add "scoped" attribute to limit CSS to this component only -->
<
style
scoped
>
</
style
>
src/components/Pagination.vue
0 → 100644
View file @
b2e5588a
<
template
>
<div
class=
"ui pagination menu"
>
<span
:class=
"['ui',
{'disabled': current
<
=
1
},
'
item
']"
@
click=
"current > 1 && $emit('page-selected', current - 1)"
>
<i
class=
"angle left icon"
></i>
</span>
<template
v-for=
"p in humanRange"
>
<span
v-if=
"!isNaN(parseInt(p, '10'))"
@
click=
"$emit('page-selected', p)"
:class=
"[
{'active': p === current }, 'item']">
{{
p
}}
</span>
<span
v-else
class=
"ui disabled item"
>
{{
ellipsis
}}
</span>
</
template
>
<span
:class=
"['ui', {'disabled': current >= total}, 'item']"
@
click=
"current < total && $emit('page-selected', current - 1)"
>
<i
class=
"angle right icon"
></i>
</span>
</div>
</template>
<
script
>
export
default
{
props
:
[
'
total
'
,
'
current
'
],
data
:
function
()
{
return
{
ellipsis
:
'
...
'
}
},
computed
:
{
humanRange
:
function
()
{
let
padding
=
2
let
r
=
[]
for
(
var
i
=
1
;
i
<=
this
.
total
+
1
;
i
++
)
{
if
(
i
<=
padding
)
{
r
.
push
(
i
)
}
else
if
(
i
>
this
.
current
-
padding
&&
i
<
this
.
current
+
padding
)
{
r
.
push
(
i
)
}
else
if
(
i
>
this
.
total
+
1
-
padding
)
{
r
.
push
(
i
)
}
else
{
if
(
r
.
slice
(
-
1
)[
0
]
!==
this
.
ellipsis
)
{
r
.
push
(
this
.
ellipsis
)
}
}
}
return
r
}
}
}
</
script
>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<
style
scoped
>
</
style
>
src/components/ScpLink.vue
0 → 100644
View file @
b2e5588a
<
template
>
<router-link
:to=
"
{name: 'detail', params: {id: realId}}" :data-tooltip="scp.name">
<slot></slot>
</router-link>
</
template
>
<
script
>
import
scpData
from
'
@/scp-data
'
export
default
{
props
:
[
'
id
'
],
computed
:
{
realId
:
function
()
{
return
parseInt
(
this
.
id
,
'
10
'
)
},
scp
:
function
()
{
return
scpData
.
data
.
scps
[
this
.
realId
]
}
}
}
</
script
>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<
style
scoped
>
</
style
>
src/components/ScpParagraph.vue
0 → 100644
View file @
b2e5588a
<
template
>
<p><component
:is=
"output"
></component></p>
</
template
>
<
script
>
import
Vue
from
'
vue
'
import
ScpLink
from
'
./ScpLink
'
export
default
{
props
:
[
'
paragraph
'
],
computed
:
{
output
()
{
let
content
=
this
.
setScpLinks
(
this
.
paragraph
)
let
temp
=
Vue
.
compile
(
`<span>
${
content
}
</span>`
)
temp
.
components
=
{
ScpLink
}
return
temp
}
},
methods
:
{
setScpLinks
:
function
(
p
)
{
let
scpRegexp
=
new
RegExp
(
/SCP-
(\d
*
)(
-
[\d
*
])?
/
,
'
gi
'
)
let
final
=
p
.
replace
(
scpRegexp
,
'
<scp-link :id="parseInt(
\'
$1
\'
,
\'
10
\'
)">SCP-$1$2</scp-link>
'
)
return
final
}
}
}
</
script
>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<
style
scoped
>
</
style
>
src/router/index.js
View file @
b2e5588a
import
Vue
from
'
vue
'
import
Router
from
'
vue-router
'
import
Home
from
'
@/components/Home
'
import
About
from
'
@/components/About
'
import
Browse
from
'
@/components/Browse
'
import
Detail
from
'
@/components/Detail
'
...
...
@@ -8,16 +9,30 @@ Vue.use(Router)
export
default
new
Router
({
mode
:
'
history
'
,
scrollBehavior
(
to
,
from
,
savedPosition
)
{
return
{
x
:
0
,
y
:
0
}
},
routes
:
[
{
path
:
'
/
'
,
name
:
'
Home
'
,
component
:
Home
},
{
path
:
'
/about
'
,
name
:
'
About
'
,
component
:
About
},
{
path
:
'
/browse
'
,
name
:
'
Browse
'
,
component
:
Browse
component
:
Browse
,
props
:
(
route
)
=>
({
initialFilters
:
route
.
query
})
},
{
path
:
'
/browse/:id
'
,
...
...
src/scp-data.js
View file @
b2e5588a
...
...
@@ -12,6 +12,8 @@ var scpData = {
searchIndex
:
null
,
sortAscending
:
true
,
sortField
:
'
id
'
,
page
:
1
,
pageSize
:
40
,
sortFields
:
[