['),
43 | # 'no_spans': True,
44 | # 'no_jump': False
45 | # },
46 | # single tag
47 | {
48 | 'type': 'single',
49 | 'start_regex': re.compile(r'()'),
50 | 'end_regex': None,
51 | 'no_spans': True,
52 | 'no_jump': True
53 | },
54 | # Math, timeline, nowiki tags
55 | {
56 | 'type': 'block',
57 | 'start_regex': re.compile(r'<(math|timeline|nowiki)[^>]*>'),
58 | 'end_regex': re.compile(r'(math|timeline|nowiki)>'),
59 | 'no_spans': True,
60 | 'no_jump': False
61 | },
62 | # General HTML tag - only for text between <(tag) and > (tag name and attributes)
63 | {
64 | # 'type': 'single',
65 | # 'start_regex': re.compile(r'<\/?(ref|blockquote|ul|li)[^>]*>'),
66 | # 'end_regex': None,
67 | # 'no_spans': True,
68 | # 'no_jump': True
69 | 'type': 'block',
70 | 'start_regex': re.compile(r'<\/?(ref|h1|h2|h3|h4|h5|h6|p|br|hr|!--|abbr|b|bdi|bdo|blockquote|cite|code|data|del|dfn|em|i|ins|kbd|mark|pre|q|ruby|rt|rp|s|samp|small|strong|sub|sup|time|u|var|wbr|dl|dt|dd|ol|ul|li|div|span|table|tr|td|th|caption)'),
71 | 'end_regex': re.compile(r'>'),
72 | 'no_spans': True,
73 | 'no_jump': False
74 | },
75 |
76 | # Headings
77 | {
78 | 'type': 'single',
79 | 'start_regex': re.compile(r'(=+|;)'),
80 | 'end_regex': None,
81 | 'no_spans': True,
82 | 'no_jump': True
83 | },
84 | # Lists and blocks
85 | {
86 | 'type': 'block',
87 | 'start_regex': re.compile(r'[\\*#\\:]*;'),
88 | 'end_regex': re.compile(r'\\:'),
89 | 'no_spans': True,
90 | 'no_jump': False
91 | },
92 | {
93 | 'type': 'single',
94 | 'start_regex': re.compile(r'[\\*#:]+'),
95 | 'end_regex': None,
96 | 'no_spans': True,
97 | 'no_jump': True
98 | },
99 | # Horizontal lines
100 | {
101 | 'type': 'single',
102 | 'start_regex': re.compile(r'-----*'),
103 | 'end_regex': None,
104 | 'no_spans': True,
105 | 'no_jump': True
106 | },
107 | # Table formatting
108 | # {
109 | # 'type': 'single',
110 | # 'start_regex': re.compile(r'(?<={})({\\||\\|}|\\|-|\\|\\+|\\|\\||)'.format(REGEX_HELPER_PATTERN)),
111 | # 'end_regex': None,
112 | # 'no_spans': True,
113 | # 'no_jump': True
114 | # },
115 | {
116 | 'type': 'block',
117 | 'start_regex': re.compile(r'{\|'),
118 | 'end_regex': re.compile(r'\|}'),
119 | 'no_spans': True,
120 | 'no_jump': False
121 | },
122 | # Linebreaks
123 | {
124 | 'type': 'single',
125 | 'start_regex': re.compile(r'({})+'.format(REGEX_HELPER_PATTERN)),
126 | 'end_regex': None,
127 | 'no_spans': True,
128 | 'no_jump': True
129 | },
130 | # HTML Escape Sequences
131 | {
132 | 'type': 'single',
133 | 'start_regex': re.compile(r'( |€|"|&|<|>| |&(?:[a-z\d]+|#\d+|#x[a-f\d]+);)'),
134 | 'end_regex': None,
135 | 'no_spans': True,
136 | 'no_jump': True
137 | },
138 | # Magic words
139 | {
140 | 'type': 'single',
141 | 'start_regex': re.compile(r'__(NOTOC|FORCETOC|TOC|NOEDITSECTION|NEWSECTIONLINK|NONEWSECTIONLINK|NOGALLERY|'
142 | r'HIDDENCAT|NOCONTENTCONVERT|NOCC|NOTITLECONVERT|NOTC|START|END|INDEX|NOINDEX|'
143 | r'STATICREDIRECT|DISAMBIG)__'),
144 | 'end_regex': None,
145 | 'no_spans': True,
146 | 'no_jump': True
147 | },
148 | # Apostrophes for formatting
149 | {
150 | 'type': 'single',
151 | 'start_regex': re.compile(r'\'\'+'),
152 | 'end_regex': None,
153 | 'no_spans': True,
154 | 'no_jump': True
155 | }
156 | )
157 |
--------------------------------------------------------------------------------
/WhoColor/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 |
4 | :Authors:
5 | Kenan Erdogan
6 | """
7 | import requests
8 | import hashlib
9 | from dateutil import parser
10 | from datetime import datetime
11 |
12 |
13 | class WikipediaRevText(object):
14 | """
15 | Example usage:
16 | wp_rev_text_obj = WikipediaRevText(page_title='Cologne', page_id=6187)
17 | # to get rev wiki text from wp api
18 | rev_data = wp_rev_text_obj.get_rev_wiki_text()
19 | # to convert (extended) wiki text into html by using wp api
20 | rev_extended_html = wp_rev_text_obj.convert_wiki_text_to_html(wiki_text=rev_data['rev_text'])
21 | """
22 |
23 | def __init__(self, page_title=None, page_id=None, rev_id=None, language='en'):
24 | """
25 | :param page_title: Title of an article.
26 | :param page_id: ID of an article
27 | :param rev_id: Revision id to get wiki text.
28 | :param language: Language of the page.
29 | """
30 | self.page_id = page_id
31 | self.page_title = page_title
32 | self.rev_id = rev_id
33 | self.language = language
34 |
35 | def _prepare_request(self, wiki_text=None):
36 | data = {'url': 'https://{}.wikipedia.org/w/api.php'.format(self.language)}
37 | if wiki_text is None:
38 | params = {'action': 'query', 'prop': 'revisions',
39 | 'rvprop': 'content|ids', 'rvlimit': '1', 'format': 'json'}
40 | if self.page_id:
41 | params.update({'pageids': self.page_id})
42 | elif self.page_title:
43 | params.update({'titles': self.page_title})
44 | if self.rev_id is not None:
45 | params.update({'rvstartid': self.rev_id}) # , 'rvendid': rev_id})
46 | else:
47 | params = {'action': 'parse', 'title': self.page_title,
48 | 'format': 'json', 'text': wiki_text, 'prop': 'text'}
49 | data['data'] = params
50 | return data
51 |
52 | def _make_request(self, data):
53 | response = requests.post(**data).json()
54 | return response
55 |
56 | def get_rev_wiki_text(self):
57 | """
58 | If no rev id is given, text of latest revision is returned.
59 | If both article id and title are given, id is used in query.
60 | """
61 | if self.page_id is None and self.page_title is None:
62 | raise Exception('Please provide id or title of the article.')
63 |
64 | data = self._prepare_request()
65 | response = self._make_request(data)
66 |
67 | if 'error' in response:
68 | return response
69 | pages = response['query']['pages']
70 | if '-1' in pages:
71 | return pages
72 | for page_id, page in response['query']['pages'].items():
73 | namespace = page['ns']
74 | revisions = page.get('revisions')
75 | if revisions is None:
76 | return None
77 | else:
78 | return {
79 | 'page_id': int(page_id),
80 | 'namespace': namespace,
81 | 'rev_id': revisions[0]['revid'],
82 | 'rev_text': revisions[0]['*']
83 | }
84 |
85 | def convert_wiki_text_to_html(self, wiki_text):
86 | """
87 | Title of the article is required.
88 | """
89 | if self.page_title is None:
90 | raise Exception('Please provide title of the article.')
91 |
92 | data = self._prepare_request(wiki_text)
93 | response = self._make_request(data)
94 |
95 | if 'error' in response:
96 | return response
97 |
98 | return response['parse']['text']['*']
99 |
100 |
101 | class WikipediaUser(object):
102 | """
103 | Example usage to get names of given editor ids:
104 | editor_ids = set(('30764272', '1465', '5959'))
105 | wp_user_obj = WikipediaUser()
106 | editors = wp_user_obj.get_editor_names(editor_ids)
107 | """
108 | def __init__(self, language='en'):
109 | self.language = language
110 |
111 | def _prepare_request(self, editor_ids):
112 | params = {'action': 'query', 'list': 'users',
113 | 'format': 'json', 'ususerids': '|'.join(editor_ids)}
114 | return {
115 | 'url': 'https://{}.wikipedia.org/w/api.php'.format(self.language),
116 | 'data': params
117 | }
118 |
119 | def _make_request(self, data):
120 | response = requests.post(**data).json()
121 | return response
122 |
123 | def get_editor_names(self, editor_ids, batch_size=50):
124 | """
125 | :param editor_ids: list of editor ids
126 | :param batch_size: number of editor ids (ususerids) in the query. WP allows 50 if not logged in.
127 | :return: a dict {editor_id: editor_name}
128 | """
129 | editor_ids = list(editor_ids)
130 | editor_names = {} # {editor_id: editor_name, ..}
131 | editors_len = len(editor_ids)
132 |
133 | c = 1
134 | while True:
135 | data = self._prepare_request(editor_ids[batch_size*(c-1):batch_size*c])
136 | response = self._make_request(data)
137 |
138 | if 'error' in response:
139 | return response
140 | users = response['query']['users']
141 | if '-1' in users:
142 | return users
143 |
144 | for user in users:
145 | editor_names[str(user['userid'])] = user.get('name', None)
146 |
147 | if batch_size*c >= editors_len:
148 | break
149 | c += 1
150 | return editor_names
151 |
152 |
153 | class WikiWhoRevContent(object):
154 | """
155 | Example usage:
156 | ww_rev_content_obj = WikiWhoRevContent(page_id=6187)
157 | wikiwho_data = ww_rev_content_obj.get_revisions_and_tokens()
158 | """
159 | def __init__(self, page_id=None, page_title=None, rev_id=None, language='en'):
160 | self.page_id = page_id
161 | self.page_title = page_title
162 | self.rev_id = rev_id
163 | self.language = language
164 |
165 | def _prepare_request(self, rev_ids=False):
166 | ww_api_url = 'https://www.wikiwho.net/{}/api/v1.0.0-beta'.format(self.language)
167 | if rev_ids:
168 | if self.page_id:
169 | url_params = 'page_id/{}'.format(self.page_id)
170 | elif self.page_title:
171 | url_params = '{}'.format(self.page_title)
172 | return {'url': '{}/rev_ids/{}/'.format(ww_api_url, url_params),
173 | 'params': {'editor': 'true', 'timestamp': 'true'}}
174 | else:
175 | if self.page_id:
176 | url_params = 'page_id/{}'.format(self.page_id)
177 | elif self.rev_id:
178 | url_params = '{}/{}'.format(self.page_title, self.rev_id)
179 | elif self.page_title:
180 | url_params = '{}'.format(self.page_title)
181 | return {'url': '{}/rev_content/{}/'.format(ww_api_url, url_params),
182 | 'params': {'o_rev_id': 'true', 'editor': 'true',
183 | 'token_id': 'false', 'out': 'true', 'in': 'true'}}
184 |
185 | def _make_request(self, data):
186 | response = requests.get(**data).json()
187 | return response
188 |
189 | def get_revisions_data(self):
190 | # get revisions-editors
191 | data = self._prepare_request(rev_ids=True)
192 | response = self._make_request(data)
193 | # {rev_id: [timestamp, parent_id, editor]}
194 | revisions = {response['revisions'][0]['id']: [response['revisions'][0]['timestamp'],
195 | 0,
196 | response['revisions'][0]['editor']]}
197 | for i, rev in enumerate(response['revisions'][1:]):
198 | revisions[rev['id']] = [rev['timestamp'],
199 | response['revisions'][i]['id'], # parent = previous rev id
200 | rev['editor']]
201 | return revisions
202 |
203 | def get_editor_names(self, revisions):
204 | # get editor names from wp api
205 | editor_ids = {rev_data[2] for rev_id, rev_data in revisions.items()
206 | if rev_data[2] and not rev_data[2].startswith('0|')}
207 | wp_users_obj = WikipediaUser(self.language)
208 | editor_names_dict = wp_users_obj.get_editor_names(editor_ids)
209 |
210 | # extend revisions data
211 | # {rev_id: [timestamp, parent_id, class_name/editor, editor_name]}
212 | for rev_id, rev_data in revisions.items():
213 | rev_data.append(editor_names_dict.get(rev_data[2], rev_data[2]))
214 | if rev_data[2].startswith('0|'):
215 | rev_data[2] = hashlib.md5(rev_data[2].encode('utf-8')).hexdigest()
216 |
217 | return editor_names_dict
218 |
219 | def get_tokens_data(self, revisions, editor_names_dict):
220 | data = self._prepare_request()
221 | response = self._make_request(data)
222 | _, rev_data = response['revisions'][0].popitem()
223 | tokens = rev_data['tokens']
224 |
225 | # set editor and class names and calculate conflict score for each token
226 | # if registered user, class name is editor id
227 | biggest_conflict_score = 0
228 | for token in tokens:
229 | # set editor name
230 | token['editor_name'] = editor_names_dict.get(token['editor'], token['editor'])
231 | # set html class name
232 | if token['editor'].startswith('0|'):
233 | token['class_name'] = hashlib.md5(token['editor'].encode('utf-8')).hexdigest()
234 | else:
235 | token['class_name'] = token['editor']
236 | # calculate age
237 | o_rev_ts = parser.parse(revisions[token['o_rev_id']][0])
238 | age = datetime.now(o_rev_ts.tzinfo) - o_rev_ts
239 | token['age'] = age.total_seconds()
240 | # calculate conflict score
241 | editor_in_prev = None
242 | conflict_score = 0
243 | for i, out_ in enumerate(token['out']):
244 | editor_out = revisions[out_][2]
245 | if editor_in_prev is not None and editor_in_prev != editor_out:
246 | # exclude first deletions and self reverts (undo actions)
247 | conflict_score += 1
248 | try:
249 | in_ = token['in'][i]
250 | except IndexError:
251 | # no in for this out. end of loop.
252 | pass
253 | else:
254 | editor_in = revisions[in_][2]
255 | if editor_out != editor_in:
256 | # exclude self reverts (undo actions)
257 | conflict_score += 1
258 | editor_in_prev = editor_in
259 | token['conflict_score'] = conflict_score
260 | if conflict_score > biggest_conflict_score:
261 | biggest_conflict_score = conflict_score
262 |
263 | return tokens, biggest_conflict_score
264 |
265 | def convert_tokens_data(self, tokens):
266 | # convert into list. exclude unnecessary token data
267 | # [[conflict_score, str, o_rev_id, in, out, editor/class_name, age]]
268 | return [[token['conflict_score'], token['str'], token['o_rev_id'],
269 | token['in'], token['out'], token['class_name'], token['age']]
270 | for token in tokens]
271 |
272 | def get_revisions_and_tokens(self):
273 | """
274 | Returns all revisions data of the article and tokens of given article.
275 | If no rev id is given, tokens of latest revision is returned.
276 | """
277 | if self.page_id is None and self.page_title is None and self.rev_id is None:
278 | raise Exception('Please provide page id or page title or rev id.')
279 |
280 | revisions = self.get_revisions_data()
281 | editor_names_dict = self.get_editor_names(revisions)
282 | tokens, biggest_conflict_score = self.get_tokens_data(revisions, editor_names_dict)
283 | tokens = self.convert_tokens_data(tokens)
284 |
285 | return {'revisions': revisions,
286 | 'tokens': tokens,
287 | 'biggest_conflict_score': biggest_conflict_score}
288 |
--------------------------------------------------------------------------------
/WhoColor/parser.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 |
4 | :Authors:
5 | Felix Stadthaus,
6 | Kenan Erdogan
7 | """
8 | import re
9 | import io
10 | from .special_markups import SPECIAL_MARKUPS, REGEX_HELPER_PATTERN
11 |
12 |
13 | class WikiMarkupParser(object):
14 | def __init__(self, wiki_text, tokens): # , revisions):
15 | # Saves the full wikipedia markup and all WikiWho tokens
16 | self.wiki_text = wiki_text
17 | self.tokens = tokens
18 | self.tokens_len = len(tokens)
19 | # self.revisions = revisions
20 | self.token = None
21 | # self.next_special_elem = None
22 |
23 | # Saves the current positions
24 | self._token_index = 0
25 | self._wiki_text_pos = 0
26 |
27 | # Saves whether there is currently an open span tag
28 | self._open_span = False
29 | # Array that holds the starting positions of blocks (special elements) we already jumped into
30 | self._jumped_elems = set()
31 |
32 | # The return values of the parser (error can be an error description)
33 | self.extended_wiki_text = io.StringIO()
34 |
35 | self.error = False
36 | self.present_editors = dict() # {editor_id: [editor_name, class_name, count], }
37 | self.conflict_scores = list()
38 |
39 | def __set_token(self):
40 | self.token = None
41 | if self._token_index < self.tokens_len:
42 | self.token = self.tokens[self._token_index]
43 | self.conflict_scores.append(self.token['conflict_score'])
44 | token_pos = re.search(re.escape(self.token['str']), self.wiki_text[self._wiki_text_pos:], re.IGNORECASE)
45 | if token_pos is None:
46 | # token is not found. because most probably it contains some characters that has different length
47 | # in lower and upper case such as 'İstanbul'
48 | # get next token
49 | self._token_index += 1
50 | self.__set_token()
51 | return
52 | self.token['end'] = self._wiki_text_pos + token_pos.end()
53 | if self.token['editor'] in self.present_editors:
54 | self.present_editors[self.token['editor']][2] += 1
55 | else:
56 | self.present_editors[self.token['editor']] = [self.token['editor_name'],
57 | self.token['class_name'],
58 | 1] # token count
59 |
60 | def __get_first_regex(self, regex):
61 | # first_match = None
62 | # for match in regex.finditer(self.wiki_text):
63 | # # search every time from the beginning of text
64 | # if (first_match is None or first_match.start() > match.start()) and match.start() >= self._wiki_text_pos:
65 | # # if match is first and starts after wiki text pos
66 | # first_match = match
67 | # if first_match is not None:
68 | # print('__get_first_regex:', self._wiki_text_pos, self._jumped_elems, first_match.start(), first_match.group(), regex)
69 | # return {'str': first_match.group(), 'start': first_match.start()}
70 | # return None
71 | # NOTE this doesnt work because if regex contains positive look behind!
72 | match = regex.search(self.wiki_text[self._wiki_text_pos:])
73 | if match:
74 | return {
75 | 'str': match.group(),
76 | 'start': self._wiki_text_pos + match.start()
77 | }
78 | return None
79 |
80 | def __get_special_elem_end(self, special_elem):
81 | # Get end position of current special markup element
82 | end_pos_data = {}
83 | if special_elem.get('end_len') is not None and special_elem.get('end') is not None:
84 | # if special markup is single (has no end regex)
85 | end_pos_data['start'] = special_elem['end']
86 | end_pos_data['len'] = special_elem['end_len']
87 | end_pos_data['end'] = end_pos_data['start'] + end_pos_data['len']
88 | else:
89 | end_regex = self.__get_first_regex(special_elem['end_regex'])
90 | if end_regex is not None:
91 | end_pos_data['start'] = end_regex['start']
92 | end_pos_data['len'] = len(end_regex['str'])
93 | end_pos_data['end'] = end_pos_data['start'] + end_pos_data['len']
94 | return end_pos_data
95 |
96 | def __get_next_special_element(self):
97 | # if self.next_special_elem and self.next_special_elem['start'] > self._wiki_text_pos:
98 | # return self.next_special_elem
99 | # Get starting position of next special markup element
100 | next_ = {}
101 | for special_markup in SPECIAL_MARKUPS:
102 | found_markup = self.__get_first_regex(special_markup['start_regex'])
103 | if found_markup is not None and \
104 | (not next_ or next_['start'] > found_markup['start']) and \
105 | found_markup['start'] not in self._jumped_elems:
106 | next_ = special_markup
107 | next_['start'] = found_markup['start']
108 | next_['start_len'] = len(found_markup['str'])
109 | if next_['type'] == 'single':
110 | # to be used in __get_special_elem_end - because it has no end regex
111 | next_['end'] = next_['start']
112 | next_['end_len'] = next_['start_len']
113 | # self.next_special_elem = next_
114 | return next_
115 |
116 | def __add_spans(self, token, new_span=True):
117 | """
118 | If there is an opened span and new_span is True, close previous span and start new span (no_spans=False)
119 | If there is an opened span and new_span is False, close previous span (no_spans=True)
120 | If there is not an opened span and new_span is True, start a new span (no_spans=False)
121 | If there is not an opened span and new_span is do nothing (no_spans=True)
122 | """
123 | if self._open_span is True:
124 | self.extended_wiki_text.write('')
125 | self._open_span = False
126 | if new_span is True:
127 | self.extended_wiki_text.write(''.\
128 | format(token['class_name'], self._token_index))
129 | self._open_span = True
130 |
131 | def __parse_wiki_text(self, add_spans=True, special_elem=None, no_jump=False):
132 | """
133 | There are 3 possible calls of this method in this algorithm.
134 | 1) start of script and adding not special tokens with spans around into extended markup:
135 | add_spans is True, special_elem is None and no_jump is False: Start, add spans around tokens until reaching
136 | next special element. And jump into that element and process tokens inside that element.
137 | 2) Handling special markup elements:
138 | add_spans is False, special_elem is not None and no_jump is True: Add each token until end of current special
139 | element into extended wiki text without spans.
140 | add_spans is False, special_elem is not None and no_jump is False: Add each token until end of current special
141 | element into extended wiki text without spans. If there is special element inside current special element,
142 | jump into that element and process tokens inside that element
143 |
144 | :param add_spans: Flag to decide adding spans around tokens.
145 | :param special_elem: Current special element that parser is inside.
146 | :param no_jump: Flag to decide jumping into next special element.
147 | :return: True if parsing is successful.
148 | """
149 | # Get end position of current special markup
150 | special_elem_end = self.__get_special_elem_end(special_elem) if special_elem else False
151 | if no_jump is False:
152 | # Get starting position of next special markup element in wiki text
153 | next_special_elem = self.__get_next_special_element()
154 |
155 | while self._wiki_text_pos < (len(self.wiki_text) - 1):
156 | if self.token is None:
157 | # No token left to parse
158 | # Add everything that's left to the end of the extended wiki text
159 | self.extended_wiki_text.write(self.wiki_text[self._wiki_text_pos: len(self.wiki_text)])
160 | self._wiki_text_pos = len(self.wiki_text) # - 1
161 | return True
162 |
163 | # Don't jump anywhere if no_jump is set
164 | if no_jump is False and (not special_elem_end or self._wiki_text_pos < special_elem_end['start']):
165 | if next_special_elem and \
166 | (not special_elem_end or next_special_elem['start'] < special_elem_end['start']) and \
167 | next_special_elem['start'] < self.token['end']:
168 | # Special markup element was found before or reaching into token
169 | # Or token itself is a start of special markup
170 | self._jumped_elems.add(next_special_elem['start'])
171 |
172 | if add_spans:
173 | # if no_spans=False, this special markup will have one span around with editor of first token
174 | self.__add_spans(self.token, new_span=not next_special_elem['no_spans'])
175 |
176 | # NOTE: add_spans=False => no spans will added inside special markups
177 | self.__parse_wiki_text(add_spans=False,
178 | special_elem=next_special_elem,
179 | no_jump=next_special_elem['no_jump'])
180 | if special_elem:
181 | # _wiki_text_pos is updated, we have to update the end position of current special markup
182 | special_elem_end = self.__get_special_elem_end(special_elem)
183 | # Get starting position of next special markup element
184 | next_special_elem = self.__get_next_special_element()
185 | continue
186 |
187 | # Is it end of special element?
188 | if special_elem_end and special_elem_end['end'] < self.token['end']:
189 | # Special element has been matched before the token
190 | # => Set position to special element's end
191 | self.extended_wiki_text.write(self.wiki_text[self._wiki_text_pos:special_elem_end['end']])
192 | self._wiki_text_pos = special_elem_end['end']
193 | return True
194 |
195 | # Add sequence author tags around token
196 | if add_spans:
197 | self.__add_spans(self.token) # close and open span tag
198 |
199 | # add remaining token (and possible preceding chars) to resulting altered markup
200 | self.extended_wiki_text.write(self.wiki_text[self._wiki_text_pos:self.token['end']])
201 | self._wiki_text_pos = self.token['end']
202 |
203 | # Increase token index
204 | self._token_index += 1
205 | # Get new token
206 | self.__set_token()
207 |
208 | # Close opened tags
209 | if self._open_span:
210 | self.extended_wiki_text.write("")
211 | self._open_span = False
212 | return True
213 |
214 | def __parse(self):
215 | # Current WikiWho token
216 | self.__set_token()
217 | return self.__parse_wiki_text()
218 |
219 | def __set_present_editors(self):
220 | """
221 | Sort editors who owns tokens in given revision according to percentage of owned tokens in decreasing order.
222 | """
223 | self.present_editors = tuple(
224 | (editor_name, class_name, token_count*100.0/self.tokens_len)
225 | for editor_id, (editor_name, class_name, token_count) in
226 | sorted(self.present_editors.items(), key=lambda x: x[1][2], reverse=True)
227 | )
228 |
229 | def generate_extended_wiki_markup(self):
230 | """
231 | Parse wiki text and add spans around tokens if possible.
232 | Generate list of editors who are present in this article page including 3 information to be used in js code:
233 | editor name, class name and authorship scores.
234 | """
235 | # Add regex helper pattern into wiki text in order to keep newlines
236 | self.wiki_text = self.wiki_text.replace('\r\n', REGEX_HELPER_PATTERN).\
237 | replace('\n', REGEX_HELPER_PATTERN).\
238 | replace('\r', REGEX_HELPER_PATTERN)
239 |
240 | self.__parse()
241 | self.__set_present_editors()
242 |
243 | # Remove regex patterns
244 | self.wiki_text = self.wiki_text.replace(REGEX_HELPER_PATTERN, '\n')
245 | self.extended_wiki_text = self.extended_wiki_text.getvalue()
246 | self.extended_wiki_text = self.extended_wiki_text.replace(REGEX_HELPER_PATTERN, '\n')
247 |
--------------------------------------------------------------------------------
/userscript/whocolor.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name WhoColor Userscript
3 | // @namespace https://wikiwho-api.wmcloud.org/whocolor/v1.0.0-beta/
4 | // @version 1.1
5 | // @description Displays authorship information on wikipedia.
6 | // @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js
7 | // @require https://wikiwho-api.wmcloud.org/static/whocolor/scripts/moment-with-locales.js
8 | // @grant GM_addStyle
9 | // @grant GM_setValue
10 | // @grant GM_getValue
11 | // @grant GM_getResourceText
12 | // @grant GM_log
13 | // @include /^http(s?):\/\/(en|es|eu|de|tr).wikipedia.org\/(.+)/
14 | // @copyright 2015+, Felix Stadthaus
15 | // ==/UserScript==
16 |
17 | // The MIT License (MIT)
18 | //
19 | // Copyright (c) 2015 Felix Stadthaus
20 | //
21 | // Permission is hereby granted, free of charge, to any person obtaining a copy
22 | // of this software and associated documentation files (the "Software"), to deal
23 | // in the Software without restriction, including without limitation the rights
24 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25 | // copies of the Software, and to permit persons to whom the Software is
26 | // furnished to do so, subject to the following conditions:
27 | //
28 | // The above copyright notice and this permission notice shall be included in
29 | // all copies or substantial portions of the Software.
30 | //
31 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
37 | // THE SOFTWARE.
38 |
39 | // See here for some resource licenses:
40 | //
41 | // https://commons.wikimedia.org/w/index.php?title=File:Clear_icon.svg&oldid=149014810
42 | // https://commons.wikimedia.org/w/index.php?title=File:Article_icon.svg&oldid=148759157
43 | // https://commons.wikimedia.org/w/index.php?title=File:ArticleSearch.svg&oldid=150569436
44 | // https://commons.wikimedia.org/w/index.php?title=File:Speechbubbles_icon.svg&oldid=148758659
45 | // By MGalloway (WMF) (Own work) [CC BY-SA 3.0 (http://creativecommons.org/licenses/by-sa/3.0)], via Wikimedia Commons
46 | //
47 | // https://commons.wikimedia.org/w/index.php?title=File:UserAvatar.svg&oldid=150569452
48 | // By MGalloway (WMF) (Own work) [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons
49 | //
50 | // https://commons.wikimedia.org/w/index.php?title=File:Speechbubbles_icon_green.svg&oldid=135292327
51 | // By Chiefwei (Own work) [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons
52 | //
53 | // http://www.ajaxload.info/ <-- The origin of ajax-loader.gif (License: WTFPL, http://www.wtfpl.net/)
54 |
55 | // Pollyfill for Greasemonkey 4, taken from
56 | // https://github.com/greasemonkey/gm4-polyfill/blob/master/gm4-polyfill.js#L33
57 | if (typeof GM_addStyle == 'undefined') {
58 | this.GM_addStyle = (aCss) => {
59 | 'use strict';
60 | let head = document.getElementsByTagName('head')[0];
61 | if (head) {
62 | let style = document.createElement('style');
63 | style.setAttribute('type', 'text/css');
64 | style.textContent = aCss;
65 | head.appendChild(style);
66 | return style;
67 | }
68 | return null;
69 | };
70 | }
71 |
72 | Wikiwho = {
73 | /* A few configuration options */
74 | // Where to fetch Wikicolor data from
75 | wikicolorUrl: "https://wikiwho-api.wmcloud.org/",
76 | // wikicolorUrl: "https://www.wikiwho.net/",
77 | // wikicolorUrl: "http://127.0.0.1:8000/",
78 |
79 | // Color palette for highlighting of tokens (Kelly)
80 | tokenColors: [
81 | "#FFB300", "#803E75", "#FF6800", "#A6BDD7", "#C10020", "#CEA262", "#817066",
82 | // The following is not good for people with defective color vision
83 | "#007D34", "#F6768E", "#00538A", "#FF7A5C", "#53377A", "#FF8E00", "#B32851",
84 | "#F4C800", "#7F180D", "#93AA00", "#593315", "#F13A13", "#232C16"
85 | ],
86 |
87 | /* Other initial values */
88 | // Specifies whether the original, unaltered content is shown
89 | showingUnalteredContent: true,
90 | // True, when initialized has already been called to prevent double initialization
91 | initialized: false,
92 | // Array with colored authors (and the colors they are colored with)
93 | coloredAuthors: {},
94 | // Variable holding the timeout that deselects the currently selected author
95 | deselectTimeout: null,
96 | // Variable telling whether provenance or history or conflict or age view view is opened
97 | provenanceViewOpen: false,
98 | historyViewOpen: false,
99 | conflictViewOpen: false,
100 | ageViewOpen: false,
101 | ageLimitFrom: 0, // days
102 | ageLimitTo: 360, // days
103 | groupSize: 10,
104 |
105 | /* Methods */
106 |
107 | // Determines whether white or black are the more contrasting colors (via the YIQ color model)
108 | getContrastingColor: function(color){
109 | var red = parseInt(color.substr(1, 2), 16);
110 | var green = parseInt(color.substr(3, 2), 16);
111 | var blue = parseInt(color.substr(5, 2), 16);
112 | var yiq = (299*red + 587*green + 114*blue) / 1000;
113 | return (yiq >= 128) ? ['black', 'darkblue'] : ['white', 'lightblue'];
114 | },
115 |
116 | // Determines whether white or black are the more contrasting colors (via the YIQ color model)
117 | /*
118 | getContrastingColorRGB: function(red, green, blue){
119 | var yiq = (299*red + 587*green + 114*blue) / 1000;
120 | return (yiq >= 128) ? ['black', 'darkblue'] : ['white', 'lightblue'];
121 | },
122 | */
123 |
124 | // Creates basic HTML elements like menu bars or buttons etc.
125 | createHTMLElements: function() {
126 | // Holds the altered Wikimedia content HTML markup
127 | Wikiwho.newcontent = $("");
128 | Wikiwho.newcontent.css('background', 'url('+Wikiwho.wikicolorUrl+'static/whocolor/images/ajax-loader.gif) no-repeat center center').css('min-height', '32px');
129 |
130 | // Holds the original, unaltered Wikimedia content HTML markup
131 | Wikiwho.originalcontent = $("#mw-content-text");
132 |
133 | // Add the altered content to the page (but hide it)
134 | $(Wikiwho.originalcontent[0].attributes).each(function() { Wikiwho.newcontent.attr(this.nodeName, this.value); });
135 | Wikiwho.originalcontent.after(Wikiwho.newcontent);
136 | Wikiwho.newcontent.hide();
137 |
138 | // The button to switch into wikicolor mode
139 | Wikiwho.onoffbutton = $('WhoColor');
140 | $("#p-views ul").prepend(Wikiwho.onoffbutton);
141 | Wikiwho.onoffbutton.find("a").click(function() { Wikiwho.onoffbuttonclick(); });
142 |
143 | // The menu on the right (displaying authors)
144 | var elementTop = $('#content').offset().top + 1;
145 |
146 | Wikiwho.rightbar = $('').hide().prependTo($("div#content.mw-body"));
147 | $("div#content.mw-body").css("position", "relative");
148 | Wikiwho.rightbar.css("top", "5em");
149 | Wikiwho.rightbar.css("right", "calc(-15.5em - 3px)");
150 |
151 | Wikiwho.rightbarcontent = $('').appendTo(Wikiwho.rightbar);
152 |
153 | $(window).scroll(function(){
154 | if($(window).scrollTop() > elementTop){
155 | $('#wikiwhorightbar').css('top', '0px');
156 | } else {
157 | $('#wikiwhorightbar').css('top', (elementTop-$(window).scrollTop())+'px');
158 | }
159 | });
160 |
161 | $(window).scroll();
162 |
163 | // Editor list
164 | $('').appendTo(Wikiwho.rightbarcontent);
165 | $('').appendTo(Wikiwho.rightbarcontent);
166 | $('').appendTo(Wikiwho.rightbarcontent);
167 | $('').appendTo(Wikiwho.rightbarcontent);
168 | $('').appendTo(Wikiwho.rightbarcontent);
169 | // var today = new Date();
170 | // today.setMonth(today.getMonth() - Wikiwho.ageLimit);
171 | // $('').appendTo(Wikiwho.rightbarcontent);
172 | $(']'+
173 | ''+
174 | '
'+
175 | ''+
176 | '
'+
177 | ''+
178 | '
'+
179 | ''+
180 | '
').appendTo(Wikiwho.rightbarcontent).hide();
181 |
182 | // Provenance view open button click event
183 | $('#provenanceviewbutton').click(function() {
184 | if(!Wikiwho.provenanceViewOpen) {
185 | // if provenance view is closed, conflict or age view must be open
186 | if (Wikiwho.conflictViewOpen) {
187 | Wikiwho.closeConflictView();
188 | } else if (Wikiwho.ageViewOpen) {
189 | Wikiwho.closeAgeView();
190 | }
191 | }
192 | });
193 | // Conflict view open button click event
194 | $('#conflictviewbutton').click(function() {
195 | if(Wikiwho.conflictViewOpen) {
196 | Wikiwho.closeConflictView();
197 | } else {
198 | Wikiwho.openConflictView();
199 | }
200 | });
201 | // Age view open button click event
202 | $('#ageviewbutton').click(function() {
203 | if(Wikiwho.ageViewOpen) {
204 | Wikiwho.closeAgeView();
205 | } else {
206 | Wikiwho.openAgeView();
207 | }
208 | });
209 | $('#ageLimitButton').click(function(){
210 | Wikiwho.ageLimitFrom = $('#ageLimitFrom').val();
211 | Wikiwho.ageLimitTo = $('#ageLimitTo').val();
212 | Wikiwho.groupSize = $('#groupSize').val();
213 | Wikiwho.openAgeView();
214 | });
215 | $('#ageLimitFrom, #ageLimitTo, #groupSize').on("keypress", function(e){
216 | if (e.keyCode === 13) {
217 | Wikiwho.ageLimitFrom = $('#ageLimitFrom').val();
218 | Wikiwho.ageLimitTo = $('#ageLimitTo').val();
219 | Wikiwho.groupSize = $('#groupSize').val();
220 | Wikiwho.openAgeView();
221 | }
222 | });
223 |
224 | // The sequence history
225 | Wikiwho.seqHistBox = $('').hide().appendTo($("div#content.mw-body"));
226 | Wikiwho.seqHistBox.append('');
227 | Wikiwho.seqHistBox.append('Selected text part is too long for the WhoColor token history
');
228 | Wikiwho.seqHistBox.append('All the selected wiki markup was added in the currently viewed revision.
');
229 | Wikiwho.seqHistBox.append('
');
230 | Wikiwho.histview = $('').appendTo(Wikiwho.seqHistBox);
231 | },
232 |
233 | onoffbuttonclick: function() {
234 | Wikiwho.onoffbutton.toggleClass("selected");
235 | if(Wikiwho.showingUnalteredContent) {
236 | Wikiwho.showAlteredContent();
237 | }else{
238 | Wikiwho.showUnalteredContent();
239 | }
240 | },
241 |
242 | showAlteredContent: function() {
243 | // Don't do anything if already showing the altered content
244 | if(!Wikiwho.showingUnalteredContent) return true;
245 |
246 | // The actual content replacement (just visual for the sake of speed)
247 | Wikiwho.originalcontent.attr("id", "old-mw-content-text");
248 | Wikiwho.newcontent.attr("id", "mw-content-text");
249 | Wikiwho.originalcontent.fadeOut(250, function() { Wikiwho.originalcontent.hide(); Wikiwho.newcontent.fadeIn(250, function() { Wikiwho.newcontent.show(); }); });
250 | //Wikiwho.newcontent.show();
251 | //Wikiwho.originalcontent.attr("id", "old-mw-content-text").hide();
252 | //Wikiwho.newcontent.attr("id", "mw-content-text");
253 |
254 | // Squeeze main content a bit and show the bar to the right
255 | //$("div#content").css("margin-right", "251px");
256 | Wikiwho.rightbar.animate({"right": "0px"}, 500, function() {});
257 | $("div#content").animate({"margin-right": "15.5em"}, 500, function() {});
258 | Wikiwho.rightbar.show();
259 |
260 | // Change flag
261 | Wikiwho.showingUnalteredContent = false;
262 | $('#provenanceviewbutton').addClass('provenanceviewbuttonopen');
263 | $('#conflictviewbutton').removeClass('conflictviewbuttonopen');
264 | $('#ageviewbutton').removeClass('ageviewbuttonopen');
265 | },
266 |
267 | showUnalteredContent: function() {
268 | // Don't do anything if already showing the unaltered content
269 | if(Wikiwho.showingUnalteredContent) return true;
270 |
271 | // The actual content replacement (just visual for the sake of speed)
272 | //Wikiwho.originalcontent.show();
273 | Wikiwho.newcontent.attr("id", "new-mw-content-text");
274 | Wikiwho.originalcontent.attr("id", "mw-content-text");
275 | Wikiwho.newcontent.fadeOut(250, function() { Wikiwho.newcontent.hide(); Wikiwho.originalcontent.fadeIn(250, function() { Wikiwho.originalcontent.show(); }); });
276 |
277 | // Unsqueeze main content and hide the bar to the right
278 | //$("div#content").css("margin-right", "");
279 | //$("div#content").animate({"margin-right": ""}, 500, function() { Wikiwho.rightbar.hide(); });
280 | Wikiwho.rightbar.animate({"right": "-15.6em"}, 500, function() { Wikiwho.rightbar.hide(); });
281 | $("div#content").animate({"margin-right": ""}, 500, function() {});
282 | //Wikiwho.rightbar.hide();
283 |
284 | // Change flag
285 | Wikiwho.showingUnalteredContent = true;
286 | },
287 |
288 | // Restore the original Mediawiki content
289 | restoreMWContent: function() {
290 | Wikiwho.originalcontent.show();
291 | Wikiwho.newcontent.hide();
292 | },
293 |
294 | // get Wikiwho author data via ajax
295 | getWikiwhoData: function() {
296 | if(!Wikiwho.tries) Wikiwho.tries = 0;
297 | Wikiwho.tries++;
298 |
299 | if(Wikiwho.tries > 3) {
300 | // Failed 3 times, stop trying
301 | // Check error and info messages
302 | if(typeof Wikiwho.api_info !== "undefined") {
303 | alert(Wikiwho.api_info);
304 | } else if (typeof Wikiwho.api_error !== "undefined") {
305 | alert(Wikiwho.api_error);
306 | } else {
307 | alert("Failed to retrieve valid WikiWho data.");
308 | }
309 | return;
310 | }
311 |
312 | var queryDict = {};
313 | location.search.substr(1).split("&").forEach(function(item) {queryDict[item.split("=")[0]] = item.split("=")[1]});
314 |
315 | // "title": $("h1#firstHeading").text()
316 | var wiki_lang = location.hostname.split('.')[0];
317 | var ajax_url = Wikiwho.wikicolorUrl + wiki_lang + "/whocolor/v1.0.0-beta/" + encodeURIComponent($("h1#firstHeading").text().trim()) + "/";
318 | if(queryDict["oldid"]) {
319 | ajax_url = ajax_url + queryDict["oldid"] + "/";
320 | }
321 | jQuery.ajax({
322 | url: ajax_url,
323 | method: 'GET',
324 | // data: data,
325 | dataType: "json",
326 | success: Wikiwho.wikiwhoDataCallback,
327 | error: function() {
328 | // Request failed, try again
329 | setTimeout(Wikiwho.getWikiwhoData, 5000); // 5 seconds
330 | return;
331 | }
332 | });
333 | },
334 |
335 | wikiwhoDataCallback: function(data) {
336 | Wikiwho.api_success = data.success;
337 | Wikiwho.api_info = data.info;
338 | Wikiwho.api_error = data.error;
339 | // Retry when no success
340 | if(Wikiwho.api_success !== true) {
341 | setTimeout(Wikiwho.getWikiwhoData, 5000);
342 | return;
343 | }
344 |
345 | // Add extended markup content
346 | Wikiwho.newcontent.append(data.extended_html);
347 |
348 | // Remove loading indicator
349 | Wikiwho.newcontent.css('background', '').css('min-height', '');
350 |
351 | // Save author and revision data
352 | Wikiwho.present_editors = data.present_editors; // [[editor_name, editor/class_name, editor_score]]
353 | Wikiwho.tokens = data.tokens; // [[conflict_score, str, o_rev_id, in, out, editor/class_name, age]]
354 | Wikiwho.tokencount = Wikiwho.tokens.length;
355 | Wikiwho.revisions = data.revisions; // {rev_id: [timestamp, parent_id, class_name/editor, editor_name]}
356 | Wikiwho.biggest_conflict_score = parseInt(data.biggest_conflict_score);
357 | Wikiwho.rev_id = parseInt(data.rev_id);
358 |
359 | // Fill right panel with data
360 | Wikiwho.fillRightPanel();
361 |
362 | // Add token events for WikiWho markup
363 | Wikiwho.addTokenEvents();
364 |
365 | // Add history view events
366 | // Add code handling text selections of Wikitext
367 | Wikiwho.addSelectionEvents();
368 | // Add code to make the history view work
369 | Wikiwho.addHistoryEvents();
370 |
371 | // Handle image selection outlines
372 | $('span.editor-token').has("img").addClass("editor-token-image");
373 |
374 | // Debug output (color tokens in alternating colors)
375 | // $(".editor-token").filter(":odd").css("background-color", "green");
376 | // $(".editor-token").filter(":even").css("background-color", "yellow");
377 | },
378 |
379 | addHistoryEvents: function() {
380 | // Open history view when open indicator is clicked
381 | Wikiwho.seqHistBox.click(function() {
382 | // Check whether open indicator was clicked
383 | if($(this).hasClass("indicator") && (!$(this).hasClass("indicatortoolong")) && (!$(this).hasClass("indicatoronerev"))) {
384 | // Calculate height of marked text part
385 | var selectionHeight = Wikiwho.seqEndToken.offset().top + Wikiwho.seqEndToken.outerHeight(false) - Wikiwho.seqStartToken.offset().top;
386 |
387 | // Calculate optimal history view height
388 | var maxviewheight = $(window).height() - (selectionHeight + 20);
389 |
390 | // Check whether selected text is too long
391 | if((maxviewheight < $(window).height()/5) || (maxviewheight < 150)) {
392 | // OK, that's too much at once :(
393 | Wikiwho.seqHistBox.addClass("indicatortoolong");
394 | return;
395 | }
396 |
397 | // Prepare open animation
398 | Wikiwho.histview.css("height", "");
399 | Wikiwho.seqHistBox.css("max-height", Wikiwho.seqHistBox.height()+"px");
400 | Wikiwho.seqHistBox.removeClass("indicator");
401 | Wikiwho.seqHistBox.animate({"max-height": maxviewheight+"px"}, 500, function() {
402 | Wikiwho.seqHistBox.css("max-height", "calc(100% - "+(selectionHeight + 20)+"px)");
403 |
404 | // Fix sizes
405 | $(window).resize();
406 | });
407 |
408 | // Mark history view as open
409 | Wikiwho.historyViewOpen = true;
410 |
411 | // Reset some variables
412 | Wikiwho.hvhiddenTokens = new Array();
413 | Wikiwho.hvhiddenRevisions = new Array();
414 | Wikiwho.hvhiddenTokenDummies = new Array();
415 |
416 | // Remove body scrollbars
417 | $("body").css("overflow", "hidden");
418 |
419 | // Scroll body to text passage
420 | $('html, body').animate({
421 | scrollTop: Wikiwho.seqStartToken.offset().top - 10
422 | }, 500);
423 |
424 | // Get start and end ID
425 | var startTokenId = parseInt(Wikiwho.seqStartToken.attr("id").slice(6));
426 | Wikiwho.startTokenId = startTokenId;
427 | var endTokenId = parseInt(Wikiwho.seqEndToken.attr("id").slice(6));
428 | if(Wikiwho.selectionEndTokenId) {
429 | endTokenId = parseInt(Wikiwho.selectionEndTokenId) - 1;
430 | }
431 | Wikiwho.endTokenId = endTokenId;
432 |
433 | // Clear and reset history view
434 | Wikiwho.histview.empty();
435 | var leftbox = $('').appendTo(Wikiwho.histview);
436 | var middlebox = $('').appendTo(Wikiwho.histview);
437 | var rightbox = $('').appendTo(Wikiwho.histview);
438 |
439 | // Populate history view
440 | var revisionsById = {};
441 | var revisionArr = new Array();
442 | revisionArr.push(Wikiwho.rev_id);
443 | revisionsById[Wikiwho.rev_id] = moment.utc(Wikiwho.revisions[Wikiwho.rev_id][0]); // timestamp
444 | var compiledTokens = new Array();
445 |
446 | for (i = startTokenId; i <= endTokenId; i++) {
447 | var token_data = Wikiwho.tokens[i];
448 | var tokenrevlist = new Array();
449 |
450 | // origin rev id
451 | var revid = parseInt(token_data[2]);
452 | if(!(revid in revisionsById)) {
453 | revisionArr.push(revid);
454 | revisionsById[revid] = moment.utc(Wikiwho.revisions[revid][0]);
455 | }
456 | tokenrevlist.push(revid);
457 |
458 | // re-inserts (ins)
459 | token_data[3].forEach(function(entry) {
460 | var revid = parseInt(entry);
461 | if(!(revid in revisionsById)) {
462 | revisionArr.push(revid);
463 | revisionsById[revid] = moment.utc(Wikiwho.revisions[revid][0]);
464 | }
465 | tokenrevlist.push(revid);
466 | });
467 |
468 | // deletions (outs)
469 | token_data[4].forEach(function(entry) {
470 | var revid = parseInt(entry);
471 | if(!(revid in revisionsById)) {
472 | revisionArr.push(revid);
473 | revisionsById[revid] = moment.utc(Wikiwho.revisions[revid][0]);
474 | }
475 | tokenrevlist.push(revid);
476 | });
477 |
478 | tokenrevlist.sort(function(a, b){
479 | var aD = revisionsById[a];
480 | var bD = revisionsById[b];
481 | return aD>bD ? -1 : aDbD ? -1 : aD').appendTo(leftbox);
495 |
496 | // Show diff links
497 | var article_title = encodeURIComponent($("h1#firstHeading").text().trim());
498 | if(i !== 0) {
499 | revinfoline.append(
500 | $('')
510 | );
511 | }
512 | var updownarrow = $('');
513 | $('').html("↕").appendTo(updownarrow);
522 |
523 | // Append date and time
524 | revinfoline.append($('').text(revisionsById[revisionArr[i]].format('YYYY-MM-DD')));
525 |
526 | // Append spacer
527 | revinfoline.append($(''));
528 |
529 | // Append author
530 | var author_name = Wikiwho.revisions[revisionArr[i]][3].replace(/^0\|/, '');
531 | revinfoline.append($('').text(author_name).addClass("hvauthorid-"+Wikiwho.revisions[revisionArr[i]][2]).append($('
')));
532 |
533 | // Append distance to next revision in list
534 | if(i !== revisionArr.length - 1) {
535 | var datetimediff = $('');
536 | revinfoline.append(datetimediff);
537 | datetimediff.append($('').text(revisionsById[revisionArr[i+1]].from(revisionsById[revisionArr[i]], true)));
538 | datetimediff.append(updownarrow);
539 |
540 | // Calculate distance in revisions
541 | // TODO: Make this more efficient (maybe restructure data sent from API?)
542 | var counter = 0;
543 | var iterrevid = revisionArr[i];
544 | var targetid = revisionArr[i+1];
545 | while(iterrevid !== targetid) {
546 | counter++;
547 | iterrevid = Wikiwho.revisions[iterrevid][1]; // parent_id
548 | }
549 | datetimediff.append($('').text(counter + (counter===1 ? " revision" : " revisions")));
550 | }
551 | }
552 |
553 | var tokenheaders = $('').appendTo(middlebox);
554 | var tokenbodies = $('').appendTo(middlebox);
555 |
556 | for (i = startTokenId; i <= endTokenId; i++) {
557 | token_data = Wikiwho.tokens[i];
558 | var htmltoken = $('').text(token_data[1]);
559 | htmltoken.addClass("editor-token token-editor-"+token_data[5]);
560 | htmltoken.addClass("editor-token-"+i);
561 | tokenheaders.append($('').append(htmltoken));
562 | var tokencol = $('').appendTo(tokenbodies);
563 | var tokenwidth = htmltoken.parent().outerWidth(true);
564 | var tokenrevindex = 0;
565 | var tokeninarticle = true;
566 | for (var revindex = 0; revindex < revisionArr.length - 1; revindex++) {
567 | if(revisionArr[revindex] === compiledTokens[i-startTokenId][tokenrevindex]) {
568 | tokeninarticle = !tokeninarticle;
569 | tokenrevindex++;
570 | }
571 | tokencol.append($(''));
572 | }
573 | }
574 |
575 | // Fix scrolling
576 | tokenheaders.bind('wheel', function(e){
577 | var scrollTo = e.originalEvent.deltaX + tokenbodies.scrollLeft();
578 | tokenbodies.scrollLeft(scrollTo);
579 | });
580 | leftbox.bind('wheel', function(e){
581 | var scrollTo = e.originalEvent.deltaY + tokenbodies.scrollTop();
582 | tokenbodies.scrollTop(scrollTo);
583 | });
584 | tokenbodies.scroll(function() {
585 | tokenheaders.scrollLeft($(this).scrollLeft());
586 | leftbox.scrollTop($(this).scrollTop());
587 | });
588 | tokenheaders.append($(''));
589 |
590 | // Add resizing events
591 | $(window).resize(function() {
592 | if(leftbox.get(0).scrollHeight >= Wikiwho.seqHistBox.height()) {
593 | Wikiwho.histview.css("height", "calc("+($(window).height() - (selectionHeight + 20))+"px - 2.75em - 1px)");
594 | }else{
595 | Wikiwho.histview.css("height", "");
596 | }
597 | });
598 |
599 | // Check for special case (only one revision in list)
600 | if(revisionArr.length === 1) {
601 | Wikiwho.seqHistBox.addClass("indicator indicatoronerev");
602 |
603 | // Remove resizing events
604 | $(window).off("resize");
605 |
606 | // Mark history view as closed
607 | Wikiwho.historyViewOpen = false;
608 |
609 | // Restore body scrollbars
610 | $("body").css("overflow", "");
611 | }
612 |
613 | // Add author click events
614 | leftbox.find(".hvrevauthor").click(function() {
615 | var editor = $(this).attr('class').match(/hvauthorid-([a-f0-9]+)/)[1];
616 | $("li#editor-"+editor).click();
617 | return false;
618 | });
619 |
620 | // Add author hover events
621 | leftbox.find(".hvrevauthor").hover(function(event) {
622 | // Mousein event handler
623 | var editor = $(this).attr('class').match(/hvauthorid-([a-f0-9]+)/)[1];
624 |
625 | // Call the general hover handler
626 | Wikiwho.hoverToken(editor);
627 | }, function(event) {
628 | // Mouseout event handler
629 | Wikiwho.deselectTimeout = setTimeout(function(){
630 | // Remove all selection markers
631 | $(".editor-token").removeClass("selected hvselected");
632 | $(".hvrevauthor").removeClass("selected");
633 | $("#wikiwhorightbar li").removeClass("selected");
634 | }, 500);
635 | });
636 |
637 | // Color tokens
638 | Object.keys(Wikiwho.coloredAuthors).forEach(function(editor) {
639 | var color = Wikiwho.coloredAuthors[editor];
640 | var contrastColor = Wikiwho.getContrastingColor(color);
641 | $("span.token-editor-"+editor).css({"background-color": color, "color": contrastColor[0]}).find("*").css("color", contrastColor[1]);
642 | $("div.hvauthorid-"+editor).css({"background-color": color, "color": contrastColor[0]});
643 | });
644 |
645 | // Color tokens differently if conflict view open
646 | if(Wikiwho.conflictViewOpen) {
647 | Wikiwho.openConflictView();
648 | }
649 |
650 | // Add hover events
651 | $('div.hvtokenheaders span.editor-token').hover(function(event) {
652 | // Mousein event handler
653 | var editor = $(this).attr('class').match(/token-editor-([a-f0-9]+)/)[1];
654 | var tokenid = $(this).attr('id').slice(10);
655 |
656 | // Call the general hover handler
657 | Wikiwho.hoverToken(editor);
658 |
659 | // Select token with red outline
660 | // TODO is this correct?
661 | // $("#token-age-"+tokenid).removeClass("selected").addClass("hvselected");
662 | $(this).removeClass("selected").addClass("hvselected");
663 | }, function(event) {
664 | // Mouseout event handler
665 | Wikiwho.deselectTimeout = setTimeout(function(){
666 | // Remove all selection markers
667 | $(".author-token").removeClass("selected hvselected");
668 | $(".hvrevauthor").removeClass("selected");
669 | $("#wikiwhorightbar li").removeClass("selected");
670 | }, 500);
671 | });
672 |
673 | // Add click events
674 | $('div.hvtokenheaders span.editor-token').click(function() {
675 | var editor = $(this).attr('class').match(/token-editor-([a-f0-9]+)/)[1];
676 | $("#editor-"+editor).click();
677 | return false;
678 | });
679 |
680 | // Add hide events (rightclick)
681 | $('div.hvtokenheaders span.editor-token').bind("contextmenu",function(e){
682 | var editor = $(this).attr('class').match(/token-editor-([a-f0-9]+)/)[1];
683 | var tokenid = $(this).attr('id').slice(10);
684 |
685 | Wikiwho.hvHideToken(parseInt(tokenid), parseInt(editor), revisionArr, compiledTokens, revisionsById, true);
686 |
687 | return false;
688 | });
689 |
690 | // Open history view
691 | Wikiwho.seqHistBox.animate({"max-height": maxviewheight+"px"}, 500, function() {
692 | Wikiwho.seqHistBox.css("max-height", "calc(100% - "+(selectionHeight + 20)+"px)");
693 |
694 | // Fix sizes
695 | // $(window).resize();
696 | });
697 | }
698 | });
699 |
700 | // Close history box when close icon is clicked
701 | $("img.hvcloseicon").click(function() {
702 | // Remove resizing events
703 | $(window).off("resize");
704 |
705 | // Close animation
706 | Wikiwho.seqHistBox.css("max-height", Wikiwho.seqHistBox.outerHeight(false) + "px");
707 | Wikiwho.seqHistBox.animate({"max-height": "0px"}, 500, function() {
708 | Wikiwho.seqHistBox.hide();
709 | Wikiwho.seqHistBox.css("max-height", "");
710 | // Restore body scrollbars
711 | $("body").css("overflow", "");
712 | });
713 |
714 | // Mark history view as closed
715 | Wikiwho.historyViewOpen = false;
716 | });
717 | },
718 |
719 | hvHideToken: function(tokenid, authorid, revisionArr, compiledTokens, revisionsById, animation) {
720 | Wikiwho.hvhiddenTokens.push(tokenid);
721 |
722 | // Search for already matching token dummies
723 | var foundDummies = new Array();
724 | for(i = 0; i < Wikiwho.hvhiddenTokenDummies.length; i++) {
725 | var dummy = Wikiwho.hvhiddenTokenDummies[i];
726 |
727 | // Check for removed dummies
728 | if(dummy === undefined) continue;
729 |
730 | if ((dummy["start"] === tokenid + 1) || (dummy["end"] === tokenid - 1)) {
731 | foundDummies.push(dummy);
732 | }
733 | }
734 |
735 | // Check how many matching dummies were found
736 | if(foundDummies.length === 0) {
737 | // No dummy matching, add one
738 | var newid = Wikiwho.hvhiddenTokenDummies.length;
739 | var htmltoken = $('').text("[...]");
740 | var dummyobj = $('').append(htmltoken).insertAfter($('div.hvtokenheaders span.editor-tokenid-'+tokenid).parent());
741 | var tokencol = $('').insertAfter($('div.hvtokenbodies div.hvtokencol-'+tokenid));
742 | var tokenwidth = htmltoken.parent().outerWidth(true);
743 | for (var revindex = 0; revindex < revisionArr.length - 1; revindex++) {
744 | tokencol.append($(''));
745 | }
746 |
747 | dummy = {
748 | 'start': tokenid,
749 | 'end': tokenid,
750 | 'object': dummyobj,
751 | 'colobj': tokencol,
752 | 'id': newid
753 | };
754 |
755 | dummyobj.click(function() {
756 | Wikiwho.hvRestoreDummy(dummy, revisionArr, compiledTokens, revisionsById, true);
757 | });
758 |
759 | Wikiwho.hvhiddenTokenDummies[newid] = dummy;
760 |
761 | // Animation
762 | var dummywidth = dummyobj.width();
763 | var tokencolwidth = tokencol.width();
764 | if(animation) {
765 | dummyobj.css('width', '0px');
766 | tokencol.css('width', '0px');
767 | dummyobj.animate({'width': dummywidth+'px'}, 500, function() {
768 | $(this).css("width", "");
769 | });
770 | tokencol.animate({'width': tokencolwidth+'px'}, 500, function() {
771 | $(this).css("width", "");
772 | });
773 | }
774 | }else if(foundDummies.length === 1) {
775 | // One dummy matching, add to dummy
776 | dummy = foundDummies[0];
777 | var dummyid = dummy['id']
778 | if(dummy['start'] === tokenid + 1) {
779 | dummy['start']--;
780 | }else{
781 | dummy['end']++;
782 | }
783 |
784 | Wikiwho.hvhiddenTokenDummies[dummyid] = dummy;
785 | }else{
786 | if(animation) {
787 | foundDummies[1]['object'].animate({'width': '0px'}, 500, function() {
788 | $(this).remove();
789 | });
790 | foundDummies[1]['colobj'].animate({'width': '0px'}, 500, function() {
791 | $(this).remove();
792 | });
793 | }else{
794 | foundDummies[1]['object'].remove();
795 | foundDummies[1]['colobj'].remove();
796 | }
797 | if(foundDummies[0]['start'] > foundDummies[1]['start']) foundDummies[0]['start'] = foundDummies[1]['start'];
798 | if(foundDummies[0]['end'] < foundDummies[1]['end']) foundDummies[0]['end'] = foundDummies[1]['end'];
799 | Wikiwho.hvhiddenTokenDummies[foundDummies[1]['id']] = undefined;
800 | }
801 |
802 | // Actual hiding of token column
803 | if(animation) {
804 | $('.hvtokenhead-'+tokenid).animate({'width': '0px'}, 500, function() {
805 | $(this).hide();
806 | $(this).css("width", "");
807 | });
808 | $('.hvtokencol-'+tokenid).animate({'width': '0px'}, 500, function() {
809 | $(this).hide();
810 | $(this).css("width", "");
811 | });
812 | }else{
813 | $('.hvtokenhead-'+tokenid).hide();
814 | $('.hvtokencol-'+tokenid).hide();
815 | }
816 |
817 | // Rehide rows that should already be hidden (in case a new dummy was added)
818 | for(i = 0; i < Wikiwho.hvhiddenRevisions.length; i++) {
819 | $('.hvrevhead-'+Wikiwho.hvhiddenRevisions[i]).hide();
820 | $('.hvtokencolpiece-'+Wikiwho.hvhiddenRevisions[i]).hide();
821 | }
822 |
823 | // Check whether we can hide rows as well
824 | var newRevisionArray = new Array();
825 | for(var i = 0; i < compiledTokens.length; i++) {
826 | // Skip token if hidden
827 | if(Wikiwho.hvhiddenTokens.indexOf(Wikiwho.startTokenId + i) !== -1) {
828 | continue;
829 | }
830 | // Add revisions of this token to array if not already in there
831 | for(var i2 = 0; i2 < compiledTokens[i].length; i2++) {
832 | if(newRevisionArray.indexOf(compiledTokens[i][i2]) === -1) {
833 | newRevisionArray.push(compiledTokens[i][i2]);
834 | }
835 | }
836 | }
837 | // Go through real revision array and hide all revisions that are not in the new revision array and not already hidden
838 | for(i = 1; i < revisionArr.length; i++) {
839 | if(newRevisionArray.indexOf(revisionArr[i]) === -1) {
840 | // Revision not in new revision array
841 | if(Wikiwho.hvhiddenRevisions.indexOf(revisionArr[i]) === -1) {
842 | // Not hidden yet, hide
843 | if(animation) {
844 | $('.hvrevhead-'+revisionArr[i]).animate({'height': '0px'}, 500, function() {
845 | $(this).hide();
846 | $(this).css("height", "");
847 | });
848 | $('.hvtokencolpiece-'+revisionArr[i]).animate({'height': '0px'}, 500, function() {
849 | $(this).hide();
850 | $(this).css("height", "");
851 | });
852 | }else{
853 | $('.hvrevhead-'+revisionArr[i]).hide();
854 | $('.hvtokencolpiece-'+revisionArr[i]).hide();
855 | }
856 |
857 | // Get index of previous shown revision
858 | var previousRevIndex = i - 1;
859 | while(Wikiwho.hvhiddenRevisions.indexOf(revisionArr[previousRevIndex]) !== -1) {
860 | previousRevIndex--;
861 | }
862 |
863 | // Get index of next shown revision
864 | var nextRevIndex = i + 1;
865 | while((nextRevIndex < revisionArr.length) && (Wikiwho.hvhiddenRevisions.indexOf(revisionArr[nextRevIndex]) !== -1)) {
866 | nextRevIndex++;
867 | }
868 | if(nextRevIndex === revisionArr.length) {
869 | // Shouldn't show a diff
870 | // TODO
871 | }else{
872 | // Calculate and update new date diff data of previous shown revision
873 | $('.hvrevhead-'+revisionArr[previousRevIndex]+' .hvlefttimediff').text(revisionsById[revisionArr[nextRevIndex]].from(revisionsById[revisionArr[previousRevIndex]], true));
874 |
875 | // Calculate distance in revisions
876 | // TODO: Make this more efficient (maybe restructure data sent from API?)
877 | var counter = 0;
878 | var iterrevid = revisionArr[previousRevIndex];
879 | var targetid = revisionArr[nextRevIndex];
880 | while(iterrevid !== targetid) {
881 | counter++;
882 | iterrevid = Wikiwho.revisions[iterrevid][1]; // parent_id
883 | }
884 |
885 | // Update distance in revisions
886 | $('.hvrevhead-'+revisionArr[previousRevIndex]+' .hvrighttimediff').text(counter + (counter===1 ? " revision" : " revisions"));
887 | }
888 |
889 | // Add to hvhiddenRevisions array
890 | Wikiwho.hvhiddenRevisions.push(revisionArr[i]);
891 | }
892 | }
893 | }
894 | },
895 |
896 | hvRestoreDummy: function(dummy, revisionArr, compiledTokens, revisionsById, animation) {
897 | // Remove dummy objects
898 | dummy['object'].remove();
899 | dummy['colobj'].remove();
900 |
901 | // Show token columns again
902 | for(i = dummy["start"]; i <= dummy["end"]; i++) {
903 | // Actual showing of token column
904 | var headwidth = $('.hvtokenhead-'+i).width();
905 | var colwidth = $('.hvtokencol-'+i).width();
906 | if(animation) {
907 | $('.hvtokenhead-'+i).css('width', '0px');
908 | $('.hvtokencol-'+i).css('width', '0px');
909 | }
910 | $('.hvtokenhead-'+i).show();
911 | $('.hvtokencol-'+i).show();
912 | if(animation) {
913 | $('.hvtokenhead-'+i).animate({'width': headwidth+'px'}, 500, function() {
914 | $(this).css("width", "");
915 | });
916 | $('.hvtokencol-'+i).animate({'width': colwidth+'px'}, 500, function() {
917 | $(this).css("width", "");
918 | });
919 | }
920 |
921 | // Remove tokens from hidden tokens array
922 | Wikiwho.hvhiddenTokens.splice(Wikiwho.hvhiddenTokens.indexOf(i), 1);
923 | }
924 |
925 | // Remove dummy from array
926 | Wikiwho.hvhiddenTokenDummies[dummy['id']] = undefined;
927 |
928 | // Check whether we can show rows as well
929 | var newRevisionArray = new Array();
930 | for(i = 0; i < compiledTokens.length; i++) {
931 | // Skip token if hidden
932 | if(Wikiwho.hvhiddenTokens.indexOf(Wikiwho.startTokenId + i) !== -1) {
933 | continue;
934 | }
935 | // Add revisions of this token to array if not already in there
936 | for(i2 = 0; i2 < compiledTokens[i].length; i2++) {
937 | if(newRevisionArray.indexOf(compiledTokens[i][i2]) === -1) {
938 | newRevisionArray.push(compiledTokens[i][i2]);
939 | }
940 | }
941 | }
942 | // Go through real revision array and show all revisions that are in the new revision array and hidden
943 | for(i = 1; i < revisionArr.length; i++) {
944 | if(newRevisionArray.indexOf(revisionArr[i]) !== -1) {
945 | // Revision in new revision array
946 | if(Wikiwho.hvhiddenRevisions.indexOf(revisionArr[i]) !== -1) {
947 | // Is hidden => show
948 | if(animation) {
949 | $('.hvrevhead-'+revisionArr[i]).show().animate({'height': '4.5em'}, 500, function() {
950 | $(this).css("height", "");
951 | });
952 | $('.hvtokencolpiece-'+revisionArr[i]).show().animate({'height': '4.5em'}, 500, function() {
953 | $(this).css("height", "");
954 | });
955 | }else{
956 | $('.hvrevhead-'+revisionArr[i]).show();
957 | $('.hvtokencolpiece-'+revisionArr[i]).show();
958 | }
959 |
960 | // Get index of previous shown revision
961 | var previousRevIndex = i - 1;
962 | while(Wikiwho.hvhiddenRevisions.indexOf(revisionArr[previousRevIndex]) !== -1) {
963 | previousRevIndex--;
964 | }
965 |
966 | // Get index of next shown revision
967 | var nextRevIndex = i + 1;
968 | while((nextRevIndex < revisionArr.length) && (Wikiwho.hvhiddenRevisions.indexOf(revisionArr[nextRevIndex]) !== -1)) {
969 | nextRevIndex++;
970 | }
971 |
972 | // Correct diff of previous revision
973 | // Calculate and update new date diff data of previous shown revision
974 | $('.hvrevhead-'+revisionArr[previousRevIndex]+' .hvlefttimediff').text(revisionsById[revisionArr[i]].from(revisionsById[revisionArr[previousRevIndex]], true));
975 |
976 | // Calculate distance in revisions
977 | // TODO: Make this more efficient (maybe restructure data sent from API?)
978 | var counter = 0;
979 | var iterrevid = revisionArr[previousRevIndex];
980 | var targetid = revisionArr[i];
981 | while(iterrevid !== targetid) {
982 | counter++;
983 | iterrevid = Wikiwho.revisions[iterrevid][1]; // parent_id
984 | }
985 |
986 | // Update distance in revisions
987 | $('.hvrevhead-'+revisionArr[previousRevIndex]+' .hvrighttimediff').text(counter + (counter===1 ? " revision" : " revisions"));
988 |
989 | // Correct diff of this revision
990 | if(nextRevIndex === revisionArr.length) {
991 | // Shouldn't show a diff
992 | // TODO
993 | }else{
994 | // Calculate and update new date diff data of this shown revision
995 | $('.hvrevhead-'+revisionArr[i]+' .hvlefttimediff').text(revisionsById[revisionArr[nextRevIndex]].from(revisionsById[revisionArr[i]], true));
996 |
997 | // Calculate distance in revisions
998 | // TODO: Make this more efficient (maybe restructure data sent from API?)
999 | counter = 0;
1000 | iterrevid = revisionArr[i];
1001 | targetid = revisionArr[nextRevIndex];
1002 | while(iterrevid !== targetid) {
1003 | counter++;
1004 | iterrevid = Wikiwho.revisions[iterrevid][1]; // parent_id
1005 | }
1006 |
1007 | // Update distance in revisions
1008 | $('.hvrevhead-'+revisionArr[i]+' .hvrighttimediff').text(counter + (counter===1 ? " revision" : " revisions"));
1009 | }
1010 |
1011 | // Remove from hvhiddenRevisions array
1012 | Wikiwho.hvhiddenRevisions.splice(Wikiwho.hvhiddenRevisions.indexOf(revisionArr[i]), 1);
1013 | }
1014 | }
1015 | }
1016 | },
1017 |
1018 | addSelectionEvents: function() {
1019 | $("html").mouseup(function(e) {
1020 | if (window.getSelection) {
1021 | // Cancel if history or age view is already opened
1022 | if(Wikiwho.historyViewOpen || Wikiwho.ageViewOpen) {
1023 | return;
1024 | }
1025 |
1026 | // Cancel if mouse is at open indicator / hist box
1027 | if(Wikiwho.seqHistBox.css("display") !== "none") {
1028 | var relX = e.pageX - Wikiwho.seqHistBox.offset().left;
1029 | var relY = e.pageY - Wikiwho.seqHistBox.offset().top;
1030 | if((relX >= 0) && (relY >= 0) && (relX < Wikiwho.seqHistBox.outerWidth()) && (relY < Wikiwho.seqHistBox.outerHeight())) {
1031 | return;
1032 | }
1033 | }
1034 |
1035 | selectionRange = window.getSelection().getRangeAt(0);
1036 |
1037 | // Check whether something is selected
1038 | if(!selectionRange.collapsed) {
1039 | // Set start and end container (should be spans)
1040 | var firstToken = $(selectionRange.startContainer.parentElement);
1041 | var lastToken = $(selectionRange.endContainer.parentElement);
1042 |
1043 | // Reset some variable
1044 | Wikiwho.selectionEndTokenId = undefined;
1045 |
1046 | // Don't do anything if we can't associate the selection with author-tokens
1047 | if(!firstToken.hasClass("editor-token")) {
1048 | var tempFirstToken = $(selectionRange.startContainer.nextElementSibling);
1049 | if(tempFirstToken.hasClass("editor-token")) {
1050 | firstToken = tempFirstToken
1051 | }else{
1052 | tempFirstToken = firstToken.parent();
1053 | if(tempFirstToken.hasClass("editor-token")) {
1054 | firstToken = tempFirstToken;
1055 | }else{
1056 | return;
1057 | }
1058 | }
1059 | }
1060 | if(!lastToken.hasClass("editor-token")) {
1061 | var tempLastToken = $(selectionRange.endContainer.previousElementSibling);
1062 | if(tempLastToken.hasClass("editor-token")) {
1063 | lastToken = tempLastToken
1064 | }else{
1065 | tempLastToken = lastToken.parent();
1066 | if(tempLastToken.hasClass("editor-token")) {
1067 | lastToken = tempLastToken;
1068 | for(i = 0; i < 3; i++) {
1069 | if(tempLastToken.next().hasClass("editor-token")) {
1070 | Wikiwho.selectionEndTokenId = tempLastToken.next().attr("id").slice(6);
1071 | break;
1072 | }
1073 | if(tempLastToken.next().find("span.editor-token").length > 0) {
1074 | Wikiwho.selectionEndTokenId = tempLastToken.next().find("span.editor-token").first().attr("id").slice(6);
1075 | break;
1076 | }
1077 | tempLastToken = tempLastToken.parent();
1078 | }
1079 | }else{
1080 | return;
1081 | }
1082 | }
1083 | }
1084 |
1085 |
1086 | // Check whether these start and end tokens are already saved and indicator is shown
1087 | if(firstToken.is(Wikiwho.seqStartToken) && lastToken.is(Wikiwho.seqEndToken) && (Wikiwho.seqHistBox.css("display") !== "none")) {
1088 | // Cancel and don't reopen the indicator
1089 | return;
1090 | }
1091 |
1092 | // Save start and end token
1093 | Wikiwho.seqStartToken = firstToken;
1094 | Wikiwho.seqEndToken = lastToken;
1095 |
1096 | // Calculate height of marked text part
1097 | var selectionHeight = Wikiwho.seqEndToken.offset().top + Wikiwho.seqEndToken.outerHeight(false) - Wikiwho.seqStartToken.offset().top;
1098 |
1099 | // Calculate optimal history view height
1100 | var maxviewheight = $(window).height() - (selectionHeight + 20);
1101 |
1102 | // Stop hide animation (allows text selection via double-click)
1103 | Wikiwho.seqHistBox.stop();
1104 |
1105 | // Check whether selection is too big and if so, notify the user
1106 | if((maxviewheight < $(window).height()/5) || (maxviewheight < 150)) {
1107 | Wikiwho.seqHistBox.addClass("indicator");
1108 | Wikiwho.seqHistBox.addClass("indicatortoolong");
1109 | Wikiwho.seqHistBox.css("bottom", "-2em");
1110 | Wikiwho.seqHistBox.animate({"bottom": "0px"}, 300, function() {});
1111 | Wikiwho.seqHistBox.show();
1112 | return;
1113 | }
1114 |
1115 | // Show history view indicator
1116 | Wikiwho.seqHistBox.addClass("indicator");
1117 | Wikiwho.seqHistBox.removeClass("indicatortoolong");
1118 | Wikiwho.seqHistBox.removeClass("indicatoronerev");
1119 | Wikiwho.seqHistBox.css("bottom", "-2em");
1120 | Wikiwho.seqHistBox.animate({"bottom": "0px"}, 300, function() {});
1121 | Wikiwho.seqHistBox.show();
1122 | }else{
1123 | // Hide history view indicator
1124 | if(!Wikiwho.historyViewOpen) {
1125 | Wikiwho.seqHistBox.animate({"bottom": "-2em"}, 300, function() {
1126 | Wikiwho.seqHistBox.hide();
1127 | Wikiwho.seqHistBox.css("top", "");
1128 | });
1129 | }
1130 | }
1131 | }
1132 | });
1133 |
1134 | Wikiwho.newcontent.mousedown(function() {
1135 |
1136 | });
1137 | },
1138 |
1139 | fillRightPanel: function() {
1140 | // Create list box for authors
1141 | var authorListBox = $("#wikiwhoAuthorList").empty();
1142 |
1143 | // Add authors to list box
1144 | for (var i = 0; i < Wikiwho.present_editors.length; i++) {
1145 | var author_name = Wikiwho.present_editors[i][0];
1146 | var author_id = Wikiwho.present_editors[i][1]; // class name
1147 | var author_score = Wikiwho.present_editors[i][2];
1148 | var author_is_anonymous = author_name.startsWith('0|')
1149 | // console.log(author_id, author_name, author_score);
1150 |
1151 | author_name = author_name.replace(/^0\|/, '')
1152 |
1153 | var authentry = $(''+author_score.toFixed(1)+'%').appendTo(authorListBox);
1154 | $('
').appendTo(authentry);
1157 | $(''+author_name+'').appendTo(authentry);
1158 |
1159 | // Create click handler (wrap in a closure first so the variables are passed correctly)
1160 | (function(author_id, authentry) {
1161 | authentry.mousedown(function(e){ e.preventDefault(); });
1162 | authentry.click(function() {
1163 | if(typeof Wikiwho.coloredAuthors[author_id] === 'undefined') {
1164 | // if editor is not selected already
1165 | if(Wikiwho.tokenColors.length === 0) {
1166 | alert("You can't select any more editors. Please deselect an editors first to be able to select another one again.");
1167 | return;
1168 | }
1169 |
1170 | if(Wikiwho.conflictViewOpen) {
1171 | alert("Conflict view is opened! Please close the conflict view first.");
1172 | return;
1173 | } else if(Wikiwho.ageViewOpen) {
1174 | alert("Age view is opened! Please close the age view first.");
1175 | return;
1176 | }
1177 |
1178 | //var colorindex = Math.floor(Math.random()*Wikiwho.tokenColors.length);
1179 | var color = Wikiwho.tokenColors.splice(0, 1)[0];
1180 | var contrastColor = Wikiwho.getContrastingColor(color);
1181 | Wikiwho.coloredAuthors[author_id] = color;
1182 | $("span.token-editor-"+author_id).css({"background-color": color,
1183 | "color": contrastColor[0]}).find("*").css("color", contrastColor[1]);
1184 | $("div.hvauthorid-"+author_id).css({"background-color": color, "color": contrastColor[0]});
1185 | authentry.css({"background-color": color,
1186 | "color": contrastColor[0]});
1187 | }else{
1188 | // if editor is already selected
1189 | Wikiwho.tokenColors.unshift(Wikiwho.coloredAuthors[author_id]);
1190 | delete Wikiwho.coloredAuthors[author_id];
1191 | $("span.token-editor-"+author_id).css({"background-color": "", "color": ""}).find("*").css("color", "");
1192 | $("div.hvauthorid-"+author_id).css({"background-color": "", "color": ""});
1193 | authentry.css({"background-color": "", "color": ""});
1194 | }
1195 | });
1196 |
1197 | authentry.hover(function(event) {
1198 | // Mousein event handler
1199 |
1200 | // Remove all selection markers
1201 | $("span.editor-token").removeClass("selected hvselected");
1202 | $("div.hvrevauthor").removeClass("selected");
1203 | $("#wikiwhoAuthorList li").removeClass("selected");
1204 | clearTimeout(Wikiwho.deselectTimeout);
1205 |
1206 | // Mark all tokens of this author
1207 | $("span.token-editor-"+author_id).addClass("selected");
1208 | $("div.hvauthorid-"+author_id).addClass("selected");
1209 | $("li#editor-"+author_id).addClass("selected");
1210 | }, function(event) {
1211 | // Mouseout event handler
1212 | Wikiwho.deselectTimeout = setTimeout(function(){
1213 | // Remove all selection markers
1214 | $("span.editor-token").removeClass("selected hvselected");
1215 | $("div.hvrevauthor").removeClass("selected");
1216 | $("#wikiwhoAuthorList li").removeClass("selected");
1217 | }, 500);
1218 | });
1219 | })(author_id, authentry);
1220 | }
1221 | },
1222 |
1223 | hoverToken: function(authorid) {
1224 | // Clear deselect timeout
1225 | clearTimeout(Wikiwho.deselectTimeout);
1226 |
1227 | // Clear "current token" marker
1228 | $(".hvselected").removeClass("hvselected").addClass("selected");
1229 |
1230 | // Clear hvauthor marker
1231 | $("div.hvrevauthor").removeClass("selected");
1232 |
1233 | // Determine whether this author is already/still selected
1234 | var selected = $("#wikiwhoAuthorList li.selected");
1235 | if(selected.length >= 1) {
1236 | var selectedAuthId = selected.attr('id').slice(7);
1237 | if(selectedAuthId === authorid) {
1238 | // Already selected, don't do anything else
1239 | return;
1240 | }
1241 |
1242 | selected.stop( false, true ).stop( false, true ).stop( false, true );
1243 | selected.removeClass("selected");
1244 | $("span.token-editor-"+selectedAuthId).removeClass("selected");
1245 | }
1246 |
1247 | // Scroll the author list to the position of the current entrys author
1248 | Wikiwho.scrollToShowAuthEntry(authorid);
1249 |
1250 | // Mark all tokens of this author
1251 | $("span.token-editor-"+authorid).addClass("selected");
1252 | $("div.hvauthorid-"+authorid).addClass("selected");
1253 | $("li#editor-"+authorid).addClass("selected");
1254 |
1255 | // Flash the author entry
1256 | $("li#editor-"+authorid).delay(300).fadeOut(100).fadeIn(300);
1257 | },
1258 |
1259 | addTokenEvents: function() {
1260 | var authortokens = $("span.editor-token");
1261 |
1262 | authortokens.hover(function(event) {
1263 | // Mousein event handler
1264 | var authorid = $(this).attr('class').match(/token-editor-([a-f0-9]+)/)[1];
1265 | var tokenid = $(this).attr('id').slice(6);
1266 |
1267 | // Call the general hover handler
1268 | Wikiwho.hoverToken(authorid);
1269 |
1270 | // If history view is open, add red outline to current token
1271 | if((Wikiwho.historyViewOpen) && ($("span#token-age-"+tokenid).length === 1)) {
1272 | // Add outline
1273 | $("span#token-age-"+tokenid).removeClass("selected").addClass("hvselected");
1274 |
1275 | // Scroll history view to right position if necessary
1276 | $("#wikiwhoseqhistbox .hvtokenbodies").stop(true);
1277 | var tokenleft = $("span#token-age-"+tokenid).parent().position().left;
1278 | var tokenright = tokenleft + $("span#token-age-"+tokenid).parent().outerWidth();
1279 | var scrollpos = $("#wikiwhoseqhistbox .hvtokenbodies").scrollLeft();
1280 |
1281 | if(tokenleft < 0) {
1282 | $("#wikiwhoseqhistbox .hvtokenbodies").stop(true).animate({scrollLeft: tokenleft+scrollpos}, 500);
1283 | }else if(tokenright > $("#wikiwhoseqhistbox .hvtokenbodies").width()-2) {
1284 | $("#wikiwhoseqhistbox .hvtokenbodies").stop(true).animate({scrollLeft: tokenright+scrollpos-$("#wikiwhoseqhistbox .hvtokenbodies").outerWidth()+2}, 500);
1285 | }
1286 | }
1287 | }, function(event) {
1288 | // Mouseout event handler
1289 | Wikiwho.deselectTimeout = setTimeout(function(){
1290 | // Remove all selection markers
1291 | $("span.editor-token").removeClass("selected hvselected");
1292 | $("div.hvrevauthor").removeClass("selected");
1293 | $("#wikiwhoAuthorList li").removeClass("selected");
1294 | }, 500);
1295 | });
1296 |
1297 | authortokens.click(function() {
1298 | if(Wikiwho.conflictViewOpen) {
1299 | alert("Conflict view is opened! Please close the conflict view first.");
1300 | return;
1301 | } else if(Wikiwho.ageViewOpen) {
1302 | alert("Age view is opened! Please close the age view first.");
1303 | return;
1304 | }
1305 | var editor = $(this).attr('class').match(/token-editor-([a-f0-9]+)/)[1];
1306 | $("li#editor-"+editor).click();
1307 | return false;
1308 | });
1309 | },
1310 |
1311 | scrollToShowAuthEntry: function(editor) {
1312 | // Scroll target
1313 | var authEntry = $('li#editor-'+editor);
1314 |
1315 | // Don't try to scroll if there is no target to scroll to
1316 | if(authEntry.length === 0) return;
1317 |
1318 | // Set a few helper Variables
1319 | var authList = $('#wikiwhorightbar');
1320 | var authListTop = authList.scrollTop();
1321 | var listHeight = authList.height();
1322 | var entryTop = authEntry.position().top;
1323 | var entryHeight = authEntry.height();
1324 |
1325 | // Determine whether we have to scroll
1326 | if(entryTop < 0) {
1327 | // Entry is too high, scroll up
1328 | authList.stop().animate({
1329 | scrollTop: entryTop + authListTop
1330 | }, 300);
1331 | }else if(entryTop > listHeight - entryHeight) {
1332 | // Entry is too low, scroll down
1333 | authList.stop().animate({
1334 | scrollTop: entryTop + authListTop - listHeight + entryHeight
1335 | }, 300);
1336 | }
1337 | },
1338 |
1339 | openConflictView: function() {
1340 | // Do nothing - no conflicts (special case)
1341 | if(Wikiwho.biggest_conflict_score === 0) {
1342 | alert('There is no conflict.');
1343 | return;
1344 | }
1345 | // Remove colorization
1346 | $('span.editor-token').css({'background-color': '', 'color': ''}).find("*").css('color', '');
1347 | $("#wikiwhoAuthorListHeader").text('Conflict View');
1348 | $("#ageLimitBox").hide();
1349 | $("#wikiwhoAuthorList").hide();
1350 | // $(".editor-token").unbind('mouseenter mouseleave');
1351 | // $(".editor-token").off('mouseenter mouseleave');
1352 | // Color all tokens
1353 | var conflict_opacity_value = 0;
1354 | for (var i = 0; i < Wikiwho.tokens.length; i++) {
1355 | var conflict_score = Wikiwho.tokens[i][0];
1356 | if (conflict_score !== 0) {
1357 | conflict_opacity_value = conflict_score/Wikiwho.biggest_conflict_score;
1358 | $('span#token-'+i).css({
1359 | 'background-color': 'rgba(255,0,0,'+conflict_opacity_value+')',
1360 | 'color': (conflict_opacity_value >= 0.5) ? 'white' : 'black'
1361 | }).find("*").css("color", (conflict_opacity_value >= 0.5) ? 'white' : 'black');
1362 | }
1363 | }
1364 | // Mark conflict view as open
1365 | Wikiwho.provenanceViewOpen = false;
1366 | Wikiwho.conflictViewOpen = true;
1367 | Wikiwho.ageViewOpen = false;
1368 | $('#provenanceviewbutton').removeClass('provenanceviewbuttonopen');
1369 | $('#conflictviewbutton').addClass("conflictviewopen");
1370 | $('#ageviewbutton').removeClass('ageviewbuttonopen');
1371 | },
1372 |
1373 | closeConflictView: function() {
1374 | // Remove colorization
1375 | $('span.editor-token').css({'background-color': '', 'color': ''}).find("*").css('color', '');
1376 | $("#wikiwhoAuthorListHeader").text('Editor List');
1377 | $("#wikiwhoAuthorList").show();
1378 | // $(".editor-token").on('mouseenter mouseleave');
1379 | // Recolor tokens
1380 | Object.keys(Wikiwho.coloredAuthors).forEach(function(authorid) {
1381 | var color = Wikiwho.coloredAuthors[authorid];
1382 | var contrastColor = Wikiwho.getContrastingColor(color);
1383 | $("span.token-editor-"+authorid).css({"background-color": color,
1384 | "color": contrastColor[0]}).find("*").css("color", contrastColor[1]);
1385 | $('.hvauthorid-'+authorid).css({
1386 | 'background-color': color,
1387 | 'color': contrastColor[0]
1388 | });
1389 | });
1390 | // Mark conflict view as closed
1391 | Wikiwho.provenanceViewOpen = true;
1392 | Wikiwho.conflictViewOpen = false;
1393 | $('#provenanceviewbutton').addClass('provenanceviewbuttonopen');
1394 | $('#conflictviewbutton').removeClass("conflictviewopen");
1395 | },
1396 |
1397 | openAgeView: function() {
1398 | // Remove colorization
1399 | $('span.editor-token').css({'background-color': '', 'color': ''}).find("*").css('color', '');
1400 | $("#wikiwhoAuthorListHeader").text('Age View');
1401 | $("#wikiwhoAuthorList").hide();
1402 | $("#ageLimitBox").show();
1403 | // Color all tokens according to age
1404 | var shade_count = Math.ceil((Wikiwho.ageLimitTo-Wikiwho.ageLimitFrom)/Wikiwho.groupSize);
1405 | var age_days = 0;
1406 | var age_opacity_value = 0;
1407 | for (var i = 0; i < Wikiwho.tokens.length; i++) {
1408 | age_days = Wikiwho.tokens[i][6] / (60 * 60 * 24);
1409 | if (Wikiwho.ageLimitFrom <= age_days && age_days <= Wikiwho.ageLimitTo) {
1410 | age_opacity_value = (1/shade_count) * (shade_count + 1 - Math.ceil((age_days-Wikiwho.ageLimitFrom)/Wikiwho.groupSize));
1411 | $('span#token-'+i).css({'background-color': 'rgba(255,255,0,'+age_opacity_value+')'});
1412 | }
1413 | }
1414 | // Mark age view as open
1415 | Wikiwho.provenanceViewOpen = false;
1416 | Wikiwho.conflictViewOpen = false;
1417 | Wikiwho.ageViewOpen = true;
1418 | $('#provenanceviewbutton').removeClass('provenanceviewbuttonopen');
1419 | $('#conflictviewbutton').removeClass("conflictviewopen");
1420 | $('#ageviewbutton').addClass('ageviewbuttonopen');
1421 | // }
1422 | // else {
1423 | // alert('No token younger than ' + Wikiwho.ageLimit + ' days.');
1424 | // }
1425 | },
1426 |
1427 | closeAgeView: function() {
1428 | // Remove colorization
1429 | $('span.editor-token').css({'background-color': '', 'color': ''}).find("*").css('color', '');
1430 | $("#ageLimitBox").hide();
1431 | $("#wikiwhoAuthorListHeader").text('Editor List');
1432 | $("#wikiwhoAuthorList").show();
1433 | // Recolor tokens
1434 | Object.keys(Wikiwho.coloredAuthors).forEach(function(authorid) {
1435 | var color = Wikiwho.coloredAuthors[authorid];
1436 | var contrastColor = Wikiwho.getContrastingColor(color);
1437 | $("span.token-editor-"+authorid).css({"background-color": color,
1438 | "color": contrastColor[0]}).find("*").css("color", contrastColor[1]);
1439 | $('.hvauthorid-'+authorid).css({
1440 | 'background-color': color,
1441 | 'color': contrastColor[0]
1442 | });
1443 | });
1444 | // Mark age view as closed
1445 | Wikiwho.provenanceViewOpen = true;
1446 | Wikiwho.ageViewOpen = false;
1447 | $('#provenanceviewbutton').addClass('provenanceviewbuttonopen');
1448 | $('#ageviewbutton').removeClass("ageviewbuttonopen");
1449 | },
1450 |
1451 | // Check whether sth should be done and what (on this specific page)
1452 | pageCheck: function() {
1453 | return $("li#ca-nstab-main").hasClass("selected") && $("li#ca-view").hasClass("selected")
1454 | && !Wikiwho.contentAlreadyReplaced && !$('table.diff').length;
1455 | },
1456 |
1457 | addStyle: function() {
1458 | GM_addStyle("\
1459 | #wikiwhorightbar .editor-score {\
1460 | float: right;\
1461 | }\
1462 | #wikiwhorightbar {\
1463 | border-bottom: none;\
1464 | position: fixed;\
1465 | width: calc(15em + 2px);\
1466 | bottom: 0px;\
1467 | padding: 0px;\
1468 | overflow-y: scroll;\
1469 | }\
1470 | #wikiwhorightbar > div {\
1471 | padding: 10px;\
1472 | margin: 0px;\
1473 | }\
1474 | #wikiwhorightbar > div > h2 {\
1475 | margin-top: 0px;\
1476 | }\
1477 | @media screen and (min-width: 982px) {\
1478 | #wikiwhorightbar {\
1479 | width: calc(15.5em + 2px);\
1480 | }\
1481 | }\
1482 | ul#wikiwhoAuthorList {\
1483 | margin: 0px;\
1484 | }\
1485 | ul#wikiwhoAuthorList li {\
1486 | padding: 1px;\
1487 | padding-right: 3px;\
1488 | padding-left: 3px;\
1489 | list-style: none;\
1490 | }\
1491 | \
1492 | ul#wikiwhoAuthorList li:hover, ul#wikiwhoAuthorList li.selected {\
1493 | border: 1px solid blue;\
1494 | /*border: 1px solid #aaa;*/\
1495 | padding: 0px;\
1496 | padding-right: 2px;\
1497 | padding-left: 2px;\
1498 | background-color: #f5fffa;\
1499 | }\
1500 | .editor-token.selected, .hvrevauthor.selected {\
1501 | outline: 1px solid blue;\
1502 | }\
1503 | .hvselected, .editor-token-image.hvselected img {\
1504 | outline: 1px solid red;\
1505 | }\
1506 | .editor-token-image.hvselected {\
1507 | outline: none;\
1508 | }\
1509 | .editor-token-image.selected {\
1510 | outline: none;\
1511 | }\
1512 | .editor-token-image.selected img {\
1513 | outline: 1px solid blue;\
1514 | }\
1515 | #wikiwhoseqhistbox {\
1516 | background-color: rgb(255, 255, 255);\
1517 | position: fixed;\
1518 | bottom: 0px;\
1519 | right: calc(15em + 3px);\
1520 | left: calc(10em + 1px);\
1521 | border-top-color: rgb(167, 215, 249);\
1522 | border-top-style: solid;\
1523 | border-top-width: 1px;\
1524 | padding: 1.25em 1.5em 1.5em 1.5em;\
1525 | white-space: nowrap;\
1526 | box-sizing: border-box;\
1527 | }\
1528 | @media screen and (min-width: 982px) {\
1529 | #wikiwhoseqhistbox {\
1530 | right: calc(15.5em + 3px);\
1531 | left: calc(11em + 1px);\
1532 | }\
1533 | }\
1534 | #wikiwhoseqhistbox .hvcloseicon {\
1535 | position: absolute;\
1536 | width: 2em;\
1537 | top: 0.25em;\
1538 | left: 0.25em;\
1539 | cursor: pointer;\
1540 | }\
1541 | #wikiwhoseqhistbox.indicator .hvcloseicon {\
1542 | display: none;\
1543 | }\
1544 | #wikiwhoseqhistbox.indicator {\
1545 | height: 1em;\
1546 | padding: 0.5em;\
1547 | top: auto;\
1548 | box-sizing: content-box;\
1549 | }\
1550 | #wikiwhoseqhistboxopenindicator {\
1551 | text-align: center;\
1552 | display: none;\
1553 | }\
1554 | #wikiwhoseqhistboxonerevindicator {\
1555 | text-align: center;\
1556 | display: none;\
1557 | }\
1558 | #wikiwhoseqhistbox.indicator:not(.indicatortoolong):not(.indicatoronerev) #wikiwhoseqhistboxopenindicator {\
1559 | display: block;\
1560 | }\
1561 | #wikiwhoseqhistboxtoolongindicator {\
1562 | text-align: center;\
1563 | display: none;\
1564 | }\
1565 | #wikiwhoseqhistbox.indicatortoolong #wikiwhoseqhistboxtoolongindicator {\
1566 | display: block;\
1567 | }\
1568 | #wikiwhoseqhistbox.indicatoronerev #wikiwhoseqhistboxonerevindicator {\
1569 | display: block;\
1570 | }\
1571 | #wikiwhoseqhistleftbox, #wikiwhoseqhistmiddlebox, #wikiwhoseqhistrightbox {\
1572 | display: inline-block;\
1573 | vertical-align: top;\
1574 | height: 100%;\
1575 | overflow: hidden;\
1576 | }\
1577 | #wikiwhoseqhistmiddlebox {\
1578 | width: calc(100% - 17em);\
1579 | }\
1580 | #wikiwhoseqhistbox.indicator #wikiwhoseqhistview {\
1581 | display: none;\
1582 | }\
1583 | #wikiwhoseqhistview {\
1584 | position: relative;\
1585 | }\
1586 | #wikiwhoseqhistview .hvtokencol {\
1587 | display: inline-block;\
1588 | }\
1589 | #wikiwhoseqhistview .hvtokenhead {\
1590 | height: 2em;\
1591 | line-height: 2em;\
1592 | margin-left: 0.1em;\
1593 | margin-right: 0.1em;\
1594 | display: inline-block;\
1595 | vertical-align: bottom;\
1596 | }\
1597 | #wikiwhoseqhistmiddlebox .hvtokenheaders {\
1598 | position: relative;\
1599 | overflow: hidden;\
1600 | right: 0px;\
1601 | left: 0px;\
1602 | }\
1603 | #wikiwhoseqhistmiddlebox .hvtokenbodies {\
1604 | overflow: auto;\
1605 | width: 100%;\
1606 | height: calc(100% - 2em);\
1607 | }\
1608 | #wikiwhoseqhistmiddlebox .hvtokencolpiece {\
1609 | height: calc(4.5em - 1px);\
1610 | width: 100%;\
1611 | border-top: dotted 1px blue;\
1612 | border-left: 1px solid white;\
1613 | border-right: 1px solid white;\
1614 | }\
1615 | #wikiwhoseqhistmiddlebox .hvtokencolpiece:last-child {\
1616 | border-bottom: dotted 1px blue;\
1617 | }\
1618 | #wikiwhoseqhistmiddlebox .hvtokencolpiece.hvtokeninarticle {\
1619 | background-color: rgb(167, 215, 249);\
1620 | }\
1621 | #wikiwhoseqhistleftbox {\
1622 | margin-top: 1.25em;\
1623 | height: calc(100% - 1.25em);\
1624 | position: relative;\
1625 | }\
1626 | #wikiwhoseqhistleftbox > div {\
1627 | height: 4.5em;\
1628 | line-height: 1.5em;\
1629 | text-align: right;\
1630 | }\
1631 | #wikiwhoseqhistleftbox > div:last-of-type {\
1632 | height: 1.5em;\
1633 | margin-bottom: 20px;\
1634 | }\
1635 | #wikiwhoseqhistleftbox > div > .hvdatetimediff {\
1636 | height: 3em;\
1637 | line-height: 3em;\
1638 | vertical-align: top;\
1639 | }\
1640 | #wikiwhoseqhistleftbox > div .hvupdownarrow {\
1641 | position: absolute;\
1642 | left: 2.5em;\
1643 | margin-top: -0.1em;\
1644 | font-size: 3em;\
1645 | }\
1646 | #wikiwhoseqhistleftbox > div .hvupdownarrow a, #wikiwhoseqhistleftbox > div .hvupdownarrow a:hover, #wikiwhoseqhistleftbox > div .hvupdownarrow a:visited, #wikiwhoseqhistleftbox > div .hvupdownarrow a:link, #wikiwhoseqhistleftbox > div .hvupdownarrow a:active {\
1647 | color: black;\
1648 | text-decoration: none;\
1649 | }\
1650 | #wikiwhoseqhistleftbox > div .hvupdownarrow a:hover {\
1651 | color: blue;\
1652 | }\
1653 | #wikiwhoseqhistleftbox > div span.hvlefttimediff {\
1654 | position: absolute;\
1655 | left: 0.5em;\
1656 | }\
1657 | #wikiwhoseqhistleftbox > div span.hvrighttimediff {\
1658 | position: absolute;\
1659 | left: 10em;\
1660 | }\
1661 | #wikiwhoseqhistleftbox .hvrevauthor {\
1662 | text-overflow: ellipsis;\
1663 | overflow: hidden;\
1664 | max-width: 8em;\
1665 | }\
1666 | #wikiwhoseqhistleftbox .hvspacer, #wikiwhoseqhistleftbox .hvspacerauth {\
1667 | border-bottom: 1px dotted blue;\
1668 | min-width: 2em;\
1669 | display: inline-block;\
1670 | vertical-align: top;\
1671 | height: 0.75em;\
1672 | }\
1673 | #wikiwhoseqhistleftbox .hvspacerauth {\
1674 | min-width: 0;\
1675 | white-space: pre;\
1676 | }\
1677 | #wikiwhoseqhistleftbox .hvrevauthor, #wikiwhoseqhistleftbox .hvrevdate, #wikiwhoseqhistleftbox .hvrevdifflinks {\
1678 | display: inline-block;\
1679 | vertical-align: top;\
1680 | }\
1681 | .hvtokenheadspacer {\
1682 | width: 100px;\
1683 | display: inline-block;\
1684 | }\
1685 | img.hvdifficon {\
1686 | height: 1.5em;\
1687 | }\
1688 | .hvtokencol.hvtokendummycol {\
1689 | background: rgb(167, 215, 249);\
1690 | background-image: repeating-linear-gradient(45deg, transparent, transparent 1em, rgba(255,255,255,.5) 1em, rgba(255,255,255,.5) 2em);\
1691 | }\
1692 | #provenanceviewbutton {\
1693 | background-color: white;\
1694 | height: 24px;\
1695 | }\
1696 | #provenanceviewbutton.provenanceviewbuttonopen {\
1697 | background-color: #00ff00;\
1698 | }\
1699 | #conflictviewbutton {\
1700 | background-color: white;\
1701 | height: 24px;\
1702 | }\
1703 | #conflictviewbutton.conflictviewopen {\
1704 | background-color: #00ff00;\
1705 | }\
1706 | #ageviewbutton {\
1707 | background-color: white;\
1708 | height: 24px;\
1709 | }\
1710 | #ageviewbutton.ageviewbuttonopen {\
1711 | background-color: #00ff00;\
1712 | }\
1713 | #ageLimitFrom{\
1714 | float: right;\
1715 | }\
1716 | #ageLimitTo{\
1717 | float: right;\
1718 | }\
1719 | #groupSize{\
1720 | float: right;\
1721 | }\
1722 | img.wwhouserinfoicon {\
1723 | height: 1.5em;\
1724 | cursor: pointer;\
1725 | }\
1726 | img.wwhouserinfoiconhidden {\
1727 | visibility: hidden;\
1728 | cursor: default;\
1729 | }\
1730 | #wikiwhoAuthorList li span:last-child {\
1731 | text-overflow: ellipsis;\
1732 | white-space: nowrap;\
1733 | overflow: hidden;\
1734 | width: calc(100% - 4.5em);\
1735 | display: inline-block;\
1736 | margin-bottom: -0.4em;\
1737 | }\
1738 | ");
1739 | },
1740 |
1741 | // Initialize the Wikiwho Userscript
1742 | initialize: function() {
1743 | if(!Wikiwho.initialized && Wikiwho.pageCheck()) {
1744 | // We're on a web page where we should do something
1745 | Wikiwho.initialized = true;
1746 | Wikiwho.addStyle();
1747 | Wikiwho.createHTMLElements();
1748 | Wikiwho.getWikiwhoData();
1749 | }
1750 | }
1751 | };
1752 |
1753 | // Do not run in frames
1754 | if (window.top !== window.self) {
1755 | // Do nothing
1756 | }else{
1757 | // Initialize the script as soon as the content text / page is loaded
1758 | function waitForMwContent() {
1759 | if($("#mw-content-text").length > 0) {
1760 | Wikiwho.initialize();
1761 | }else{
1762 | setTimeout(waitForMwContent, 100);
1763 | }
1764 | }
1765 |
1766 | waitForMwContent();
1767 | }
1768 |
--------------------------------------------------------------------------------