From 1f7b58ad1c7ce0e0904a24b9ebd19b26814823d3 Mon Sep 17 00:00:00 2001 From: poni Date: Mon, 4 Oct 2021 11:48:57 +0200 Subject: [PATCH] paged.js scripts added --- layout/index.html | 212 + layout/interface.css | 4 + layout/script/paged.js | 32736 +++++++++++++++++++++++++++++ layout/script/paged.polyfill.js | 32782 ++++++++++++++++++++++++++++++ layout/styles/1.css | 0 layout/styles/2.css | 0 layout/styles/3.css | 0 7 files changed, 65734 insertions(+) create mode 100644 layout/index.html create mode 100755 layout/interface.css create mode 100755 layout/script/paged.js create mode 100755 layout/script/paged.polyfill.js create mode 100644 layout/styles/1.css create mode 100644 layout/styles/2.css create mode 100644 layout/styles/3.css diff --git a/layout/index.html b/layout/index.html new file mode 100644 index 0000000..38869f3 --- /dev/null +++ b/layout/index.html @@ -0,0 +1,212 @@ + + + + + + + + + + + +
+

It said the waiting time would be 40 mins, it ended up taking close to 2 hours

I found this ridiculous, you wouldn't mind waiting if it actually told you it was going to be that length of time

When the food did arrive it was barely warm and not of a very high standard

I was starving at this stage so i did eat it but won't be ordering from here again

+

+

food was bland. would not order again

+ +
+ +

Ordered food all tasted basically same,one meal was missing so phoned up to be told they would send the driver back out with it waited an hour and still never got delivered called back to be told the driver had gave the meal to someone else by mistake but had to wait an hour to be told I wernt getting the meal I ordered so had to order from somewere else would definitely not order here again very small portions aswell literally was same currys just bit spicer than the other one very unprofessional do not waste your money ive just wasted £40

Unfortunately not a great meal, edible in parts but ultimately had to bin it. Keema naan was sugary and sweet and seemed to be laden with either cheap red tandoori spice or some sort of red dye. Samosas were the tiny premade filo pastry kind which was disappointing. Aloo gobi was bizarrely crunchy although the potatoes were perfect. Paratha was edible but was incredibly a dense doughy rather than rich, buttery and flakey. Overall the meal was edible in parts, arrived on time, well packed but the food was overall very poor.

the food is normally excellent well that’s if you get the proper chef that is to the extent that the house special dish I always have looked and tasted like something completely different with the quality of a microwave meal and when you call and I say that you get poor customer service like I should know as customer the proper chef isn’t working and I should order something else well maybe these 20 reviews coming up will attract the attention of someone who actually cares about a very regular customer

Ordered food just after 8pm, we got told it would be here just before 10pm, which we thought was a while (2 hours) but we understood it was giants weekend and it they were going to be busy. Food didn’t arrive until around 10:30, and it was stone cold and obviously not cooked fresh (frozen) I had called them at around 10:15, asking whether it had been dispatched, and had staff laughing and mocking. They said the food had been sat there a while (problem with delivery) and they were going to cook some fresh, obviously not the case... Overall very dissapointing. Threw the food away, and ended up going elsewhere

I have always been a regular customer of this restaurant, but on this occasion, we waited for 1 hour 40 minutes for it to be delivered(Half an hour later than the expected time), rang up twice and the customer service was average at best. When it arrived i said i wanted to check if it was warm before i accepted it, to which the driver was very rude and told me 'to be careful not to burn your fingers'. When we ate it, the food was lukewarm and not of a great quality. I rang up the restaurant to complain about the driver and were not offered any compensation or discount. Overall, terrible customer service, late delivery and poor food.

I wish I was able to give this restaurant 0 stars. I had to ring up to see if the order had actually been noticed by the restaurant and was told that they weren't quite sure where our order was and was told to just wait it out. Awful customer service. Once the food did arrive, some 90 mins later, the food was cold and we had only received two curries and no rice. This is such a shame because I have actually eaten in the restaurant a number of times and have rave reviews about it. Will not be giving them my custom again. Absolutely shocking.

I have ordered from here for a while now as happy with the food even though sometimes the quality is not always consistently great! On Saturday I order food as normal, the food arrived with no rice for the both of us. I straight away called the restaurant to ask what has happened. They told me I had not ordered it! The manager or owner was rude and argumentative and did not resolve the problem as he should of. I offered to pay for the rice if it was my fault but he was rude and spoke over me. I told him I would never use his restaurant again as I now had 2 curries with no rice!! He was not bothered, We ended up throwing the food in the bin as it was now cold as I was on the phone for 15 minutes. No apology was given or even any empathy! Terrible service recovery!! Will never use them again!! Andy Fernandez

After being told there would be a 45 minute wait, I called up after I had waited an hour to find out where my order was. I was told on the phone that it would 'be there in 5 minutes'. After another half an hour the meal finally came, delivered by a rude man who practically threw the meal at me, I had waited over an hour and a half in total for my meal, which I wouldn't mind that much if the food was nice. When it came it was cold. I ordered a chicken korma and, for the price, I was shocked to find that there was 2 small pieces of chicken and the box it came in was half full. After eating the meal both me and my girlfriend fell ill and I was up 3 or 4 times through the night to go and be sick. I definitely won't be using this place again atall.

Quality is very poor. Food arrived luke-warm had to be re-heated in the microwave, (so I do hope cooking was done from scratch and nothing was re-heated at the restaurant) The massive chunks of lemon peel in the rice are not pleasant. I can understand the need for lemon peel in this dish, but please cut away all the white pith (extremely bitter) and slice rind more finely and the dish will work better. The riata was appalling - cucumber and yoghurt do not a riata make. I had to finish this dish off at home with my own herbs and spices, which traditionally go into a riata. My onion chutney was missing entirely which I was looking forward to as I like that with my naan bread and for which I PAID!!! Well I ate it and I'm not hungry any more and I can't say I enjoyed it very much - sorry. I didn't want to rate the service marked above, because I don't believe I experienced any service, but I could not delete the star.

+ +

is shocking      better Awful      was cold      a good      was awful      was 20:42      my order arrived      directly to make sure      paid good      was sloppy      a pleasant      so burnt      was black      the last      Food arrived      's okay      food online      and soggy      are bad      should be pink      pink and creamy      pink and creamy and beautiful      a large      for trusting      really grumpy      a thin      , artisanal      I ordered      a premier      a premier corner shop innthr premier      All delivered      and cold      cold , dry      Really bad      seems frozen      a ready      , similar      Horribly expensive      was pretty      paid extra      this resultant      this resultant again Hallal      this resultant again Hallal Italian clasic cheap      ages and its realy      ignoring such      better frozen      the white      Nothing fresh      the oily      too salty      was nice      was dreadful      dreadful , bland      as possible      possible , cold      were bland      so bad      Too expensive      any other      My regular      pizza joint      completely soggy      completely soggy and tasteless      that bad      my own      out tasteless      was suppose      the wrong      , first      half cold and half wrong      the second      Too much      was late      apologised due      was nice      ’ s      not proper      so disappointing      5 mins late      the expected      were cold      so bad      had been hot      than cold      the only      absolutely terrible      an authentic      incredibly bland      not tasty      was disgusting      a frozen      Soggy and cold      and nasty      10 minutes late..no      was lukewarm,garlic      a little      really do n't mean to be funny      more flavor...delivery      was chirpy      for extra      - first      an electric      was awful      really sloppy      very hot      t eat chilli due      Very expensive      really not nice      was lucky      the light      not happy      all Horrible      all Horrible food tiny      a huge      taste fresh      not quite sure      the excellent      was awful      its a friday      is fair      the first      a standard      that sweet      completely uncooked      My other      curry was basic      the only      - horrendous      was late      my meal Food arrived      30 minutes late      late and cold      were reasonable      very average      being full      Absolutely disgusting      an extra      an 

+
+

We ordered food the food was late when we called they forgot when we ordered and what we ordered when the food came it was cold and hard and one of the pizzas we ordered was the completely wrong and when the delivery man came he threw the pizza and my friend and left straight away before we could complain

+

+

It still isn’t here

+ +
+ +

Sorry to write but the food was rather disappointing the chicken kebab and grilled chicken literally was bland had no flavourings whatsoever. And very dry Was very hard and awkward to swallow. The pizza was slightly undercooked could have done with the cheese browning off rather than just been melted. Chips were quite soggy and had minted cabbage in my salad. Need to allow us options to what salad is preferred. The burgers weren’t bad. Think they was the only food on the menu my kids and partner enjoyed and liked.

We had placed the order at 6:30pm and the original driver was unsure of the location, which is fine. I tried to direct the driver although there was limited English he could understand. We received the order at 9:25pm. Upon which the food was cold. The staff had apologised although this was not good enough. 3 hours to deliver and the food was still cold when it did arrive. They also had re-made the order... Then gave us the old order too. I'm sorry but this isn't good enough. I understand drivers can get lost and I had recommended to remake the food but I wasn't expecting the food to be cold and another 2 hours delay. (Originally was 1 hour on estimated arrival) 3 hours total!

Said delivery was for 3.55am rang shop at 3.55am when food should of already been delivered , man answered said we are closed so I said over my order he asked for my postcode then said food would arrive at 4.25am I said over the time it said food should of already been delivered he said no that's not time I have wait 4.25am ... Delivery arrived at 4.20am Luke warm chips not cooked properly food was awful delivery driver never put ice cream in I paid £3.49 for either rang restaurant back to tell them he said we are closed I get him to bring it now or tomorrow I give you something free sorry no I know shop is closed but I placed my order at 3am food should be just as good as when restaurant first opens everyday to it closes not half cooked an Luke warm either and still at nearly 5am waiting on Ben an Jerry's ice cream I want a refund please as I was hungry an looked forward to having my meal yet had a bit of it and as its that bad can not eat anymore

my order a mixed kebab and a garlic bread and a bottle of pop. food arrived on time, delivery lad great. food place is one mile away. the bottle of pop come in the hot food insulate bag, so was warm. asked for no tomatoes, I got them, very few pieces of meat, garlic bread was cold, not garlic flavoured and like rubber if I am honest. really did used to love this place, it went down a while ago and since they set up again with new page I thought I’d give them a go. I was mistaken. shame. to reiterate the lad who delivered the food was great.

+ +

incredibly rude      actually 3.4      the catchment      was awful      awful and cold      me dierarra      Not happy      a simple      chicken and tasteless      again NaN      very average      the new      're new      a human      arrived cold      's DREADFUL      , tiny      was difficult      just pure      ( cold      was horrid      of feta      me aware      mint sauce First      18.30 due      being inedible      inedible , bland      inedible , bland and cold      just to add insult      Was easy      order online      the same      instead got sent chunky      was greasy      greasy bland and cold      spoke to was rude      rude and dismissive      a weird      And poor      Also arrived cold      was late      The strange      before from different      very nice      not sure      n't good      was disgusting      was tough      2 small      was wrong      got was cold      t so late      was awful      Topping was scant      was cold      very bad      like old      very bad      The chips awful      not good      was cold      cold and dry      was wet      wet and cold      wet and cold and crushed      a worse      me wrong      the same      in such      each other      where okay      arrived cold      was squashed      Two minuscule      a tiny      the same      is ready      looked online      still hungry      arrived freezing cold      and tasteless      so cold      the map      turned up freezing cold      Very unhappy      this order Food arrived      is freezing cold      cold and disgusting      was cold      was cold      cold very unhappy      Food cold      got incorrect      is disgusting      were cold      was burnt      very unhappy      My daughters crepe      was soggy      and crepe      was cold      was cold      bed very upset      when arrived      a refund my crepe      came squashed      more like      together not happy      Wasnt impressed      it arrived      my crepe      and cold      my thick      to be able      was tiny      two small      Very disappointed      my delivery Crepe rock solid      my ice cream Last      been nice      the little      not receive      no delivery awful      Ordered white      and raspberry      no raspberry      me outside      the wrong      the correct      The wrong      was freezing cold      a whole      nothing absolute      Absolutely terrible      was cold      paid extra      was terrible      your own      , 

+
+

Ordered the Special kebab yet really wish I hadn’t

Shish is just sliced beef but the chicken was cooked lovely so that’s a plus

The Donna meat was white

Clearly not cooked

Or is this how you normally serve Donna meat?

As a new customer n last I suggest you cook the Donna meat first Thanks, would be much appreciated 😏

+

+

Wasn't fresh and pizza wasn't cooked properly

+ +
+ +

We always order from Amalfi an have always been happy with the food in the past and if we have had to call which was very rearly we have had good service but this evening I called up the restaurant to just check where are food was as on the just eat app the order status hadn't changed and had 5 minutes until then it would have been late! Someone answered and passed me to someone else, when the gentleman came on he sounded flustered and asked me what I ordered, when I had just given him my post code (which is usually sufficient) I said to him that it was a big order an could he not look with my post code an he said abruptly 'just tell me your order ( no please or thank you ) I asked him not to speak to me like that then he said 'I'm busy what did you order' I asked him again not to speak to me like that! The gentleman did apologise which I appreciate but I just think busy or not please have manners as I did when I called you up! The food was OK, the double cheeseburger, chips and donna meat was OK but the garlic bread and popcorn chick was both overcooked. We spent a lot of money tonight ... 1. don't appreciate being spoken to like that and 2. Don't appreciate getting food that is overcooked. I don't think we would order again now as left a bad taste in my mouth. Thank you

Delivery was 30mins late the food was like warm at best and absolutely disgusting! This is the second time we’ve ordered from here and will be the last the burger was nasty, the pizza we got was bland and cold and the kebab was well not a kebab just meat an salad with sauce thrown in a box an a bit of bread thing with it in a separate bag couldn’t call it a pitta or a naan it was just a thing on top of that the box of chicken was swimming in some sort of watery substance so inedible all in all would never ever order or eat from here again even if it was free and not worth the money so please go elsewhere and save your money!

I ordered food at 18.10 on a Saturday evening and the delivery time said 19.15 which is understandable on a Saturday night plus Liverpool had played at home, but it got to 19.45 so I rang them and they assured me the delivery was on its way! I gave it another half an hour until 20.15 and they tried saying I had already had my order and did I want to order again! It clearly hasn’t been delivered so they then said they would make the order again and resend it. I gave it another 45 minutes and rang back they said their driver had just rang and he would be outside any minute. Nobody turned up! Absolutely horrific service! I couldn’t go out to collect anything as I had a baby in bed! So I ended up getting food from the famous blue star on Stopgate lane and received a prompt delivery!

Firstly the driver wouldn't come up to my apartment because he isn't insured which is ridiculous! I had the chicken kebab with cheese hands down the most vile kebab I have ever had, the chicken was cheap burnt and had a weird taste and smell. The cheese was the really cheap nasty stuff they put on pizzas, only ate some of the fyi wrap not a naan bread and bits of the salad which was pretty much half an onion dumped on top. My partner had a chicken burger the chips were cold and he only ate some of the burger he said it was awful and he eats anything! Avoid avoid avoid will never order here again shocking service shocking food also quite expensive!

Very very poor got ripped off and treated very very poorly ordered a half pound cheese burger, with donnar meat and fries and got a cold fries and a bun with donnar meat no burger instead and when i phoned he was very rude tried to tell me i only ordered a donnar burger but when I said he only had to look at the bill to see that's not the case and why was i charged £4 for only donnar he laughed and said okay okay i will get it to u in 5 minutes in sarcastic tone and hung up the phone!! I still have not received my burger"???

+ +

raspberry      a very late      came cold      the outside      not worth      was cold      very bland      very small      Arrived frozen      the main      Very expensive      kinder bueno      toffee due      the kinder bueno      are horrible      Ice so soft      so much      was cold      still late      still late and cold      all hot      an extra      for fresh      Very expensive      for mediocre      Very disappointing      the wrong      a contact      ‘ thick      was watery      watery and tasteless      and cold      - nice      very helpful      as solid      was late      - first      very disappointed      as good      and finger      was cold      30mins late      how bad      to wrong      is ridiculous      half full      the wrong      the right      the second      so unable      its only      not receive      was late      was stale      Very poor      a regular      of large      Not happy      and missing      half decent      were massive      Arrived cold      properly due      very dry      Cold soggy      was received cold      and inedible      The whole      was so      were green      to yellowish      not very pleased      better quality other      horrendously small      the order wrong      were rude      was so      were green      to yellowish      my food🤬🤬🤬🤬 Delivered      completely wrong      Terrible service and terrible      was soggy      an 90      was cold      cold and disgusting      was late      , soggy      a stale      , whole      was was terrible      15 minutes late      the front      So disappointing      take long      restaurant and a regular      was cold      Very unhappy      have ever tasted was freezing cold      it outside      extremely late      most was cold      was full      Food okay      so bad      was cold      was disgusting      is good      rock solid      was disgusting      Food arrived      me somebody else      Not very happy      m not happy      m not happy Not happy      was horrible      was cold      extremely poor      Oily and soggy      not good      were uneven      properly also not crispy      and plus      the same      from such      Really bad      was freezing cold      was late      was horrible      the same      when i      most expensive      so oily      not warm      is great      was rude      the wrong      was cold      was cold      very warm      was grey      un eatable      were undercooked      not fried      to due      to abysmal      thing 

+
+

The service is diabolical, i have been waiting 1 hour 10 minutes for my order and it has still not arrived and the restaurant are not answering the phone

The contact us on just eat s site is about as much use as an ashtray on a motorbike, it does not allow you to contact JUST EAT !

!

!

!

!

!

!

I have had to rate this travesty just to get this review through to someone

+

+

Was an oil fest

+ +
+ +

Ordered from here twice now. Food was late for the second time. First time I didn’t mind as much as the food was OK. However this time, the food was cold and very poor. Poppadoms were completely stale, as if they was cooked hours before... the naan bread was undercooked to the point were it was just dough in the middle... and the chicken madras did not taste anything like a chicken madras I’ve had before, it was asif it was overspiced and rushed just to add flavour. I ended up throwing most of it away!!

the only thing i can say was good about this meal was the fast delivery other than that it wasnt good at all got the set meal for 2 am still looking for the chicken in the pakoras was just a load of batter the chicken tikka was pink an super sweet an tasted of coconut never tasted tikka like it in my life was disgusting the other meal just tasted of curry powder an the rice was just enough for 1 person only sent 3 samosa for a set meal for 2 i didnt even both trying the veg curry the naan bread was they only thing we ended up eating the rest went in the bin wont be ordering from here again

I've just received my order from Bombay Spices. The restaurant had double bagged my order. The first bag was upright and the second was side to side. When it was delivered it was done so by the handles of the outer bag somthat the inner bag, containing our food was in its side. The two cartons of curry sauce had leaked out, one completely. The food was covered in curry sauce. By the time we'd cleaned it off the food was cold. No one enjoyed it. We each tasted a small amount and none of us liked it. The kitchen worktop I'd littered with food that no one wants to eat. It'll all be thrown away. The only part that was edible was the pilau rice. I've ordered from this outlet many times and haven't previously had cause for complaint. This was awful and has put us off ordering from Just Eat again. DIRE!

Ordered 18:30, ETA 19:25, at 20:05 this was revised to 20:30, arrived 20:57, so it was late for being late for being late. I know things are a bit mad at the mo, and I don't mind stuff taking a while, but it's poor to say it'll be there and it not be, then take 35 minutes after it was due to say it'll be late, and then it be late for that. Food when it arrived was a decent temperature and was pretty nice. Nowhere near the best I've had but defo not the worst. I'll chalk it down to a bad night under weird circumstances and maybe they'll get another crack of the whip down the line.

+ +

positive      was inedible      was soggy      a mutton Jamaican      the plastic      were okay      the frozen      was late      late and cold      very small      the chill      was nonexistent      a few      the worse      was uneatable      were cold      cold and soggy      very much      entered wrong      a veggie      my photographic      Very dissatisfied      not happy      that missing      was ignored      a nice      was wasted      no driver available      a little      Absolutely terrible      were awful      and grey      to mock      a small      was obvious      were stale      barely warm      really poor      expect fresh      from high      a specific      came chopped      its own      the naan      its own      serve other      the 1st      had hot      a small      would have been nice      t taste good      really disappointed      The only      was cooked fresh      days old      days old and reheated      was dark      3 separated      was ooooookkk      Nothing special      the only      the only ones open      arrived cold      so bad      was cold      cold and stale      cold and stale Called and left      of compliant      Bad ice cold      the missing      was cold      a hot      , rude      were cold      was poor      was cold      taste fresh      , unable      arrived was soggy      its correct      Food cold      was bad      and pink      cooked proper      could taste uncooked      uncooked potato such      and disgusting      is decent      are nice      are inept      inept and rude      was warm      warm not hot      not even willing      the missing      was terrible      one long      was cold      cold , soggy      , soggy      very disappointing      the full      was late      late and cold      was dry      so undercooked      were pale      the chicken old      the outside      but undercooked      was cold      Food cold      literally frozen      is ooor      , last      not bad      my second      my first      very dry      arrived cold      was cold      a very funny      to be honest      very poor      was disgusting      was freezing cold      a five minute      away so should have been warm      was warm      a million years Arrived      50 minutes late      was cold      with other      sent correct      but frozen      all disgusting      a full      delivered was foul      , appalling      out of appalling      As usual      , great      and good      

+
+

We ordered 2 days in a row

the first day

The service was great, as was the food

The second day, the driver lied saying he called outside the house

No call, until he got back the shop, to being g the food back to the same street! No apology and when he had to come back, he still took his time about it

Sadly wont be ordering again

+

+

L

+ +
+ +

Phoned up to chase up order when it didn’t arrive after having already used the app to say it was delayed. The guy was ok said food on its way waited another 15 minutes. Called up to be told my post code was incorrect, informed them I used just eat regularly and had not had this issue before. He said he needed to call the driver with my post code as he was saying this the driver pulled up. Clearly my post code was correct, such a shame as the food was lovely . Customer service leave a lot to be desired.

+ +

a huge      was cold      cold , dry      very little      Not very good      all helpful      us a £4      the second      the same      very poor      me a new      extremely unhelpful      was outside      30 mins later      the wrong      was freezing cold      Absolutely awful      Absolutely awful Wrong order Cold food arrive      an understatement Spring      turned up wrong      the the right      just missing      from frozen      very stale      of bland      was full      was adamant      a box full      was cold      cold and disgusting      as disgusting      the initial      extremely late      extremely late , cold      of read      read reviews first      was dry      my drinks and no answer funny      the last      them and the last      After numerous      very fatty      my opinion undercooked      The large      of hard      of runny      absolutely awful      I wont      from Dianas      was inedible      were cold      cold and soggy      very very greasy      an extra      was cold      probably due      's late      untill 6:40pm      It all arrived      a good      40 minutes late      stone cold      the price shocking      not worth      were unavailable      was only      too busy      would be 5-10      me fresh      again Wrong      Not satisfied      very small      was hard      that burnt      my poor      with out      was inedible      how bad      how bad      that bad      The food arrived      30 min late      ( cheesy      ordered Missing      came cold      the way text.Food arrived      was wrong      was late      Very rude      Very rude and bad      , wrong      , cold      , cold      and poor      did n't show up tried      sometimes not good      and miss      incredibly rude      was disgusting      barely inedible      80 years old      the entire      was horrible      were greasy      the last      was cold      when delivered      didnt have      was v      the full      was dry      was oily      was tasteless      and arrived      So greasy      So greasy and full      of fat      a long      was fine      although arrived      as free      not true      ordering several      that £2.50      is unacceptable      this further      was disgusting      was manky      were cold      was disgusting      The only      greasy and heavy      was cold      very oily      too cold      Very poor      not sure      just eat are toothless      was 

+
+

In the nicest possible way

This food was an abomination

Every single dish tastes like perfume

Just the smell coming from the bag is revolting

I have never ever thrown up after eating food

It may be too late to call back now but I can gaurentee I will be at the shop tomorrow to get my money back and you can take your food and eat it yourself

What an embarrassment to Chinese food

+

+

still not here! 30minites late!

+ +
+ +

Placed order at 8.06pm and delivery was meant to be 9pm. Phoned up bout 9.30 to ask where it was, told about 10 minutes away and that 'you're miles away love'. Then maybe change your delivery boundary if it is an issue? That isn't exactly the issue of the customer. Food arrived at 9.40 - two missing items - chips and curry sauce. Didn't bother phoning to ask for it to be sent as took so long in the first place. Food was quite cold, guess that's because I'm miles away ha ha. Won't be bothering again.

Disappointed. Ate from here a lot recently, enough to give them a glowing review a month back and to recommend the place to a lot of people. Tonight I ordered at 6.40pm & the food arrived at 8.30pm. I was given a delivery time of 7.30pm do called at 7.50pm & they said my food hadn't even been started yet. The sausage dinner was ok, as was the Fried Rice & BBQ Sauce but the Char Sui, wonton noodle soup was pretty much inedible. The Portion size for £6.50 was small with hardly any broth. The noodles were like mush & the char sui very fatty. Hoping it was just a bad night.

Average and overpriced One of the more expensive Chinese takeaways locally. Portion sizes good but quality lacking. Order was very late and no preemptive communication from the restaurant - bad service. £7 for chunks of chicken in a greasy undercooked batter was a terrible waste of my money. Worst 'chicken balls' I've ever had by far. Chilli chicken was reasonable but expensive compared to others I've had. Beef in black bean sauce much the same. Could have had better food or less money elsewhere, probably would have taken less than 75 minutes to get to me as well...

+ +

old      was cold      cold and soggy      was cold      a long      Got the houmous      without houmous      and dry      were good      the second      in general      stupidly late      the good      was nice      was late      did arrived      not fresh      Absolutely disgusting      Shawarma wrap was freezing cold      has been good      be consistent      was disgusting      was late      got rude      from restruant      restruant again absolutely terrible      was late      a very rude      was cold      a deconstructed      after another15      a new      was cold      cold and horrible      20mins late      not hot      was cold      was late      Very bad      Not helpful      have previously had good      were cold      cold and soggy      extremely oily      was due      was cold      Very long      again 😵      very poor      30 minutes late      was cold      ’ s      hours and 20 mins late      just eat intervened      very bad      the food cold      cold and dry      30 minutes later      not edible      t the 1st      Ordered £25      so greasy      not fresh      was cold      the same      the same very disappointing      and only      food cold      as useless      did n't have enough      really dark      very old      very old oil Missing      n't good      was un-eatable      so busy      to deliver simple      30 minutes late      's live      the next      were nice      extremely late      is ridiculous      arrived 45mins      the last      something extra      was cold      that good      a further      was late      were cold      the same      as previous      , tasteless      Given wrong      had ordered Southern      of tiny      A real      was late      the 3RD      is good      was cold      was cold      Very oily      was tasty      A little      A little bit dodgy      called multiple      alright Ridiculous      the first      every single      is lait      always barely warm      was late      tasted stale      s fine      was good      30mins late      was cold      were soggy      the wait      was cold      some missing      Food arrived      arrived late and cold      but cold      of recycled      for local      not good      15 extra      40 minutes late      late and cold      was late      to spicy      to oily      20minutes late      was cold      very nice      wrote clear      the last      to be honest      is excellent      a very poor      

+
+

Heard great things about this place but was very disappointed

Poppadoms were so burnt they were inedible and went straight in the bin

Chicken tikka chat was okay bit bland

Mixed biryani wasnt very nice again quite bland, quality of meat wasnt great either

Curry sauce to accompany it was lovely though

+

+

3 hours 20 mins still no food

+ +
+ +

My 15 yr old daughter suffered a severe allergic reaction from this take away. 2 phone calls were made, one prior to placing the order and once the order had been placed & allergies were noted in the notes & khans takeaway still took no notice & contaminated my daughters food. If a business says they can cater for nut allergy sufferers then this is what I would expect but clearly seeing as none of their chefs don’t understand about cross contamination I would suggest hiring people with food health & safety qualifications & basic understanding of preparation of food. I will not be ordering from here again & all my daughter has received after nearly dying is a sorry from the staff. Food went straight in the bin & any professional establishment would have said & done a lot more. Not happy at all, actually disgusted that human beings can be so cruel & lack empathy at a time like this. You should all be ashamed of yourselves for nearly killing a 15 year old child.

Furious with the service from Khan's. We ordered from the takeaway at 5:30pm for 8:15 delivery to try and avoid the rush due to New Years Eve. After trying to contact the restaurant several times, I received a text at 8:45 saying the food would be here at 9:25. We were happy to be patient as we understand how busy they must be, however the food has still not been delivered, is over an hour and a half late and we have had no contact from the restaurant. Appalled and will never order from here again.

So we were told the order would arrive an hour after we ordered which was fine but then it didn’t arrive until another 15 minutes after that after me having to chase where it was they are literally not that far. Food was piping hot and delivery driver was nice however we specified that the food was to have no nuts unfortunately this wasn’t adhered to and my partner started having issues about 5/10 minutes after starting to eat. Called them to ask if there were nuts in the food and was passed onto the chef who very proudly told me that they use the very best whole almonds. Very good but not when you have a nut allergy which then resulted in us having to call an ambulance and my partner having to go to hospital because his eyes nose and throat were swelling and closing and he couldn’t breathe. The fact that they didn’t pay attention could have potentially killed him

Butter chicken was nothing like butter chicken tasted exactly like korma, it also contained no chunks of chicken it was full of what can only be described as what looked liked leftover scraps of chicken. We ordered 2 & 1 wasn’t even touched. Poppadoms were also wasted as ordered the onion & mint relishes & they were so small they only did 1 person for 1 poppadom. Onion bhajis were over cooked & we could hardly cut into them as they were so hard. Ordered from Khans before & it’s always been nice, total waste of £45 this time apart from garlic chicken tikka which was nice. Unfortunately won’t be ordering from Khans again based on this delivery.

Don’t order from this restaurant!!! 4 hours late two months ago. The 1 star should be 0 because we obviously did not accept the food and refused to take it. just eat won’t give us a refund and it was £65. Rang khans numerous times and every time they just laughed down the phone at us. Disgraceful service. Was a family meal in which my mother paid. Her, my dad and my sister work for the NHS, working all week and just wanted a nice meal. Cannot believe the attitude of the people who work in this place. Absolute disgrace, would not recommend eating from a place who deliver 4 hours late and think it’s not a problem. Also have a bad habit of taking the phone off the hook when they know they have too many orders waiting. Should be ashamed of yourselves khans.

I waited 50 mins for my food when it came it was freezing and as I picked the food out the bag it was just drenched in greese were the curry had split I rang them 2 mins later and asked them to take the food back and give me my money back they said the driver would be back in 15 mins to then the driver ringing me to tell me I'm not getting one I asked to speak to the manger and he refused to get back on the phone to me like they was desperate to keep my money absolute shocking customer service first and last time ordering from this place

The food overall was nice I would say if you added salt to the rice as it was quite watery, naan bread was nice except was wet and damp due to the packaging , the chicken overall was nice however I would say it needed cooking a-bit more as some bits was more chewy, the worse bit of the service was the restaurant service I clearly stated to call my phone when here thank you , however the delivery man ignored that and as we didn’t hear him when he arrived straight away he was rude when I opened the door he tutted and said he was going to go and just gave me the food , in all of this I said sorry 3 times to him

+ +

in normal      was bad      finally arrived      was greasy      them a few      , first      first time it arrived      arrived late and cold      was available      did arrive      the large      that large      the two main      The Southern      did arrive      barely edible      not sure      really greasy      seem fresh      was cold      were rude      Very unprofessional      Old refried      it cold      to re-order      another half hour later      was disgusting      a few      tonight estimated      2hrs 15 mins later      was inedible      and forgot      20 minutes late      was cold      cold and tasteless      no next      very bad      not right not healthy      in separate      poorly bad      inside pitta      it together right      was cold      was awful      all lumpy      lumpy and slimy      were dry      were nice      terrible , terrible      terrible , terrible , terrible      very dissatisfying      very dissatisfying and disappointing      finally arrived      was tasteless      really dry      late and cold      as previous      been hot      a bit late      , arrived      and poor      very angry      how late      this next      Food arrived      tried multiple      20 mins later      tasted horrible      Very disappointed      Absolutely terrible      Absolutely terrible , ordered      not helpful      was cold      Used to be s      hill cold      it finaly      barely even warm      was shocking      could give minus      is abysmal      not happy      this service again,regards mr      and cold      a good      the bad      very soggy      the last      is disgraceful      was hungry      arrived cold      Absolutely disgusting      still hasn      the expected      n't able      app...not acceptable      only 1mile      was late      late , average      in future      40 minutes later      later Food arrived      the second      now 56      now 56 mins late      16 mins late      the online      43 mins late      cannot be honest      when busy      to be responsible      for online      only ordered online      is bad      absolutely rotten      very poor      on Pattie      Peas cold      was soggy      not crispy      t oick      the other      the same      above Second      very late      as unrecognised      " live      not free      about warm      the missing      at other      are many      extremely late      completely incorrect      was late      , poor      always late      turned up cold      cold and missing      always nice      is disgusting      

+
+

As an Indian living in the UK, this is easily the most horrible Ragada Patties I've had

All the spices are wrong and worst- there was no potato patty at all!

Unnecessarily loaded with peas with just about two drops of tamarind sauce

The flavour was completely wrong, had to throw away most of it!

Completely disappointed

+

+

Wouldn’t deliver my food.

+ +
+ +

Food items 90 minutes late, even though pre-ordered. Cold. terrible customer service from your team as I've tried to pick this up on the online chat multiple times. I’d like to speak to someone about being fully reimbursed for my order. Also I’d like to know why your staff thought it acceptable to blame the restaurant, who have also had a very hard year, the last thing they need at such a time is an organisation like yourselves throwing them under the bus. I called them and they had the order waiting for one of your drivers for 90 minutes.

+ +

'm sorry      my own      pitta ordered      that not fast      very very very very bland      was black      black and uneatable      again very black      40 minutes late      Website stated      order 30 minutes late      looks good      the order late      when unwrapped      “ large      received normal      Very disappointed      was disgusting      freezing cold      cold not happy      cold not happy very angry      on public      is terrible      were rude      his bike absolutely joke      NEVER USE      were soggy      soggy and cold      t taste fresh      very poor      45 minutes late      50mins late      in 5-10      too late      10.15 original      was cold      was dry      dry and tasteless      generally 90      Tried to call multiple      not able      a bit salty      was soggy      and pita      not only do i go hungry      are busy      Still hadn      all The food arrived      30 minutes late      was cold      with soggy      also soggy      very disappointed      45 minutes extra      me a wrong      is appalling      was good      is ridiculous      a bit long      're short      everything wrong      , disgusting      was like      were cold      cold overall VERY poor      again RIDICULOUS      The baked      Very disappointed      the undelivered      was rotton      , last      were undercooked      so couldn      t eat      not enjoyable      , poor      in wrapped      ordered belgum      , second      and third      I wan      is cold      freezing cold      was disgusting      'm absoulty      again @      wa sun edible      were cold      Chips dry      better brown      lamb kebab shocking      considering small      meat Mayonnaise runny      Very disappointing      was late      late and cold      cold very disappointed      was cold      was warm      came deconstructed      was annoying      t fancy      my own      the last      was wet      were okay      their store their great      was cold      not possible      phone engaged/user busy      are good      n't arrive      too early      was soggy      Really not happy      the past      is average      is unacceptable      not very helpful      the last      ( original      their revised      Food arrived      really good      the last      20 mins later      arrived cold      Called just-eat      Food arrived      , little      not very hot      is 20mins      a bad      is bad      was only      would be late      was cold      so soggy      was 

+
+

Won’t order here again

I understand on a Saturday that the place will be busy but some general customer service and setting expectations would go down well

Our food was 1 hour late and cold

No chips that we ordered and when we rang to tell them we where told we would get free chips on our next order

Not good enough

They agreed to send the chips but as of yet, (50 mins later) no chips, not that they’d be touched now like

+

+

I still haven’t received my food

+ +
+ +

So disappointed! First time trying here & after reading the reviews was really looking forward to it. Ribs were absolutely disgusting, literally onne edible one out of the portion. All the rest were just fat or gristle 🤮 chips were unbelievably greasy. Kids liked the sausage & nuggets & the curry sauce was nice. Complete waste of money as most went in the bin. Also ordered collection for or 5:30 & received text just after 5:10 saying it was ready to collect so the food was sat round until I could get there for when I ordered for

Really disappointed with tonight's order. Asked for a chicken fried rice, received king prawn fried rice. Asked for 1 x plain chicken burger (daughter doesn't like mayo) and 1 x chicken burger with lettuce and mayo on. Both had lettuce and mayo on. Chips were cold and didn't taste as though they were a fresh batch and the deep fried combo was well overdone and dry. The staff were manic when we went into collect, looking for missing orders and trying to keep on top of all the orders that were still flying in. It seems that every time we order there's something missing or incorrect. Never gave feedback before but sadly, I don't think we'll be ordering from there again. Seems to have gone down hill since Yung left a couple of years ago.

Absolutely shocking! We have ordered from her before usually very nice tonight I order mixed meat noodles, char siu Cantonese and chicken Cantonese. My husband enjoyed his noodles but me and my daughter ended up eating beans on toast our meals were disgusting! Char siu was just fatty and rubbery the chicken was gristle and rubbery and the chips must of been warmed up at least 3 times I wouldn't of fed them to a scabby dog. Complete waste of money! I'm so disgusted you find it acceptable to deliver food at such low standards and expect humans to eat it.

+ +

rude      were good      good but cold      of pickled      was 45      the order wrong      was cold      was late      had last      was good      cold booked      very greasy      was cold      ’ s      usually fab      45 mins late      was old      really poor      is late      always arrive      will always be dry      wasnt happy      the wrong      very bizzy      was 45      45 mins late      very rude      very rude and unhelpful      a few      was terrible      is good      absolutely shocking      the last      this long      to advise long      were soggy      not hot      was freezing cold      my order wrong      is good      was late      two spicy      is awful      a vegan      are busy      about next      30 minutes late      n't bother      never other      at 19:05      the next      the next      a 10min      my house or a 3min      of collected      rather rude      Very disappointed      30mins late      n't bother      was unbeatable      these new      being late      just rude      then was cold      a complete      an additional      the restaurant multiple      was cold      not particularly flavoursome      pushed back      , cold      was cold      here Bad      from fish      with other      taste great      not worth      's mic      just white      pretty much      with big      Absolutely dreadful      were cold      was hot      was cold      was funky      So dissatisfied      was bad      is 3rd      2 hrs      on Incorrect      n't very hot      Called numerous      how long      or complimentary      actually very tasty      were mushy      really gross      items wrong      an additional      and speak      15 minutes later      The last      Delivery time long      actually disgusting      Not good      'm disappointed      very hungry      very polite      a further      20minutes late      was cold      dianas but not anymore Order      's ridiculous      20 mins late      an apology/      25 mins late      , stale      hard pitta      somewhere else      was cold      were busy      was disgusting      was cold      arrived warm      was like      not nice      were chunky      only good      chicken and some battered      looking oily      ) horrible      were nasty      too spicy      too spicy and hard      and come      Order £25.50      will be £27.40      the original      got frustrated      was £25.50      was cold      and poor      20mins later      Not great      got cold      was due      

+
+

Only because I can only rate it one star, it's got one

It would get less

They got our order wrong and then told us to read the menu correctly and said we'd got it wrong! We paid more money for EXTRA, AND THEY DIDN'T EVEN DELIVER IT

Even after we tipped the driver the cheeky idiots, as a woman calling they continued to shout at me and pass the phone around until my partner spoke to them and they backed down

Do not order, do not waste your money

Ridiculous!

+

+

Food dryed out

+ +
+ +

Unfortunately I have to write this review ... I thought we’d try a different takeout for a change.. the order came to over £42 so wasn’t cheap... it arrived half an hour late ( took nearly 1 hour twenty minutes 😢)and the curly fries were cold.... phoned the restaurant only to have an argument with the man who told me there was nearly 50 orders... wouldn’t go here again ... burgers were nothing special... expensive for what I received... VERY rarely right a bad review but wouldn’t go here again! Sorry... but wouldn’t recommend because of the service 😢

Ordered food on Sunday, got confirmation that my food is on its way, its now Saturday (6 days later!) I'm starting to give up hope that it's coming... Normally nice food but im wasting away here.. haven't eat in 6 days... on the plus side ive dropped a dress size! So anyone looking to diet and lose a bit of weight for the summer i would deffo order from here! (I had to do this review on a previous meal we ordered wich we got and was sound as they later cancelled our order so we can't review 😂sneaky swines)

Placed my order, Waited till it was accepted, (which I always do) do I know they have seen it and it will be delivered at a certain time. 20 mins after the expected delivery time I called to ask where the food was (understandably) I was then told order wasn't sent out or even cooked because I didn't answer my phone (I hadn't had a call) Because they didn't know if I wanted it, I explained if I didn't want it I wouldn't of ordered it. They apologised and said the food can be delivered In 20-25 minutes so I agreed as I was just hungry, I was then asked to call them back And prove it was my number even though I had just called them, And then tell my my order. I asked them if they can not bring up my order and he refused... In the end I left it Usually really nice from this take out feel disappointed at the fact they tried to blame it on me, and then where to lazy to get the order up online.

+ +

were assured      would still be hot      n't surprised      10 minutes late      was cold      were cold      cold and dry      your operative      away maximum      always had good      but last      not nice      was late      the completely wrong      20 mins extra      no grilled      no grilled wings only      this before      cannot eat fried      my full      n't bother      25 minutes late      the wrong      the right      the right order delievered      had to go to shop next      meal second      a large      was fresh      fresh and hot      the other      was cold      Not happy      40 minutes late      therefore cold      again Large      n't fresh      were mixed      a few      Very shocked      where cold      was fantastic      an off      Please be aware      a long      was cold      Food average      was dismissive      to other      the next      of canceled      n't fresh      40 mins late      was incorrect      obviously marked      how late      not ordering direct      late cold      late cold and soggy      was cold      cold and horrible      the normal      40 minute      far too long      ( much      arrived cold      actually awful      me my requested      was third      a loyal      n't read      was cold      pretty good      delivery Worse      the order wrong      the pitta      hard so was unedible      not sure      8:50pm terrible      the first      came cold      the first      were dry      in paid      , overall      a very regular      very bad      the past      tasted nice      to short      me hot      for free      free charged full      when arrived      till past      was sweet      with overall      was cold      a certain      was cold      food arrived      the revised      was friendly      friendly and polite      everyone else      , poor      arrived cold      hours food cold      was freezing cold      a good      a build your own      of 2 x      2 x      with 4 little      our first      of proper      and decent      is prompt      is hot      is great      the case last      the mushy      not great      not ideal      for wrong      in helpful      was rude      was 20:25      it fine      do n't have hot      was sad      of funny      usually really good      very disappointing      's long      too long      the plus      was nice      n't fulfil      n't hot      not very 

+
+

Abysmal service

FIVE HOURS after I placed the order I am still waiting for the order to arrive

After making several calls the only explanation I received was they were busy, FIVE hours later I have received no refund or apology and am still waiting for my food to arrive ! boycott this restaurant at all costs

Absolutely DISGUSTING service

FIVE hours is simply unacceptable !

+

+

Sorry to say, I didn’t like the taste.

+ +
+ +

Food was a little late but we kinda expected that with the COVID-19 going on. Can't complain about the food at all except for the meat portions within the curries were small. We did order x2 large Tango Ice Blasts which came to £10 but these were never delivered. Called the store & spoke to staff who asked for my address but 2 hours later they still haven't arrived. Kids are now in bed & the way Just Eat works it's nearly impossible to get a refund. Have called the restaurant again but had the same result, I'm told it's on it's way but I'm not holding my breath. It'll be syrup water by the time it gets here tomorrow 😔

As a first time customer to this restaurant I won’t be ordering again. Placed an order for £55 which had an original wait of 1hour 30minutes. I haven’t a problem with that, except it’s now 40minutes past that time and still no sign of it even though it is showing as delivered. Tried calling numerous times but they’re ignoring their phone. Tried the option to get an updated time but again that was ignored. It’s now 8 O’clock, I’ll have to wait for my £55 to be refunded and theres now no where to order any decent food. I wouldn’t even mind the extra wait if they had the common curtesy and decent customer service to answer their phone or even call me to update the time. Never again.

Ordered two pilau rice and two butter chicken. Stated mild 4 naan bread - recurved 4 naan two half filled pilau rice and one butter chicken and one a don’t no something spicy - called just eat refused to deal with - called restaurant refused to replace and called me a lied told restaurant food still in container come collect it refused to do so told me I got what I ordered when reiterated food still here ok container come collect she refused again and called me a lier . I what Annoys me is good as always been perfect asked for owner who I’ve spoke to before and had food delivered by him always spot on apparently he’s away and the restraint as gone the pot .and no one is interested in dealing with me issue to hand. Charming service and pathetic management

Ordered the food at 6:45 and was given a delivery time of 8pm. At 8:20 I called and was told it would be here in 15 minutes. After 20 minutes I called back again and he still said it hadn’t left yet. I told him that if it wasn’t here by 8:45 I didn’t want it and he hung up on me. I called back and told him to cancel the order and that I now didn’t want it. At 8:55 the delivery driver still turned up and had no idea that I had cancelled it. Really really annoyed as I had eaten from here before and was really happy with the food but after that customer service I will never order from them again!

I recieved confirmation that my order had been accepted at 17.54 with a delivery time of 19.25. I wasnt to bothered about the wait as it was a Saturday night. 19.25 came and went and at 19.49 my husband rang the restaurant to find out where our order was. The restaurant wasnt bothered about our late order and after having to give our address multiple times he was just told the driver left 10 minutes ago. It was a further 10 to 15 minutes later before we recieved our food By this time we was expecting the food not to be hot and we was right. You expect the driver to multiple deliveries however i only live 5 minutes away from the restuarant. I expected the delivery man tonmaie multiple deliverys however it should be done so everyone recieves hot food! The chips and naan were both lukewarm and soggy, the rice was cold and the poppadoms broken into several pieces. The curry was hot enough and was nice.. but everything else let it down. Just eat themselves asked if we wanted a refund, i would have accepted however they couldnt guarantee we could keep the food and at that time of night i had no choice but to feed the children. Just eat also rang the restaurant to see if they would compensate and again ive had no choice but to accept the £5 voucher. I certainly won't be using this on Jasmines restaurant.

Ordered food at 6:20pm for a 7:30pm delivery. Once the card payment went through I got a notification to say the restaurant had pushed the delivery back to 8pm. Not great but not the end of the world either. 8pm came and went. It got to about 8:20 so I decided to call the restaurant... ‘user busy’ so I called again and again and again. It was like they had unplugged the phone. Delivery arrives just after 8:30pm - driver nice and very apologetic. Brought the food in and opened the bag, they’d forgotten the two portions of onion bahjis we had ordered. Called the restaurant... ‘user busy’, called again and again and again and you guessed it; ‘user busy’. Positives: food quality was good - tasty and plenty of it, great value for money but delivery times and an inability to communicate with the restaurant really lets it down.

Unfortunately, the whole experience was disappointing. It took ages for the food to arrive but the delivery driver was very apologetic when he eventually turned up. Also, the food was quite poor. Maybe it was just the time of night (late) but the pieces of chicken and lamb were extremely chewy and tough. I also ordered poppadoms and I'm used to most restaurants supplying a small raita dip and cucumber and onions with these but they came on their own. Maybe this is normal for this restaurant but based on my past experiences, it was a bit of a let down and I wouldn't order again.

Order was placed for 19:35 delivery (50 mins). Was put back by 25 minutes. 10 past 8 get a phone call asking me to come outside. Go outside and call the driver that is not there and he asks me if I am sure I am stood on my front door step. He had mistook 27 for 21. Clearly not looking at the delivery instructions as 21 looks nothing like my entrance or house. Direct the driver with the fact same notes as I have left on the order. Go to the main road three times in the rain and wind (My house is quite set back on a private estate). Use Google maps to send him a screenshot of the entrance to my property and also text directions. Over 15 minutes later still no sign. Call the restaurant telling them there is still no sign of food. over 10 minutes later I have to call back to ask where the driver is. They tell me someone else is personally on the way with fresh food. If the new driver has left with fresh food after just over 10 minutes, why it took over an hour and 20 minutes for the first order to get to number 21 not 27 is beyond me. New driver had no problem following the delivery instructions so they couldn't have been unclear to follow. However a small apology, not offer of compensation or even a free side order was surprising. Then to be told they had no coke left so had substituted it for Pepsi without asking me if that was okay (Despite the numerous conversations regarding it's delivery). I would have expected a phone call before substitution, or the item to be free in case it was unwanted, which it was. The hot wings were lovely, however the Mixed Madras contained only three pieces of chicken and three pieces of lamb. I am the type of person who prefers quality over quantity, however I don't want to search through a tub of sauce with no depth for 6 pieces of meat, especially considering there are no vegetables in it either. It was clear the food was rushed.

Food was really good, pity about the service/delivery time as that was shockingly bad and means we won't use them again. Initial estimated ASAP time (when ordering at 6.09pm) was 7.05pm - which was about the longest I would have wanted to wait. Eventually, Jasmine's gave a confirmed delivery time of 7.30pm (which if I had known pre-ordering, I would have looked elsewhere). Food was not delivered until after 8.30pm - about 2.5 hours after ordering. Phoned up once at 7.50pm asking where it was - was told it was out for delivery. Didn't turn up for another 40 minutes. The place is 3 miles from my address. Not good enough.

This meal was ordered at 7pm, estimated to deliver by 7:50. By 8pm we were getting concerned and double checked - it had been revised to 8:10. So we waited further and by 8:30 it still hasn't arrived. We rang to see what was happening and got a half hearted apology. After the food finally arrived, almost an hour late and having taken around an hour and forty minutes to deliver, we were rang ball whilst we were eating, only for them to ring is back to say exactly the same thing! The food itself was only okay, apart from the garlic nann which to be fair was really nice.

+ +

helpful      a good      the wrong      arrived cold      'm usaly      day terrible      was cold      was wrong      n't wrong      a new      was due      was cold      Very slow      was peak      quite cold      very rude      room next      Do not recommend      arrived 30mins      n't hot      was fine      the 1st      not impressed      nearly 11pm      very unimpressed      not happy      I ordered      a large      my next      over in black      black and white      was cold      cold and dry      Fairly nice      , good      the expected      a 1.45      is crazy      just eat      was nice      a nearby      was late      was cold      After several      was told was sorry      again Not good      to rude      are good      the second      of normal      are busy      another chip shop next      and cold      30 minutes late      was burnt      burnt and dry      was dry      the chips cold      was different      the past      me a estimated      the multiple      a 15minute      was terrible      were soggy      or proper      're busy      more drivers....customer      customers loyal      take long      the drinks free      the 3rd      a total      2 arrived      would be sent next      for free      a different      the order next      " free      the first      'm unsure      is unacceptable      would get free      free drinks next      20 minutes late      the first      1 minute      said sorry      them the next      this terrible      ' good      the correct      am allergic      Not happy      is wrong      wrong Missing foid Horrible      Horrible horrible horrible      sooo hard      was old      was cold      will say local      the second      my order wrong      was wrong      the 3rd      the same      the last      that tasteless      tasteless and watery      so rude      was tiny      totally wrong      food missing      items Missing      the wrong      was mixed      elses Missing      No large      all small      was cold      completely wrong      find hard      had received was getting cold      An infuriating      a complete      of basic      a full      was unacceptable      NOT very hot      Forgot many      was small      very very very dry      was cold      burger no sweet      in sweet      the correct      FREEZING COLD      was cold      cold and wrong      going wrong      than estimated      arrived cold      give correct      correct order Unacceptable      no fried      

+
+

they tried to deliver over an hour early when we weren’t in, then when they did deliver at the time we specified half was cold - feel they reheated everything but not properly so didn’t feel safe to eat

Nothing tasted great, the Keema naan had about a teaspoon of meat, samosa was soggy

Rang to complain and they kept fobbing me off with excuses, none of which were very believable

Very poor customer service, food not good, worried about hygiene

Would never order from here again

+

+

Stale food

+ +
+ +

Food was freezing forgot some items all had to be warmed up in the microwave was really late said it had been delivered but had not turned up my partner phoned up and they said it had been sent out the free garlic bread was like a biscuit base worst food I have ever had from hear this is my favorite Indian takeaway by far but so disappointed by the food standed and the time and missing items cost 35 pounds all had to be warmed up plate by plate witch ment we never a ate at the same time hopefully it was a one off but just eat were helpful when I spoke to them

Got a text from the delivery driver to say that he had arrived, he hadn't so I was waiting for him outside for 7 minutes. Order incomplete, when I phoned the driver to explain he hung up on me and then when I called the restaurant, after they hung up on me and I called back, they told me they couldn't refund it back onto my card only if it was done from a personal account so I would have to pass on my bank details because it would cost them two pound to send the delivery driver back to me with a refund. Offered to send me replacement but would have to wait and by that time rest of food would have been cold. Also sent me the wrong naan bread. Food was mediocre at best. Wouldn't recommend and wouldn't order from them again!

Appalling. My jalfrezi wasn't possible to eat. Level of heat unbearable even for my Asian friend who tried it with me. Level 'hot' unspecified, but definitely too hot even for people who eat that kind of food every day. Called to the place, they offered kebab instead and I am vegetarian. For extra £2 (on the menu the same price!) wanted to charge me to change for rogan josh. I refused. This food was supposed to be for my lunch today and I cannot even make it better with neutralising stuff. Its unsafe and can trigger stomach ache for sensitive people.

my food came extremely early which I found strange, I ordered it and 10 minutes later my driver was calling my phone to tell me he was on his way, hate to be negative but the full order apart from the poperdoms went into the bin it was apsloutely vile I got a lamb khari and it taste like chunks of lamb with tin tomatoes no taste what so ever, I tried one chunk of the meat and stopped eating as it taste weird and off.. so for my dinner I had 2 poperdoms! a bit of a let down as when your spending £12 on a meal for one you expect the food to be edible. would not recommend anyone to order from this place 🙁

+ +

for them.Second      is cold      just eat      service The main      was late      late and cold      was cold      , cold      Not good      completely cold      was cold      the rubbish      was overcooked dry      dry and luke      cold☹️☹️☹️ Food cold      Very oily      to much      came super      together really messy      the 1 litre      CHIPS AND POPCORN      Not even enough      my entire      completely wrong      One persons entire      , other      the order wrong      than advised      not ready      a £47      in such      and burnt      2 extra      the extra      pepsi max not full      my regular      got charged      no additional      i payed      The whole      absolutely ridiculous      has been taken Wrong      and cold      very cold      m very unhappy      a full      so outrageous      ’ s      no simple      the bin terrible      , large      fries and 1.5 litre      paid 17.99      , constant      my drink Awful      your order right      your order right Old soggy      was hard      hard and stale      all ripped      it wrong      said checked      Very bad      ca n't do much      A whole      a refund abhorrent      8.15pm Food arrived      do n't care Wrong      order cold      was late      item and wrong      was cold      was cold      cold Absolutely inedible      freezing cold      all incorrect      was cold      the wrong      3 items Second      of recent      had missing      not here unhappy      Completely wrong      Received cold      was hard      hard and dry      me the wrong      very cold      very cold and dry      the bag heavy      Stone cold and all wrong      Food cold      Food arrived      The whole      was terrible      terrible and unsatisfactory      receiving wrong      was halve full      full and cold      2 hot      so dry      so dry and cold      all and all disappointing      all and all disappointing order Missing      not good      Strips undercooked      Tried many      was cold      cold and disappointing      someone else      a full      , 1st      is super      the wrong      ’ s      the exact      & stale      Awfully late      never even arrived      hours late      at 10pm      was cold      slightly wrong      , sent      the first      not helpful      was cold      chips and salad Wrong      than scheduled      arrival Missing      AGAIN Was late      my order wrong      is 

+
+

Original delivery time was pushed back by 20 mins, and the food didn't arrive until 20 mins after that

I chose 30 minutes late above to split the difference between "later than originally said" and the revised time

Second order via Just Eat in 2 weeks (different restaurants) that has been massively later than advertised

At least Landmark bothered to notify us about the first delay

+

+

Awful!!!!

+ +
+ +

Driver came 35 minutes earlier than the estimated delivery time showing on the app. He called me which i missed. Then after 5 minutes he called me again and started shouting and shouted — come fast, very fast. He was very rude over the phone. I ran to my gate and asked why did not he call me if he came 15 minutes earlier and why did he come so early. Again he started telling thay he came more than 15 minutes ago and could not reach my phone. Very dissatified with his rude and poor behaviour. Not ordering from this restaurant any more.

Absolutely disgraceful service. New to Liverpool for a couple of nights with the family, ordered food from this places only for the driver to call us an hour later and ask if we would meet him on another street to pick the food from him!! If I’m paying for delivery I expect my food delivered, meeting at the door yes, a different street no!! I then called the restaurant to inform them, was told someone would get back to us. Now they are refusing to answer my call and 1 hour and 40 mins later we are still without food, having to order again from somewhere else. Ridiculous customer service, Highly unprofessional!!!

So we order lots from you this time it took 1 hour and 50 mins, i aplreviate hard at mo but we paid 60 pounds and had to pay for delivery, also the 3 quirn curries we not quorn they were a horrible sausage substitute, items missing, and standard not up to par very watery curries, it being nearly 2 hours from order in receiving it and items incorrect and missing and other point i would like some compensation, chatted to just eat but they say you have to mark the order as delivered as they cant do anything, tried ringing 3 times no answer not happy atall

Was an awful experience. Placed an order at 18:40. At the time it said there was a 40 minute wait. The wait time then changed to an hour and a half without my consent. At 20:10 it didn’t arrive. Eventually I contacted just eat and was told it was en route and would be with with me at 21:05. After the restaurant calling me and saying I couldn’t get a refund because I’d paid up front the food finally arrived at 21:45. It was stone cold and the bread and bhajis were damp and soggy. It was awful service.

The food itself arrived half an hour late, with the order left unfulfilled. The biryani we ordered didn't come with any sauce, and the chicken tikka masala didn't arrive at all. There was a rice in its place which wasn't really of any use. We called numerous times over the course of an hour, and even when the line wasn't busy, our calls were ignored. While the current circumstances are challenging, especially when it comes to delivering food on time, it doesn't excuse our order being both incomplete and uncompensated.

We never deviate from our favourite but due to it being closed we decided on another nearby... never again. Food was ordered at 20:20 for the delivery time to be 21:55. That’s fair as it’s Friday night and we appreciate that due to current circumstances that it may take longer than normal. But even with this extended delivery time, the food was late and arrived stone cold. My main was extremely watery and completely bland and my partners jalfrezi had next to no heat at all. We’re never ones to complain but after waiting so long and paying more money than usual, it’s so disappointing when you don’t enjoy your food at all.. What a shame!

FOOD NEVER ARRIVED. I phoned restaurant twice and was rudely spoken to and hung up on. Finally got through to someone who said he would process the refund. Refund never happened. I phoned back 3 days later. The man was apologetic and said he’d do it right away. Once again it never happened. I’ve waited long enough now to have my money returned to me. (10 days) Just eat won’t help because the restaurant has marked the order as delivered. This is false and as it stands, Landmark Indian restaurant has STOLEN FROM ME and I’m furious to say the least. Not only about the money but the fact I wasted a whole night waiting for my dinner to arrive just to end up having to go out and get something anyway. Plus all the time I’ve spent trying to chase up my money. It’s an ABSOLUTE JOKE!

We usually always order from here. Now the food is so oily a yellow it died my hands and my plates as I washed them. The food was very greasy and was stone cold. The driver had to force himself to speak when he couldn’t find the address. Long silences and just said he could find our address. When calling the restaurant they said it’s on the way. Then 30 mins later said 5 mins. When I asked if they called the driver they said they would then. They didn’t at all. Food was cold, chips were hard and cold and there was this yellow oil from the food all over the bag and containers. Never ever again. This was a fabulous takeaway. Now it is abysmal. Rude delivery driver, staff who were not bothered and eating dinner at 10:15 when you ordered at 7:30 is shocking. Avoid avoid avoid. I never write reviews just read them but I had to write this.

I ordered a Lamb Korma and it was not posted on the website that it was cooked in peanut oil. Consequently, when I tried the dish it caused an allergic reaction. When I called the restaurant to explain what happened and to suggest to put which dishes are cooked in peanut oil, the person on the phone became very irate and aggressive, suggesting that it was my fault that I had caused this situation. The way the person spoke to me on the phone was extremely rude and unprofessional, especially when the issue caused could have been extremely severe.

Absolutely terrible service. After waiting 20 minutes after the delivery time, we rang the take away who advised it would be a further 25 minutes. We decided to cancel the order because over 1 hour for for 0.3 miles away round the corner is ridiculous. Just eat chat confirmed the order hadn't been cancelled, and that the refund will take 5-10 working days. 2 hours later at almost 11pm there was a knock on the door for someone trying to deliver food!!! Worst service ever. Avoid at all costs, unless you want your Saturday night absolutely RUINED.

What a mess up. I ordered at 1910 for delivery at 2105. I got an irate delivery driver calling me at 2005 that he had been waiting for fifteen minutes and then he couldn't redeliver or promise to redeliver. I was not happy at all with the interaction and would not order from here again, the driver claimed the food had been cooked fresh but I don't believe it. Eventually it turned up after my original order time. Whats the point of being able to choose a delivery slot if they are going to turn up whenever? Very disappointed.

Appalling ! Ordered on Saturday evening and was told it would be 45 minutes for delivery , after 90 minutes I phoned to be told rudely they were busy and there was "nothing they could do" I was told it would be a further 15 minutes max, needles to say this wasn't the case and the food turned up over 2 hours late. Once the food arrived it was stone cold and inedible as well as missing items I had paid for on my card, phoned the restaurant and told them to which they replied "don't know what to say to you, email Just Eat and try and get your money back" .... Will not be using again !!

Do not order from this takeaway!! We live a five minute walk from there but due to the bad weather, we ordered food to be delivered. We ordered at 7.40 (a small order for two people), predicted delivery 8.40. With it being Valentine's day, we knew there would be a wait. When we had waited 1hr40 I phoned up and was told our food was being delivered (again we are two minutes away in the car) forty minutes later and still no sign of the food. I phoned up and was told "your food was ready half an hour ago but the delivery is going to L1 first." L1 is in the middle of town and miles away, so by the time our food had gone there and come back to us it was stone cold. I phoned to complain and was told "I will tell the manager" without any mention of compensation. When I insisted on speaking to the manager I was told they were not there and someone would phone me back in fifteen minutes (it was gone 10pm by this time). No-one rang back so I rang again to be told the manager would phone me back. They still haven't and it's closed now so I guess they're not phoning me back. We would've cancelled the order but had already paid on card. Will not be ordering from here again.

+ +

undercooked      undercooked Completely wrong      30 minutes late      was late      was cold      a waste wrong      wrong order wrong      Sent wrong      sent zinger      n't eat      a full      completely wrong      was cold      cold and inedible      The wrong      so disappointed      Even arrived      No good      ’ s      my order wrong      is annoying      And hot      if possible      were stale      was unedible      Many missing      our large      the plus      is allergic      NOT happy      n't speak      not acceptable      the first      that repeated      , disappointing      was ridiculous      was cold      it cold      items such      the creamy      so long      items and cold      really poor      was cold      cold and tasteless      of such      one big      the cold      was cold      has been wrong      and wrong      been unable      had missing      and wrong      very very cold      ordered Incorrect      3 sweet      3 sweet      one regular      the sweet      , small      was cold      with hot      of pop      of fresh      , large      , large      was sat      a little      like buying      absolutely useless      quite ridiculous      all wrong      cold and missing      was cold      cold and late      very hungry      not great      to receive such      , brown      with tasteless      completely stunned      to be served such      my next      them I wont      Very poor      one Missed      was cold      Very poor      how hard      and late      the delivery time further      so unhelpful      and cold      cold , horrific      was late      was cold      the crispy      was squishy      very greasy      Super quick      and polite      was freezing cold      the right      the other      burgers unorganised      Definitely not happy      of cold      the same      as food,made      eat it.total wast.....wont      gave wrong      arrived cold      the wrong      , poor      came cold      the wrong      despite numerous      restaurant direct      this appany      All chicken dry      3 mini fillets      kids 20 minutes late      items wrong      Got the wrong      for missed      after estimated      was rude      was cold      cold , wrong      was cold      missing Ridiculous      actually happy      90 minutes late      so disappointed      my full      45mins late      ordered extra      Expecting full      just eat      s ridiculous      was late      was cold      the second      ve forgotten      So annoying      was aggressive      was rude      the 

+
+

We had to fetch an update about delivery times ourself because it was running half an hour late

At that point, we were told it’d be with us in just over five minutes, it took a further fifteen to get to us

As a result, the fries weren’t a great temperature

What’s disappointing is that the burgers tasted incredible, it’s just a shame we had to wait 85 minutes for an order of two burgers and some fries

+

+

Food has not been delivered

+ +
+ +

Food arrived late here, it was due at ten to but arrived at 5 past. On taking it out the bag it was clear that the fries were already cold, even the burger cheese had no heat to it and the cheeseballs were cold which surprised me cause usually they hold heat for quite a while - they ended up just being greasy, cold and soggy. Rang to call the restaurant but they’d closed so it’s pretty clear that our food was the last of the day and there’s nothing we can do with the restaurant to sort this out. If it was on time and hot it would’ve no doubt been nice food but in the state it turned up in its very disappointing.

+ +

orange      was missing First      was great      was hot      2nd and 3rd      and cold      30mins late      customers fresh      Hit or miss      Very disappointing      all wrong      paid extra      my drink wrong      one tiny      Just overall      the wrong      the new      were available      not fresh      too old      happens second      ’ s      me first      Said driving      , wrong      wrong or cold      Today Large      and large      1.5 litre      a single      so wrong      after last      very dissatisfied      how many      for such      a measly      was cold      cold , small      and missing      the same      the wrong      a great      was dry      than necessary      was incorrect      was cold      refused to deliver missing      t the first      the wrong      really embarrassing      was cold      am still waiting Missed      are incompetent      when arrived      very bad      A really bad      were cold      and cold      was so      so rude and disrespectful      give bad      this terrible      Incredibly unhappy      late Please      was awful      as late      just plain      Once again only      Not good      the large      the first      was short      was cold      cold Food Missing      food cold      n't bother      is super      the restaurant multiple      all wrong      the first      the wrong      & NO      30 minutes late      t hot      was rubbery      so undercooked      something extra      completely poor      so poor      corn and a 1.5 litre      Very disappointing      is notorious      5 separate      it sticky      sticky and inedible      was incomplete      was cold      cold and grey      Absolutely disgusting      again No      the first      Freezing cold      order wrong      Not Happy      the first      t very good      the order wrong      and marked      another 1h30      was rude      my front      the completely wrong      never coming      is unacceptable      to be able      and brown      on burned      burned and soggy      soggy only good      Not sure      very inconvenient      Very disappointing      was good      was good      Too expensive      not hot      absolutely terrible      absolutely terrible and cold      me the wrong      kfc ever Missing      very unhelpful      - inedible      were dry      the first      the missing      seriously Bad      was cold      was dry      very very dry      very confused      just disgusting      very dry      not fresh      a fillet      a plain      were stale      

+
+

Food was 30 mins late , we put stuff on notes and swapped it other way around man was nice on the phone ordered chips aswell spicy burnt and just snapped

Jalapeño bites was just filled with cottage cheese didn’t even taste like it we even tried the stuff to see if we liked it and it was vile don’t understand why the bun was black won’t be ordering again

+

+

I never got this order

+ +
+ +

So this is the first review I have ever written about a food place as I’m not one to complain. However that being said this was horrific I’ve come in from a drink and ordered what I thought would be some good scran. It was about 50 minutes late, yeah I get it Tuesday night is so very hectic. I can get over the lateness If the food was good. So here comes my second point, my order the only thing that was correct was the waffles however they were cold. No one on this plant goes out for a drink and decides they want a waffle it was a little extra and it’s the only thing in my order that was correct. And it was cold so yeah not ideal. The quality of the food I got was extremely disappointing it tasted like day old whetherspoon food and again one more time after waiting 2 hours my order was incorrect. Such a waste of time. Next time I a, going to go to a local bin. It will be quicker possibly warmer and all in all better quality. Must say it’s unlikely I’ll be coming here again. Thank you for taking the time to read this rant about the worst just eat experience I have had 🤙

+ +

only only      only only half full      were cold      was cold      very small      to crunchy      :( Wrong      the right      Happened second      2 Hot      were unable      a third      was wrong      not great      were minuscule      considering tje      Really bad      Absolutely appalling      Did not send right      take simple      was cold      was rubbery      So much      the burger sloppy      to wrong      have always been rude      Really bad      Very rubbery      were soggy      soggy and cold      of poor      a full      was greedy      the entire      was greesy      was cold      really greasy      were wrong      were wrong      2 hot      the two fillet      ordered arrived      really small      really small and disappointing      the two fillet      was late      very cold      very cold and soggy      very dry      basically stale      the whole      am unable      me the wrong      me the right      was late      was cold      were cold      tasted weird      was late      So was unable      the first      the first      the first and last      was awful      is nasty      more Awful      to other      had missing      30 minutes late      therefore cold      Food poor      me regular      , white      a 9-5      and delayed      eats other      just eat      Not happy      10 pieces wicked      not happy      was cold      the wrong      pretty much      was meh      meh , rubbish      the drink wrong      have received is ridiculous.i      am furious      very few      were burnt      me the wrong      a real      was cold      was stale      a full      was cold      was disgusting      The wrong      very dry      was terrible      soo hungry      was cold      were cold      for local      was cold      were wrong      was cold      was late      was late      freezing cold      cold and wrong      paid extra      and dry      the only      a crispy      up with 50%      Very poor      and hungry      hungry Pretty awful      the order wrong      35 mins late      late and cold      n't bother      not willing      spoke to just eat several      now 10pm      Ridiculously bad      did not receive full      also wrong      t find      was going to take good      was minimum      minimum very few      vety small      and wrong      was cold      ordered had only      half empty      the correct      , wrong      and wrong      was 

+
+

First of all the food arrived 50 mins after the expected delivery time

When I opened the bag to find a tiny portion of Donner meat and chips and a tiny Shawarma

Very disappointed

Might have at least enjoyed what little was there if it had arrived at a hotter temperature in good time

Very unhappy

Spoke to the owner who said he would give me a free Shawarma on my next order

I hope they are much better next time

Not good at all

+

+

Food was all cold an nog much taste

+ +
+ +

Food was delivered and left in the apartment lobby downstairs. No notification that the food was delivered, no phone call and didn’t ring our apartment door buzzer from downstairs. Called the shop to query why we hadn’t received our food, they told us they had passed it to me. Told them a few times that I hadn’t received it. They put the phone down and when I called back, they didn’t answer. Terrible attitude. I went downstairs to see if it was dropped off and it was on the FLOOR by the door. Food was disgustingly cold and inedible. Don’t order from here - if you have a problem with the order or food, they will be extremely rude and not help.

Absolutely appalling service from both the restaurant and driver, my order was over an hour late and i kept calling about 5 times for my order and when i finally received it, i was missing my “gyros” kebab and when i called they said yeah it’s on the way without me even explaining them hanged up, i called up again and they said it’s on the way and i never got it :) i will not order from here again, also the “gyros” is not actually greek gyros for anyone wondering, it’s just normal chicken shawarma, i would comment on the “gyros” kebab but i never got it lol anyways if i could rate this service 0 i would 🙃

+ +

cold      not sure      really bad      t hot      at wrong      were soggy      looked old      not happy      all wrong      delivery perfect      all burnt      2 extra      very poor      was revolting      tasted like      was cold      food cold      even 45      even 45 mins late      the same      Order Wrong      and dry      all nasty      was horrible      the wrong      not able      reissue the same      instead informed      Very unsatisfied      completely wrong      it food never arrived      a separate      with expired      in as first      t arrive      not expectable      was wrong      was cold      not too fussed      the third      has been incorrect      be correct      was pink      pink and uncooked      a complete      is ALWAYS      2 hot      together bad      Soggy wrap Complete      20 different      the first      the first      was surprised      a few      a timely      two cold      the extra      was lovely      two different      very friendly      very friendly and helpful      the first      the funny      the second      the funny      the main      increasingly frustrated      not very friendly      very helpful      a different      the main      the latter      latter and mentioned      as closed      any outstanding      the second      and low      our original      what other      was late      was given wrong      tasted like      came cold      when contacted      was assured      8 mins late      a further      15 mins later      later it arrived      really poor      really poor service Third      , third      completely wrong      ’ s      two regular      was wrong      arrived cold      cold and soggy      cold and soggy , unhelpful      part wrong      also open      has been cold      the wrong      were luke      were cold      my full      the same      my food we both ordered      and ridiculous      delivery Cold food and inedible      chicken and the wrong      customer a different      have receive      REF 626688846 food late      order wrong      45 minutes late      me the wrong      in as past      paid 36      just incompitant      TWO big      a poor      the missing      m pretty      terrible service our food come      had little      were hungry      not great      not good      very poor      just eat      actually not very nice      very disappointed      Food cold      the receipt Wrong      a further      - disgraceful      just eat      very friendly      a positive      us 

+
+

Ordered 2 kids chicken burgers and a chicken burger and gourmet chicken burger

The chicken was the thinnest chicken I've ever seen on a burger and the slice of cheese on it was so thin it was see through

There was no difference in childrens burgers to the normal ones from the menu

Will not be ordering again

+

+

I haven't received my order

+ +
+ +

Poor poor delivery and customer service. Drinks were missing from my order, I rang the restaurant twice over the space of 30 mins to see where about the drinks were to be told “they’re on their way sorry” both times. By the time the drinks arrived (over an hour after the food came), I’d fell asleep. I rang the restaurant today to ask them to mark the delivery as complete so that just eat could compensate me, but after hanging up and speaking to a just eat representative l was told that the restaurant still hadn’t marked my order as complete. So the just eat rep then rang the restaurant directly - where the man on the phone told the rep that there was no record of my order?! How dishonest and cheeky - all over 3 cans? Usually I wouldn’t be bothered about £3 but it was the poor level of customer service last night and today that has been infuriating. The food was a solid 3 out of 5 but the service and delivery time has let this restaurant down big time. Thankfully just eat have helped rectify this issue, but this restaurant was uncooperative.

Avoid this place. I used to think it was really nice and and good customer service but that has changed the last few times we’ve ordered. You leave notes for stuff to be left off ie no tomato and most of the time they just ignore it, always late, I had an order where they left off my halloumi and when I called they said they could send it out or refund me given it was already over 20 minutes late I told them to refund me which they never did so the next time someone else in the house ordered I had to order it from my account to tell them to send it as they hadn’t refunded which I’ll admit they did but it should have been refunded I shouldn’t have had to wait almost 2 weeks until someone else ordered. There has been an occasion when I’ve ordered a halloumi wrap and luckily I’m not vegetarian as when it arrived it had chicken in with the halloumi had I been veggie and not just in the mood for a halloumi wrap this would have been a big issue and if they did that to someone who was veggie they could be sued for it as the person may not know into they but into it. Half the time the food is barely warm when it arrives and last week I decided to order through an app called ordergogo, over an hour after it was meant to arrive it still wasn’t here and we had tried calling over 20 times and got not answer so by 9:45 I decided to call ordergogo who managed to get in touch with them only to be told they were closed but they hadn’t told ordergogo so they could take them offline they just turned the printer off so people were Placing orders that weren’t going to be delivered not only was it too late to order from somewhere else by the time I managed to speak to someone but the money had been taken and I’ve had to wait almost a week for a refund. I won’t be Ordering again and I suggest everyone avoids them like the plague

Food was a bit late, but still warm. Double Chicken burger was really wet; I assumed it was from the salad; water dripping off the burger the entire time I ate it. The flavour of it was okay, but nothing special. The sweet potato fries were burnt and had a dry texture; they went in the bin straight away. The BBQ ribs we ordered had a weird flavour to them. The only thing we enjoyed about the meal was the cheesy garlic bread, but as it was the only thing that we enjoyed- unfortunately- the garlic bread wasn’t that nice to be ordering from here again

Food arrived on time but took over an hour despite the fact they are only 5mins away. We ordered 3 cheese and bacon gourmet burgers, 1 normal chips, 1 peri peri chips, peri mayonnaise and 3 passion fruit drinks. We received 2 portions of normal chips which were dried up and tasteless, normal mayonnaise and the ‘gourmet’ burgers where 2 very thin fillets of chicken, with a bit of bacon and cheese and limp lettuce. Very disappointed as I had heard good things about this place when it first opened. Only good part of the meal was the Portuguese drinks which were lovely. Won’t be ordering again.

It saddens me to say this as we love the food but the delivery time is just unacceptable. It has been getting progressively worse over time but tonight was just too much - we rang after 2 hours and they hadn't even started cooking it- we were apparently next on the list, but would we have been if I hadn't called? I would rather an order was declined than sit waiting so long so it's too late to order from elsewhere. Very disappointed as this was one of our favourites, but we will no longer be using them.

+ +

wrong      one hour late      well clear      it finally arrived      literally uneatable      50 minutes late      tasted like      a refund ridiculous      where wrong      was cold      again Missing      , cold      and warm      Extremely poor      , warm      30 min late      was late      and cold      was stale      not fresh      but luke      me the wrong      2 orange      were packed      was cold      was wrong      , poor      is 19:17      were cold      were wrong      being allergic      the only      the cob      a compleat      m allergic      so cold      charged extra      was wrong      ( wrong      , wrong      10 minutes late      was cold      a strange      to make sure      an outstanding      so oily      just gross      ‘ new      be try      the burgers full      was disgusting      was burnt      the outside      outside and mushy      the inside      were stale      Only decent      the fish      Yoo late      particularly good      much tastier available      food cold      food cold and soggy      never ordered Bad      was late      for free      at 18.38      be careful      was cold      just eat      to happy      store service rude      the wrong      my front      received Came cold      our first      was nice      was burnt      very salty      very salty and warm      some sort chemical      Absolutely disgusting      how cold      was like      a few      the next      the good      The overall      was professional      a bizarre      the last      for cold      cold food My      very rude      Always same      ( old      rings mobile      20 mins late      the cold      looks good      the wandsworth      time piping hot      I wont      say i      not great      was burnt      really bad      all different      t arrive      was late      late , poor      ( burnt      and cold      was late      late and cold      was late      late and cold      it arrived      So disappointing      have good      the last      the rude      m sure      no fav      a vegi      was cold      very long      was cold      a half late      on first      is abysmal      would be 1:50      no other      were open      Very angry      me the wrong      am allergic      my correct      my correct      taste fresh      a few      a few days old      30 minutes late      are useless      not happy      was cold      cold and burnt      

+
+

not impressed at all, clearly stated no salad no sauce on the burger

Order arrives and there salad all over the burger

Phoned the shop to be told to take the salad off the burger, they won't be sending another out because they are to busy

Isn't it a good job my partner isn't allergic to salad, though the burger didn't get eaten, disgusting response of THEIR mistake

+

+

Missing items

+ +
+ +

I’m absolutely disgusted and disappointed in my experience from the cafe naz stag and delivery driver . I ordered my food just before 7pm and it said it will be delivered at 8pm . At 8.05pm it told me my food had been delivered and it hadn’t. At 8.15pm I call the restaurant to enquire as to where my order was , to which I was told “it’s just leaving the restaurant now” and it will be 10-15 mins . At 8.30pm my phone rang and the driver said he could not find my house (clearly marked with the No5) and could I come out into the Main Street so he could see me I told him that I would come and open my . I stood in the street for 5 mins to no avail. It turns out he was SAT in his parked car up the street and hadn’t even gotten of his car to find my house.. I found this incredibly lazy and extremely unprofessional . He had 1 job and that was to DELIVER my order to my door. Not sit in his car and for me to then go find him to pick up my DELIVERY.. I will be seeking a full refund as by the time I got my food it was only warm and had to be heated up before we could eat .. I will be sharing this review on as many review platforms/and pages I can find ..

Tasteless food. Items not as described. Naan breads very flat in taste. No savoury quality in them. They were also hard not fluffy or buttered as they are supposed to be. Mixed vegetables Bhaji was a disgrace. They just sent peas and mashed up carrot and potatoes in a curry sauce. All universal mixed Bhaji’s are generally know to have cauliflower, carrots, green beans, mushrooms and peas in a curry sauce. Their lamb Bhuna was tasteless no spices no salt no nothing. Tough pieces of unchewable lamb. Also ordered aloo chat. They sent a puri with aloo ki sabzi inside. This is not chat at all. Chaat consists of crispy small flat bread almost biscuit texture. Boiled potatoes diced. Tamarind sauce. Coriander chutney. Sev. And yoghurt nothing of the sort. Tried to call the restaurant after speaking to just eat. Whilst they apologised they were very dismissive. I was advised promised a phone call from manager. Didn’t take down my number or nothing. Couldn’t get me of the phone quick enough. They have definitely been blacklisted from our takeaway list now. Shame they can’t keep up with consistency in their standards. Poor poor customer service.

after looking around for a while for a restaurant that would serve both mine and my boyfriends cravings. We finally stopped on Cafe Nez and proceeded with the order. soon after I placed the order., I received a phone call from the Cafe regarding meeting points and delivery time. So far so Good. As soon as we received our food, we were appalled. the half pound chicken burger was a plain chicken burger in bun. looked very dry. Tasted even drier. My Tandoori grilled Chicken looked and tasted like microwaved pink roast chicken with a bit of onions around it. King Prawn Purée was flavourless overcooked King Prawns in a curry purée. slightly Wrapped in an uncooked soggy mucus looking roti/pancake (unsure). The only delightful part of our order were the hot wings and fries. which were actually quite nice to be honest. Do not order from here, unless you want to waste £20

Would not order from here again. The chicken tikka massala we ordered was not like any tikka massala i've ever had. It was red and really sweet, more like a barbeque sauce than massala. The pilau rice was tri-colour and not pilau. I rang to complain and they said this is how they do theirs. Also, asked for red wine and got white. When the driver finally arrived to swap the wine he tried to suggest the wine had been opened!!! Complained about the food again and got the same answer, even the yoghurt and mint dip tasted like a sweet cream. I told him it was like everything had been covered with sugar!! He apologised but that was it. Their food was like nothing I've had from anywhere else. Told the driver i won't be ordering from there again and i won't!

Was left without part of our order due to receiving a discount. We weren't told that certain foods weren't available if we took the offer and they were initially very unhelpful when we called to complain about not receiving part of our order - for example we ordered a chicken tikka main that can with curry sauce and we initially received some chicken, some slices of pepper and onions, which I could have made at home. Eventually they delivered some curry sauce. I've ordered regularly from here in the past but, won't be ordering again.

Just received the order and we were extremely disappointed. The mixed kebab starters were cold and the onion bhaji was raw and the samosa very hard. The salmon main course was supposed to come with pilau rice but none arrived with order and the Naz Special Massalla was supposed to be topped with an omelette but instead a fried egg was on the top of the meal!! Have ordered from Cafe Naz previously and have always been very pleased with meals but wont be ordering from them again. Very, very disappointing and tasteless meals!!!

+ +

when outside      am unsure      was cold      the same      was 15mins      was cold      the same      2 different      Would recommend just staying hungry      a disappointing      by rude      was cold      cold and different      was late      was cold      30 mins late      Not happy      my pizza Last      very bad      extremely bad      very disrespectful      very disrespectful and rude      the extra      30 minutes due      is unacceptable      a full      never arrived Bad      Food arrived      the scheduled      speaking English      ordered last      quite long      's fine      25 minutes late      have to make new      're outside      the police due      the service last      was awful      illegally driving      their own      the correct      a small      hour delivery wait Not very hot      never again Awful      never again Awful awful awful      extremely late      was cold      the wrong      the just eat      t able      a wrong      a full      food disgusting      was cold      were burnt      more like      was disgusting      to add roquito      Extremely disappointed      was wrong      was right      was inedible      to wrong      with cold      a regular      , awful      23 minutes late      n't warm      after expected      was making lame      stated getting rude      Really disappointed last      the service next      was 30mins      the bbq      have bbq      for ASAP      an additional      my entire      was warm      not hot      a warm      is ridiculous      Was waiting outside      utterly pathetic      the second      Good food awful      was cold      took long      the right      was near      arrived 35min      completely dry      completely dry and cold      40 mins late      was late      Not happy      a 16      un professional      Absolutely disgraceful      surprise wrong      so angry      was late      issus Bad      was cold      the wrong      Terrible service and horrible      for new      its Garlic      a cold      very rude      very rude and aggressive      the order wrong      was reluctant      is fine      the other      very short      not very helpful      the wrong      else is open      had to sleep hungry      this poor      was good      is late      was cold      arrived Arrived      Freezing cold      cold and burnt      another pizza next      the next      Food arrived      arrived late and cold      was hard      hard and cold      was cold      cold and burnt      It 

+
+

Order jacket potato with tuna an mayo came with cheese phoned up had another sent out only for them to do the same

Order my food at 9

15 it’s now 11

55 and I still haven’t eaten my jacket potato

Not one bit impressed first time ordering Let’s see if this 3rd jacket potato turns up befor I say I’ll order again!

+

+

Very cheap food and arrived very very early

+ +
+ +

Ordered a plain garlic bread and got given one with copious amounts of cheese on it so couldn’t eat it. Didn’t have time to call the place to get a new one as I was taking it with me when I was going to my friends and didn’t think I’d get a replacement quick enough. Shouldn’t have to call anyway, I didn’t ask for cheese as I can’t eat it. Cake was frozen so couldn’t eat that. Donner meat was ok I’m told but a bit thick. Don’t think I’ll order again as this is the 3rd time an order has been wrong. They shouldn’t rush orders out and make sure they’re correct. Just annoyed I have to pay for more food tonight

+ +

order again 👎🏾      It order again 👎🏾 Ok food terrible      Pizza quality poor      basically massive      never came Delivered      The only      was good      was burnt      a crisp      's pathetic      was cold      45 mins late      very rude      5 minute      n't hot      n't hot barely warm      n't hot barely warm , forgot      was late      30mins late      late Awful      my 1st      was awful      a pizza online      was late      was wrong      me bbq      was rude      rude and late      rude and late Pizza undercooked      the chicken dippers undercooked      The only      was salty      was cold      this again Slow      the rudest      basically cold      too long      came Italian      ordered deep      the next      Very unhelpful      not nice      hugely undercooked      my order wrong      very rude      an extra      my own      the main      my flat      got angry      this terrible      and cold      did n't comr Was freezing cold      was cold      was stale      stale and cold      so rude      is realllllyyyy      Very poor      not good      not nice      needed to be cooked longer was cold      is horrible      was soggy      so greasy      that great      somewhere else      40 mins extra      Absolutely abysmally late      from previous      their restaurant number convenient      “ later      a few      I text their mobile      a sharp      ’ s      their the only      their the only place open      feel obligated      was late      so bad      very disappointed      absolutely vile      was cold      and disgusting      disgusting Cheese taste off🤢🤢🤢      was awful      my food late      the expected      was 1hr      an additional      the prosciutto      not very nice      35 minutes late      to salty      my dogs but the bbq      were fine      fine Cold horrible      is stupid      a small      had high      its heart just there own      how much      was polite      only warm      only warm and late      only warm and late pretty disappointing      burn bad      very late      was missing multiple      was late      as much      MIA The only      was nice      the hot      not nice      extremely late      was cold      and inedible      inedible Food cold      calls later      the last      anywhere else      absolutely disgusting      1hr40mins late      was cold      in different      very rude      than stated      was cold      

+
+

Asked for lettuce ONLY on my kebab and it came with salad items i am allergic to

I rang to let them know and had a very rude aggressive person on the other end telling me to just ‘take it off’ and telling me there was nothing that could be done

Also the chips where rock hard and unedible and the garlic bread was burnt! AVOID AVOID this take-away

Gave this take-away the benefit of the doubt after messing up a previous orders

Will NEVER order from here again

+

+

It was verry burnt and the chips were soggy.

+ +
+ +

I usually rate this the best place to order food from, but was very disappointed with my last order and won´t be ordering from here again. Ordered chicken then received a phone call asking me to pick something else, asked for a half-pounder burger and fries to the same value only to get a quarter-pounder burger with no cheese and no sauce. Asked for a kebab with a small amount of chilli sauce and a few onions, received a kebab drowned in chilli sauce and mayo (?!?!?!) but they did give an extra can of Cherry Coke (wow!). Makes me wonder where the normal staff were, dreadful service.

+ +

cold only nice      as cold      number busy      30 minutes late      a allergic      was cold      so disrespectful      was cold      almost inedible      was due      with thier      , disastrous      n't arrived      at 11:50      a new      some extra      not really interested      Very bad      was dreadful      Too much      no other      was decent      Pizza taste too doughy      really average      really average , similar      sent wrong      more different      a mental      to wrong      were chopped      slices Order wrong      was cold      was miserable      was cold      ’ next      to useless      the same      were wrong      TOTALLY USELESS      with cold      the entirely wrong      1 vegetarian      were aware      me the correct      as last      was great      will be unlikely      from expected      and cold      it tough      tough and crispy      was long      was cold      cold and stale      dont have many      for full      Its sad      is good      were soggy      was nice      The pizza arrived      20 minutes late      then came cold      cold , second      for 12.30      normally very good      and inedible      was stiff      stiff and hard      was awful      looked black      Again inedible      the irritating      an overly irritating      the money Wrong      a two half unsold      was cold      cold and gummy      was cold      and arrived      just cold      was cold      cold and burnt      are tiny      a bit      the wrong      a well used      a full      the expected      their own      the next      the next      and cold      through complete      my flat      my flat      would still be warm      and awful      Thanks Awful      getting worse      my first      and first      of first      was late      was due      being busy      is understandable      n't hear      the same      is late      arrived cold      even tepid      even tepid or luke      a quick      n't really edible      really good      a decent      would have been lovely      really tough      too hard      a normal      And cold      as good      as hot      a bit wrong      an extra      Not sure      to be learnt      of bad      Laura Missing      m sad      as usual      provides top      Arrived cold      was nice      delivered Great      Food arrived      Customer service absolute      incredibly busy      tried to call multiple      the wrong      would be ready      again 

+ \ No newline at end of file diff --git a/layout/interface.css b/layout/interface.css new file mode 100755 index 0000000..ba4b335 --- /dev/null +++ b/layout/interface.css @@ -0,0 +1,4 @@ + + +/* CSS for Paged.js interface – v0.2 */ /* Change the look */ :root { --color-background: whitesmoke; --color-pageSheet: #cfcfcf; --color-pageBox: violet; --color-paper: white; --color-marginBox: transparent; --pagedjs-crop-color: black; --pagedjs-crop-shadow: white; --pagedjs-crop-stroke: 1px; } /* To define how the book look on the screen: */ @media screen { body { background-color: var(--color-background); } .pagedjs_pages { display: flex; width: calc(var(--pagedjs-width) * 2); flex: 0; flex-wrap: wrap; margin: 0 auto; } .pagedjs_page { background-color: var(--color-paper); box-shadow: 0 0 0 1px var(--color-pageSheet); margin: 0; flex-shrink: 0; flex-grow: 0; margin-top: 10mm; } .pagedjs_first_page { margin-left: var(--pagedjs-width); } .pagedjs_page:last-of-type { margin-bottom: 10mm; } .pagedjs_pagebox{ box-shadow: 0 0 0 1px var(--color-pageBox); } .pagedjs_left_page{ z-index: 20; width: calc(var(--pagedjs-bleed-left) + var(--pagedjs-pagebox-width))!important; } .pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-crop { border-color: transparent; } .pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-middle{ width: 0; } .pagedjs_right_page{ z-index: 10; position: relative; left: calc(var(--pagedjs-bleed-left)*-1); } /* show the margin-box */ .pagedjs_margin-top-left-corner-holder, .pagedjs_margin-top, .pagedjs_margin-top-left, .pagedjs_margin-top-center, .pagedjs_margin-top-right, .pagedjs_margin-top-right-corner-holder, .pagedjs_margin-bottom-left-corner-holder, .pagedjs_margin-bottom, .pagedjs_margin-bottom-left, .pagedjs_margin-bottom-center, .pagedjs_margin-bottom-right, .pagedjs_margin-bottom-right-corner-holder, .pagedjs_margin-right, .pagedjs_margin-right-top, .pagedjs_margin-right-middle, .pagedjs_margin-right-bottom, .pagedjs_margin-left, .pagedjs_margin-left-top, .pagedjs_margin-left-middle, .pagedjs_margin-left-bottom { box-shadow: 0 0 0 1px inset var(--color-marginBox); } /* uncomment this part for recto/verso book : ------------------------------------ */ /* .pagedjs_pages { flex-direction: column; width: 100%; } .pagedjs_first_page { margin-left: 0; } .pagedjs_page { margin: 0 auto; margin-top: 10mm; } .pagedjs_left_page{ width: calc(var(--pagedjs-bleed-left) + var(--pagedjs-pagebox-width) + var(--pagedjs-bleed-left))!important; } .pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-crop{ border-color: var(--pagedjs-crop-color); } .pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-middle{ width: var(--pagedjs-cross-size)!important; } .pagedjs_right_page{ left: 0; } */ /*--------------------------------------------------------------------------------------*/ /* uncomment this par to see the baseline : -------------------------------------------*/ /* .pagedjs_pagebox { --pagedjs-baseline: 22px; --pagedjs-baseline-position: 5px; --pagedjs-baseline-color: cyan; background: linear-gradient(transparent 0%, transparent calc(var(--pagedjs-baseline) - 1px), var(--pagedjs-baseline-color) calc(var(--pagedjs-baseline) - 1px), var(--pagedjs-baseline-color) var(--pagedjs-baseline)), transparent; background-size: 100% var(--pagedjs-baseline); background-repeat: repeat-y; background-position-y: var(--pagedjs-baseline-position); } */ /*--------------------------------------------------------------------------------------*/ } /* Marks (to delete when merge in paged.js) */ .pagedjs_marks-crop{ z-index: 999999999999; } .pagedjs_bleed-top .pagedjs_marks-crop, .pagedjs_bleed-bottom .pagedjs_marks-crop{ box-shadow: 1px 0px 0px 0px var(--pagedjs-crop-shadow); } .pagedjs_bleed-top .pagedjs_marks-crop:last-child, .pagedjs_bleed-bottom .pagedjs_marks-crop:last-child{ box-shadow: -1px 0px 0px 0px var(--pagedjs-crop-shadow); } .pagedjs_bleed-left .pagedjs_marks-crop, .pagedjs_bleed-right .pagedjs_marks-crop{ box-shadow: 0px 1px 0px 0px var(--pagedjs-crop-shadow); } .pagedjs_bleed-left .pagedjs_marks-crop:last-child, .pagedjs_bleed-right .pagedjs_marks-crop:last-child{ box-shadow: 0px -1px 0px 0px var(--pagedjs-crop-shadow); } + diff --git a/layout/script/paged.js b/layout/script/paged.js new file mode 100755 index 0000000..145bb48 --- /dev/null +++ b/layout/script/paged.js @@ -0,0 +1,32736 @@ +/** + * @license Paged.js v0.2.0 | MIT | https://gitlab.pagedmedia.org/tools/pagedjs + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Paged = {})); +}(this, (function (exports) { 'use strict'; + + function getBoundingClientRect(element) { + if (!element) { + return; + } + let rect; + if (typeof element.getBoundingClientRect !== "undefined") { + rect = element.getBoundingClientRect(); + } else { + let range = document.createRange(); + range.selectNode(element); + rect = range.getBoundingClientRect(); + } + return rect; + } + + function getClientRects(element) { + if (!element) { + return; + } + let rect; + if (typeof element.getClientRects !== "undefined") { + rect = element.getClientRects(); + } else { + let range = document.createRange(); + range.selectNode(element); + rect = range.getClientRects(); + } + return rect; + } + + /** + * Generates a UUID + * based on: http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript + * @returns {string} uuid + */ + function UUID() { + var d = new Date().getTime(); + if (typeof performance !== "undefined" && typeof performance.now === "function") { + d += performance.now(); //use high-precision timer if available + } + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { + var r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16); + }); + } + + function attr(element, attributes) { + for (var i = 0; i < attributes.length; i++) { + if (element.hasAttribute(attributes[i])) { + return element.getAttribute(attributes[i]); + } + } + } + + /* Based on by https://mths.be/cssescape v1.5.1 by @mathias | MIT license + * Allows # and . + */ + function querySelectorEscape(value) { + if (arguments.length == 0) { + throw new TypeError("`CSS.escape` requires an argument."); + } + var string = String(value); + + var length = string.length; + var index = -1; + var codeUnit; + var result = ""; + var firstCodeUnit = string.charCodeAt(0); + while (++index < length) { + codeUnit = string.charCodeAt(index); + + + + // Note: there’s no need to special-case astral symbols, surrogate + // pairs, or lone surrogates. + + // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER + // (U+FFFD). + if (codeUnit == 0x0000) { + result += "\uFFFD"; + continue; + } + + if ( + // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is + // U+007F, […] + (codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F || + // If the character is the first character and is in the range [0-9] + // (U+0030 to U+0039), […] + (index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) || + // If the character is the second character and is in the range [0-9] + // (U+0030 to U+0039) and the first character is a `-` (U+002D), […] + ( + index == 1 && + codeUnit >= 0x0030 && codeUnit <= 0x0039 && + firstCodeUnit == 0x002D + ) + ) { + // https://drafts.csswg.org/cssom/#escape-a-character-as-code-point + result += "\\" + codeUnit.toString(16) + " "; + continue; + } + + if ( + // If the character is the first character and is a `-` (U+002D), and + // there is no second character, […] + index == 0 && + length == 1 && + codeUnit == 0x002D + ) { + result += "\\" + string.charAt(index); + continue; + } + + // support for period character in id + if (codeUnit == 0x002E) { + if (string.charAt(0) == "#") { + result += "\\."; + continue; + } + } + + + // If the character is not handled by one of the above rules and is + // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or + // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to + // U+005A), or [a-z] (U+0061 to U+007A), […] + if ( + codeUnit >= 0x0080 || + codeUnit == 0x002D || + codeUnit == 0x005F || + codeUnit == 35 || // Allow # + codeUnit == 46 || // Allow . + codeUnit >= 0x0030 && codeUnit <= 0x0039 || + codeUnit >= 0x0041 && codeUnit <= 0x005A || + codeUnit >= 0x0061 && codeUnit <= 0x007A + ) { + // the character itself + result += string.charAt(index); + continue; + } + + // Otherwise, the escaped character. + // https://drafts.csswg.org/cssom/#escape-a-character + result += "\\" + string.charAt(index); + + } + return result; + } + + /** + * Creates a new pending promise and provides methods to resolve or reject it. + * From: https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred#backwards_forwards_compatible + * @returns {object} defered + */ + function defer() { + this.resolve = null; + + this.reject = null; + + this.id = UUID(); + + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + Object.freeze(this); + } + + const requestIdleCallback = typeof window !== "undefined" && ("requestIdleCallback" in window ? window.requestIdleCallback : window.requestAnimationFrame); + + function CSSValueToString(obj) { + return obj.value + (obj.unit || ""); + } + + function isElement(node) { + return node && node.nodeType === 1; + } + + function isText(node) { + return node && node.nodeType === 3; + } + + function *walk$2(start, limiter) { + let node = start; + + while (node) { + + yield node; + + if (node.childNodes.length) { + node = node.firstChild; + } else if (node.nextSibling) { + if (limiter && node === limiter) { + node = undefined; + break; + } + node = node.nextSibling; + } else { + while (node) { + node = node.parentNode; + if (limiter && node === limiter) { + node = undefined; + break; + } + if (node && node.nextSibling) { + node = node.nextSibling; + break; + } + + } + } + } + } + + function nodeAfter(node, limiter) { + if (limiter && node === limiter) { + return; + } + let significantNode = nextSignificantNode(node); + if (significantNode) { + return significantNode; + } + if (node.parentNode) { + while ((node = node.parentNode)) { + if (limiter && node === limiter) { + return; + } + significantNode = nextSignificantNode(node); + if (significantNode) { + return significantNode; + } + } + } + } + + function nodeBefore(node, limiter) { + if (limiter && node === limiter) { + return; + } + let significantNode = previousSignificantNode(node); + if (significantNode) { + return significantNode; + } + if (node.parentNode) { + while ((node = node.parentNode)) { + if (limiter && node === limiter) { + return; + } + significantNode = previousSignificantNode(node); + if (significantNode) { + return significantNode; + } + } + } + } + + function elementAfter(node, limiter) { + let after = nodeAfter(node, limiter); + + while (after && after.nodeType !== 1) { + after = nodeAfter(after, limiter); + } + + return after; + } + + function elementBefore(node, limiter) { + let before = nodeBefore(node, limiter); + + while (before && before.nodeType !== 1) { + before = nodeBefore(before, limiter); + } + + return before; + } + + function displayedElementAfter(node, limiter) { + let after = elementAfter(node, limiter); + + while (after && after.dataset.undisplayed) { + after = elementAfter(after); + } + + return after; + } + + function displayedElementBefore(node, limiter) { + let before = elementBefore(node, limiter); + + while (before && before.dataset.undisplayed) { + before = elementBefore(before); + } + + return before; + } + + function rebuildAncestors(node) { + let parent, ancestor; + let ancestors = []; + let added = []; + + let fragment = document.createDocumentFragment(); + + // Handle rowspan on table + if (node.nodeName === "TR") { + let previousRow = node.previousElementSibling; + let previousRowDistance = 1; + while (previousRow) { + // previous row has more columns, might indicate a rowspan. + if (previousRow.childElementCount > node.childElementCount) { + const initialColumns = Array.from(node.children); + while (node.firstChild) { + node.firstChild.remove(); + } + let k = 0; + for (let j = 0; j < previousRow.children.length; j++) { + let column = previousRow.children[j]; + if (column.rowSpan && column.rowSpan > previousRowDistance) { + const duplicatedColumn = column.cloneNode(true); + // Adjust rowspan value + duplicatedColumn.rowSpan = column.rowSpan - previousRowDistance; + // Add the column to the row + node.appendChild(duplicatedColumn); + } else { + // Fill the gap with the initial columns (if exists) + const initialColumn = initialColumns[k++]; + // The initial column can be undefined if the newly created table has less columns than the original table + if (initialColumn) { + node.appendChild(initialColumn); + } + } + } + } + previousRow = previousRow.previousElementSibling; + previousRowDistance++; + } + } + + // Gather all ancestors + let element = node; + while(element.parentNode && element.parentNode.nodeType === 1) { + ancestors.unshift(element.parentNode); + element = element.parentNode; + } + + for (var i = 0; i < ancestors.length; i++) { + ancestor = ancestors[i]; + parent = ancestor.cloneNode(false); + + parent.setAttribute("data-split-from", parent.getAttribute("data-ref")); + // ancestor.setAttribute("data-split-to", parent.getAttribute("data-ref")); + + if (parent.hasAttribute("id")) { + let dataID = parent.getAttribute("id"); + parent.setAttribute("data-id", dataID); + parent.removeAttribute("id"); + } + + // This is handled by css :not, but also tidied up here + if (parent.hasAttribute("data-break-before")) { + parent.removeAttribute("data-break-before"); + } + + if (parent.hasAttribute("data-previous-break-after")) { + parent.removeAttribute("data-previous-break-after"); + } + + if (added.length) { + let container = added[added.length-1]; + container.appendChild(parent); + } else { + fragment.appendChild(parent); + } + added.push(parent); + } + + added = undefined; + return fragment; + } + + /* + export function split(bound, cutElement, breakAfter) { + let needsRemoval = []; + let index = indexOf(cutElement); + + if (!breakAfter && index === 0) { + return; + } + + if (breakAfter && index === (cutElement.parentNode.children.length - 1)) { + return; + } + + // Create a fragment with rebuilt ancestors + let fragment = rebuildAncestors(cutElement); + + // Clone cut + if (!breakAfter) { + let clone = cutElement.cloneNode(true); + let ref = cutElement.parentNode.getAttribute('data-ref'); + let parent = fragment.querySelector("[data-ref='" + ref + "']"); + parent.appendChild(clone); + needsRemoval.push(cutElement); + } + + // Remove all after cut + let next = nodeAfter(cutElement, bound); + while (next) { + let clone = next.cloneNode(true); + let ref = next.parentNode.getAttribute('data-ref'); + let parent = fragment.querySelector("[data-ref='" + ref + "']"); + parent.appendChild(clone); + needsRemoval.push(next); + next = nodeAfter(next, bound); + } + + // Remove originals + needsRemoval.forEach((node) => { + if (node) { + node.remove(); + } + }); + + // Insert after bounds + bound.parentNode.insertBefore(fragment, bound.nextSibling); + return [bound, bound.nextSibling]; + } + */ + + function needsBreakBefore(node) { + if( typeof node !== "undefined" && + typeof node.dataset !== "undefined" && + typeof node.dataset.breakBefore !== "undefined" && + (node.dataset.breakBefore === "always" || + node.dataset.breakBefore === "page" || + node.dataset.breakBefore === "left" || + node.dataset.breakBefore === "right" || + node.dataset.breakBefore === "recto" || + node.dataset.breakBefore === "verso") + ) { + return true; + } + + return false; + } + + function needsPreviousBreakAfter(node) { + if( typeof node !== "undefined" && + typeof node.dataset !== "undefined" && + typeof node.dataset.previousBreakAfter !== "undefined" && + (node.dataset.previousBreakAfter === "always" || + node.dataset.previousBreakAfter === "page" || + node.dataset.previousBreakAfter === "left" || + node.dataset.previousBreakAfter === "right" || + node.dataset.previousBreakAfter === "recto" || + node.dataset.previousBreakAfter === "verso") + ) { + return true; + } + + return false; + } + + function needsPageBreak(node, previousSignificantNode) { + if (typeof node === "undefined" || !previousSignificantNode || isIgnorable(node)) { + return false; + } + if (node.dataset && node.dataset.undisplayed) { + return false; + } + const previousSignificantNodePage = previousSignificantNode.dataset ? previousSignificantNode.dataset.page : undefined; + const currentNodePage = node.dataset ? node.dataset.page : undefined; + return currentNodePage !== previousSignificantNodePage; + } + + function *words(node) { + let currentText = node.nodeValue; + let max = currentText.length; + let currentOffset = 0; + let currentLetter; + + let range; + const significantWhitespaces = node.parentElement && node.parentElement.nodeName === 'PRE'; + + while (currentOffset < max) { + currentLetter = currentText[currentOffset]; + if (/^[\S\u202F\u00A0]$/.test(currentLetter) || significantWhitespaces) { + if (!range) { + range = document.createRange(); + range.setStart(node, currentOffset); + } + } else { + if (range) { + range.setEnd(node, currentOffset); + yield range; + range = undefined; + } + } + + currentOffset += 1; + } + + if (range) { + range.setEnd(node, currentOffset); + yield range; + } + } + + function *letters(wordRange) { + let currentText = wordRange.startContainer; + let max = currentText.length; + let currentOffset = wordRange.startOffset; + // let currentLetter; + + let range; + + while(currentOffset < max) { + // currentLetter = currentText[currentOffset]; + range = document.createRange(); + range.setStart(currentText, currentOffset); + range.setEnd(currentText, currentOffset+1); + + yield range; + + currentOffset += 1; + } + } + + function isContainer(node) { + let container; + + if (typeof node.tagName === "undefined") { + return true; + } + + if (node.style && node.style.display === "none") { + return false; + } + + switch (node.tagName) { + // Inline + case "A": + case "ABBR": + case "ACRONYM": + case "B": + case "BDO": + case "BIG": + case "BR": + case "BUTTON": + case "CITE": + case "CODE": + case "DFN": + case "EM": + case "I": + case "IMG": + case "INPUT": + case "KBD": + case "LABEL": + case "MAP": + case "OBJECT": + case "Q": + case "SAMP": + case "SCRIPT": + case "SELECT": + case "SMALL": + case "SPAN": + case "STRONG": + case "SUB": + case "SUP": + case "TEXTAREA": + case "TIME": + case "TT": + case "VAR": + case "P": + case "H1": + case "H2": + case "H3": + case "H4": + case "H5": + case "H6": + case "FIGCAPTION": + case "BLOCKQUOTE": + case "PRE": + case "LI": + case "TR": + case "DT": + case "DD": + case "VIDEO": + case "CANVAS": + container = false; + break; + default: + container = true; + } + + return container; + } + + function cloneNode(n, deep=false) { + return n.cloneNode(deep); + } + + function findElement(node, doc) { + const ref = node.getAttribute("data-ref"); + return findRef(ref, doc); + } + + function findRef(ref, doc) { + return doc.querySelector(`[data-ref='${ref}']`); + } + + function validNode(node) { + if (isText(node)) { + return true; + } + + if (isElement(node) && node.dataset.ref) { + return true; + } + + return false; + } + + function prevValidNode(node) { + while (!validNode(node)) { + if (node.previousSibling) { + node = node.previousSibling; + } else { + node = node.parentNode; + } + + if (!node) { + break; + } + } + + return node; + } + + + function indexOf$3(node) { + let parent = node.parentNode; + if (!parent) { + return 0; + } + return Array.prototype.indexOf.call(parent.childNodes, node); + } + + function child(node, index) { + return node.childNodes[index]; + } + + function hasContent(node) { + if (isElement(node)) { + return true; + } else if (isText(node) && + node.textContent.trim().length) { + return true; + } + return false; + } + + function indexOfTextNode(node, parent) { + if (!isText(node)) { + return -1; + } + let nodeTextContent = node.textContent; + let child; + let index = -1; + for (var i = 0; i < parent.childNodes.length; i++) { + child = parent.childNodes[i]; + if (child.nodeType === 3) { + let text = parent.childNodes[i].textContent; + if (text.includes(nodeTextContent)) { + index = i; + break; + } + } + } + + return index; + } + + + /** + * Throughout, whitespace is defined as one of the characters + * "\t" TAB \u0009 + * "\n" LF \u000A + * "\r" CR \u000D + * " " SPC \u0020 + * + * This does not use Javascript's "\s" because that includes non-breaking + * spaces (and also some other characters). + */ + + /** + * Determine if a node should be ignored by the iterator functions. + * taken from https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace#Whitespace_helper_functions + * + * @param {Node} node An object implementing the DOM1 |Node| interface. + * @return {boolean} true if the node is: + * 1) A |Text| node that is all whitespace + * 2) A |Comment| node + * and otherwise false. + */ + function isIgnorable(node) { + return (node.nodeType === 8) || // A comment node + ((node.nodeType === 3) && isAllWhitespace(node)); // a text node, all whitespace + } + + /** + * Determine whether a node's text content is entirely whitespace. + * + * @param {Node} node A node implementing the |CharacterData| interface (i.e., a |Text|, |Comment|, or |CDATASection| node + * @return {boolean} true if all of the text content of |nod| is whitespace, otherwise false. + */ + function isAllWhitespace(node) { + return !(/[^\t\n\r ]/.test(node.textContent)); + } + + /** + * Version of |previousSibling| that skips nodes that are entirely + * whitespace or comments. (Normally |previousSibling| is a property + * of all DOM nodes that gives the sibling node, the node that is + * a child of the same parent, that occurs immediately before the + * reference node.) + * + * @param {ChildNode} sib The reference node. + * @return {Node|null} Either: + * 1) The closest previous sibling to |sib| that is not ignorable according to |is_ignorable|, or + * 2) null if no such node exists. + */ + function previousSignificantNode(sib) { + while ((sib = sib.previousSibling)) { + if (!isIgnorable(sib)) return sib; + } + return null; + } + + function breakInsideAvoidParentNode(node) { + while ((node = node.parentNode)) { + if (node && node.dataset && node.dataset.breakInside === "avoid") { + return node; + } + } + return null; + } + + /** + * Find a parent with a given node name. + * @param {Node} node - initial Node + * @param {string} nodeName - node name (eg. "TD", "TABLE", "STRONG"...) + * @param {Node} limiter - go up to the parent until there's no more parent or the current node is equals to the limiter + * @returns {Node|undefined} - Either: + * 1) The closest parent for a the given node name, or + * 2) undefined if no such node exists. + */ + function parentOf(node, nodeName, limiter) { + if (limiter && node === limiter) { + return; + } + if (node.parentNode) { + while ((node = node.parentNode)) { + if (limiter && node === limiter) { + return; + } + if (node.nodeName === nodeName) { + return node; + } + } + } + } + + /** + * Version of |nextSibling| that skips nodes that are entirely + * whitespace or comments. + * + * @param {ChildNode} sib The reference node. + * @return {Node|null} Either: + * 1) The closest next sibling to |sib| that is not ignorable according to |is_ignorable|, or + * 2) null if no such node exists. + */ + function nextSignificantNode(sib) { + while ((sib = sib.nextSibling)) { + if (!isIgnorable(sib)) return sib; + } + return null; + } + + function filterTree(content, func, what) { + const treeWalker = document.createTreeWalker( + content || this.dom, + what || NodeFilter.SHOW_ALL, + func ? { acceptNode: func } : null, + false + ); + + let node; + let current; + node = treeWalker.nextNode(); + while(node) { + current = node; + node = treeWalker.nextNode(); + current.parentNode.removeChild(current); + } + } + + /** + * Layout + * @class + */ + class BreakToken { + + constructor(node, offset) { + this.node = node; + this.offset = offset; + } + + equals(otherBreakToken) { + if (!otherBreakToken) { + return false; + } + if (this["node"] && otherBreakToken["node"] && + this["node"] !== otherBreakToken["node"]) { + return false; + } + if (this["offset"] && otherBreakToken["offset"] && + this["offset"] !== otherBreakToken["offset"]) { + return false; + } + return true; + } + + } + + /** + * Render result. + * @class + */ + class RenderResult { + + constructor(breakToken, error) { + this.breakToken = breakToken; + this.error = error; + } + } + + class OverflowContentError extends Error { + constructor(message, items) { + super(message); + this.items = items; + } + } + + var eventEmitter = {exports: {}}; + + var d$3 = {exports: {}}; + + var isImplemented$6 = function () { + var assign = Object.assign, obj; + if (typeof assign !== "function") return false; + obj = { foo: "raz" }; + assign(obj, { bar: "dwa" }, { trzy: "trzy" }); + return (obj.foo + obj.bar + obj.trzy) === "razdwatrzy"; + }; + + var isImplemented$5 = function () { + try { + Object.keys("primitive"); + return true; + } catch (e) { + return false; + } + }; + + // eslint-disable-next-line no-empty-function + var noop$4 = function () {}; + + var _undefined = noop$4(); // Support ES3 engines + + var isValue$5 = function (val) { + return (val !== _undefined) && (val !== null); + }; + + var isValue$4 = isValue$5; + + var keys$2 = Object.keys; + + var shim$5 = function (object) { + return keys$2(isValue$4(object) ? Object(object) : object); + }; + + var keys$1 = isImplemented$5() + ? Object.keys + : shim$5; + + var isValue$3 = isValue$5; + + var validValue$1 = function (value) { + if (!isValue$3(value)) throw new TypeError("Cannot use null or undefined"); + return value; + }; + + var keys = keys$1 + , value$3 = validValue$1 + , max$1 = Math.max; + + var shim$4 = function (dest, src /*, …srcn*/) { + var error, i, length = max$1(arguments.length, 2), assign; + dest = Object(value$3(dest)); + assign = function (key) { + try { + dest[key] = src[key]; + } catch (e) { + if (!error) error = e; + } + }; + for (i = 1; i < length; ++i) { + src = arguments[i]; + keys(src).forEach(assign); + } + if (error !== undefined) throw error; + return dest; + }; + + var assign$2 = isImplemented$6() + ? Object.assign + : shim$4; + + var isValue$2 = isValue$5; + + var forEach$1 = Array.prototype.forEach, create$6 = Object.create; + + var process = function (src, obj) { + var key; + for (key in src) obj[key] = src[key]; + }; + + // eslint-disable-next-line no-unused-vars + var normalizeOptions = function (opts1 /*, …options*/) { + var result = create$6(null); + forEach$1.call(arguments, function (options) { + if (!isValue$2(options)) return; + process(Object(options), result); + }); + return result; + }; + + var isCallable$1 = function (obj) { + return typeof obj === "function"; + }; + + var str = "razdwatrzy"; + + var isImplemented$4 = function () { + if (typeof str.contains !== "function") return false; + return (str.contains("dwa") === true) && (str.contains("foo") === false); + }; + + var indexOf$2 = String.prototype.indexOf; + + var shim$3 = function (searchString/*, position*/) { + return indexOf$2.call(this, searchString, arguments[1]) > -1; + }; + + var contains$1 = isImplemented$4() + ? String.prototype.contains + : shim$3; + + var assign$1 = assign$2 + , normalizeOpts = normalizeOptions + , isCallable = isCallable$1 + , contains = contains$1 + + , d$2; + + d$2 = d$3.exports = function (dscr, value/*, options*/) { + var c, e, w, options, desc; + if ((arguments.length < 2) || (typeof dscr !== 'string')) { + options = value; + value = dscr; + dscr = null; + } else { + options = arguments[2]; + } + if (dscr == null) { + c = w = true; + e = false; + } else { + c = contains.call(dscr, 'c'); + e = contains.call(dscr, 'e'); + w = contains.call(dscr, 'w'); + } + + desc = { value: value, configurable: c, enumerable: e, writable: w }; + return !options ? desc : assign$1(normalizeOpts(options), desc); + }; + + d$2.gs = function (dscr, get, set/*, options*/) { + var c, e, options, desc; + if (typeof dscr !== 'string') { + options = set; + set = get; + get = dscr; + dscr = null; + } else { + options = arguments[3]; + } + if (get == null) { + get = undefined; + } else if (!isCallable(get)) { + options = get; + get = set = undefined; + } else if (set == null) { + set = undefined; + } else if (!isCallable(set)) { + options = set; + set = undefined; + } + if (dscr == null) { + c = true; + e = false; + } else { + c = contains.call(dscr, 'c'); + e = contains.call(dscr, 'e'); + } + + desc = { get: get, set: set, configurable: c, enumerable: e }; + return !options ? desc : assign$1(normalizeOpts(options), desc); + }; + + var validCallable = function (fn) { + if (typeof fn !== "function") throw new TypeError(fn + " is not a function"); + return fn; + }; + + (function (module, exports) { + + var d = d$3.exports + , callable = validCallable + + , apply = Function.prototype.apply, call = Function.prototype.call + , create = Object.create, defineProperty = Object.defineProperty + , defineProperties = Object.defineProperties + , hasOwnProperty = Object.prototype.hasOwnProperty + , descriptor = { configurable: true, enumerable: false, writable: true } + + , on, once, off, emit, methods, descriptors, base; + + on = function (type, listener) { + var data; + + callable(listener); + + if (!hasOwnProperty.call(this, '__ee__')) { + data = descriptor.value = create(null); + defineProperty(this, '__ee__', descriptor); + descriptor.value = null; + } else { + data = this.__ee__; + } + if (!data[type]) data[type] = listener; + else if (typeof data[type] === 'object') data[type].push(listener); + else data[type] = [data[type], listener]; + + return this; + }; + + once = function (type, listener) { + var once, self; + + callable(listener); + self = this; + on.call(this, type, once = function () { + off.call(self, type, once); + apply.call(listener, this, arguments); + }); + + once.__eeOnceListener__ = listener; + return this; + }; + + off = function (type, listener) { + var data, listeners, candidate, i; + + callable(listener); + + if (!hasOwnProperty.call(this, '__ee__')) return this; + data = this.__ee__; + if (!data[type]) return this; + listeners = data[type]; + + if (typeof listeners === 'object') { + for (i = 0; (candidate = listeners[i]); ++i) { + if ((candidate === listener) || + (candidate.__eeOnceListener__ === listener)) { + if (listeners.length === 2) data[type] = listeners[i ? 0 : 1]; + else listeners.splice(i, 1); + } + } + } else { + if ((listeners === listener) || + (listeners.__eeOnceListener__ === listener)) { + delete data[type]; + } + } + + return this; + }; + + emit = function (type) { + var i, l, listener, listeners, args; + + if (!hasOwnProperty.call(this, '__ee__')) return; + listeners = this.__ee__[type]; + if (!listeners) return; + + if (typeof listeners === 'object') { + l = arguments.length; + args = new Array(l - 1); + for (i = 1; i < l; ++i) args[i - 1] = arguments[i]; + + listeners = listeners.slice(); + for (i = 0; (listener = listeners[i]); ++i) { + apply.call(listener, this, args); + } + } else { + switch (arguments.length) { + case 1: + call.call(listeners, this); + break; + case 2: + call.call(listeners, this, arguments[1]); + break; + case 3: + call.call(listeners, this, arguments[1], arguments[2]); + break; + default: + l = arguments.length; + args = new Array(l - 1); + for (i = 1; i < l; ++i) { + args[i - 1] = arguments[i]; + } + apply.call(listeners, this, args); + } + } + }; + + methods = { + on: on, + once: once, + off: off, + emit: emit + }; + + descriptors = { + on: d(on), + once: d(once), + off: d(off), + emit: d(emit) + }; + + base = defineProperties({}, descriptors); + + module.exports = exports = function (o) { + return (o == null) ? create(base) : defineProperties(Object(o), descriptors); + }; + exports.methods = methods; + }(eventEmitter, eventEmitter.exports)); + + var EventEmitter = eventEmitter.exports; + + /** + * Hooks allow for injecting functions that must all complete in order before finishing + * They will execute in parallel but all must finish before continuing + * Functions may return a promise if they are asycn. + * From epubjs/src/utils/hooks + * @param {any} context scope of this + * @example this.content = new Hook(this); + */ + class Hook { + constructor(context){ + this.context = context || this; + this.hooks = []; + } + + /** + * Adds a function to be run before a hook completes + * @example this.content.register(function(){...}); + * @return {undefined} void + */ + register(){ + for(var i = 0; i < arguments.length; ++i) { + if (typeof arguments[i] === "function") { + this.hooks.push(arguments[i]); + } else { + // unpack array + for(var j = 0; j < arguments[i].length; ++j) { + this.hooks.push(arguments[i][j]); + } + } + } + } + + /** + * Triggers a hook to run all functions + * @example this.content.trigger(args).then(function(){...}); + * @return {Promise} results + */ + trigger(){ + var args = arguments; + var context = this.context; + var promises = []; + + this.hooks.forEach(function(task) { + var executing = task.apply(context, args); + + if(executing && typeof executing["then"] === "function") { + // Task is a function that returns a promise + promises.push(executing); + } + // Otherwise Task resolves immediately, add resolved promise with result + promises.push(new Promise((resolve, reject) => { + resolve(executing); + })); + }); + + + return Promise.all(promises); + } + + /** + * Triggers a hook to run all functions synchronously + * @example this.content.trigger(args).then(function(){...}); + * @return {Array} results + */ + triggerSync(){ + var args = arguments; + var context = this.context; + var results = []; + + this.hooks.forEach(function(task) { + var executing = task.apply(context, args); + + results.push(executing); + }); + + + return results; + } + + // Adds a function to be run before a hook completes + list(){ + return this.hooks; + } + + clear(){ + return this.hooks = []; + } + } + + const MAX_CHARS_PER_BREAK = 1500; + + /** + * Layout + * @class + */ + class Layout { + + constructor(element, hooks, options) { + this.element = element; + + this.bounds = this.element.getBoundingClientRect(); + + if (hooks) { + this.hooks = hooks; + } else { + this.hooks = {}; + this.hooks.layout = new Hook(); + this.hooks.renderNode = new Hook(); + this.hooks.layoutNode = new Hook(); + this.hooks.beforeOverflow = new Hook(); + this.hooks.onOverflow = new Hook(); + this.hooks.afterOverflowRemoved = new Hook(); + this.hooks.onBreakToken = new Hook(); + } + + this.settings = options || {}; + + this.maxChars = this.settings.maxChars || MAX_CHARS_PER_BREAK; + this.forceRenderBreak = false; + } + + async renderTo(wrapper, source, breakToken, bounds = this.bounds) { + let start = this.getStart(source, breakToken); + let walker = walk$2(start, source); + + let node; + let prevNode; + let done; + let next; + + let hasRenderedContent = false; + let newBreakToken; + + let length = 0; + + let prevBreakToken = breakToken || new BreakToken(start); + + while (!done && !newBreakToken) { + next = walker.next(); + prevNode = node; + node = next.value; + done = next.done; + + if (!node) { + this.hooks && this.hooks.layout.trigger(wrapper, this); + + let imgs = wrapper.querySelectorAll("img"); + if (imgs.length) { + await this.waitForImages(imgs); + } + + newBreakToken = this.findBreakToken(wrapper, source, bounds, prevBreakToken); + + if (newBreakToken && newBreakToken.equals(prevBreakToken)) { + console.warn("Unable to layout item: ", prevNode); + return new RenderResult(undefined, new OverflowContentError("Unable to layout item", [prevNode])); + } + return new RenderResult(newBreakToken); + } + + this.hooks && this.hooks.layoutNode.trigger(node); + + // Check if the rendered element has a break set + if (hasRenderedContent && this.shouldBreak(node)) { + this.hooks && this.hooks.layout.trigger(wrapper, this); + + let imgs = wrapper.querySelectorAll("img"); + if (imgs.length) { + await this.waitForImages(imgs); + } + + newBreakToken = this.findBreakToken(wrapper, source, bounds, prevBreakToken); + + if (!newBreakToken) { + newBreakToken = this.breakAt(node); + } + + if (newBreakToken && newBreakToken.equals(prevBreakToken)) { + console.warn("Unable to layout item: ", node); + return new RenderResult(undefined, new OverflowContentError("Unable to layout item", [node])); + } + + length = 0; + + break; + } + + // Should the Node be a shallow or deep clone + let shallow = isContainer(node); + + let rendered = this.append(node, wrapper, breakToken, shallow); + + length += rendered.textContent.length; + + // Check if layout has content yet + if (!hasRenderedContent) { + hasRenderedContent = hasContent(node); + } + + // Skip to the next node if a deep clone was rendered + if (!shallow) { + walker = walk$2(nodeAfter(node, source), source); + } + + if (this.forceRenderBreak) { + this.hooks && this.hooks.layout.trigger(wrapper, this); + + newBreakToken = this.findBreakToken(wrapper, source, bounds, prevBreakToken); + + if (!newBreakToken) { + newBreakToken = this.breakAt(node); + } + + length = 0; + this.forceRenderBreak = false; + + break; + } + + // Only check x characters + if (length >= this.maxChars) { + + this.hooks && this.hooks.layout.trigger(wrapper, this); + + let imgs = wrapper.querySelectorAll("img"); + if (imgs.length) { + await this.waitForImages(imgs); + } + + newBreakToken = this.findBreakToken(wrapper, source, bounds, prevBreakToken); + + if (newBreakToken && newBreakToken.equals(prevBreakToken)) { + console.warn("Unable to layout item: ", node); + return new RenderResult(undefined, new OverflowContentError("Unable to layout item", [node])); + } + + if (newBreakToken) { + length = 0; + } + } + + } + + return new RenderResult(newBreakToken); + } + + breakAt(node, offset = 0) { + let newBreakToken = new BreakToken( + node, + offset + ); + let breakHooks = this.hooks.onBreakToken.triggerSync(newBreakToken, undefined, node, this); + breakHooks.forEach((newToken) => { + if (typeof newToken != "undefined") { + newBreakToken = newToken; + } + }); + + return newBreakToken; + } + + shouldBreak(node) { + let previousSibling = previousSignificantNode(node); + let parentNode = node.parentNode; + let parentBreakBefore = needsBreakBefore(node) && parentNode && !previousSibling && needsBreakBefore(parentNode); + let doubleBreakBefore; + + if (parentBreakBefore) { + doubleBreakBefore = node.dataset.breakBefore === parentNode.dataset.breakBefore; + } + + return !doubleBreakBefore && needsBreakBefore(node) || needsPreviousBreakAfter(node) || needsPageBreak(node, previousSibling); + } + + forceBreak() { + this.forceRenderBreak = true; + } + + getStart(source, breakToken) { + let start; + let node = breakToken && breakToken.node; + + if (node) { + start = node; + } else { + start = source.firstChild; + } + + return start; + } + + append(node, dest, breakToken, shallow = true, rebuild = true) { + + let clone = cloneNode(node, !shallow); + + if (node.parentNode && isElement(node.parentNode)) { + let parent = findElement(node.parentNode, dest); + // Rebuild chain + if (parent) { + parent.appendChild(clone); + } else if (rebuild) { + let fragment = rebuildAncestors(node); + parent = findElement(node.parentNode, fragment); + if (!parent) { + dest.appendChild(clone); + } else if (breakToken && isText(breakToken.node) && breakToken.offset > 0) { + clone.textContent = clone.textContent.substring(breakToken.offset); + parent.appendChild(clone); + } else { + parent.appendChild(clone); + } + + dest.appendChild(fragment); + } else { + dest.appendChild(clone); + } + + + } else { + dest.appendChild(clone); + } + + let nodeHooks = this.hooks.renderNode.triggerSync(clone, node, this); + nodeHooks.forEach((newNode) => { + if (typeof newNode != "undefined") { + clone = newNode; + } + }); + + return clone; + } + + async waitForImages(imgs) { + let results = Array.from(imgs).map(async (img) => { + return this.awaitImageLoaded(img); + }); + await Promise.all(results); + } + + async awaitImageLoaded(image) { + return new Promise(resolve => { + if (image.complete !== true) { + image.onload = function () { + let {width, height} = window.getComputedStyle(image); + resolve(width, height); + }; + image.onerror = function (e) { + let {width, height} = window.getComputedStyle(image); + resolve(width, height, e); + }; + } else { + let {width, height} = window.getComputedStyle(image); + resolve(width, height); + } + }); + } + + avoidBreakInside(node, limiter) { + let breakNode; + + if (node === limiter) { + return; + } + + while (node.parentNode) { + node = node.parentNode; + + if (node === limiter) { + break; + } + + if (window.getComputedStyle(node)["break-inside"] === "avoid") { + breakNode = node; + break; + } + + } + return breakNode; + } + + createBreakToken(overflow, rendered, source) { + let container = overflow.startContainer; + let offset = overflow.startOffset; + let node, renderedNode, parent, index, temp; + + if (isElement(container)) { + temp = child(container, offset); + + if (isElement(temp)) { + renderedNode = findElement(temp, rendered); + + if (!renderedNode) { + // Find closest element with data-ref + let prevNode = prevValidNode(temp); + if (!isElement(prevNode)) { + prevNode = prevNode.parentElement; + } + renderedNode = findElement(prevNode, rendered); + // Check if temp is the last rendered node at its level. + if (!temp.nextSibling) { + // We need to ensure that the previous sibling of temp is fully rendered. + const renderedNodeFromSource = findElement(renderedNode, source); + const walker = document.createTreeWalker(renderedNodeFromSource, NodeFilter.SHOW_ELEMENT); + const lastChildOfRenderedNodeFromSource = walker.lastChild(); + const lastChildOfRenderedNodeMatchingFromRendered = findElement(lastChildOfRenderedNodeFromSource, rendered); + // Check if we found that the last child in source + if (!lastChildOfRenderedNodeMatchingFromRendered) { + // Pending content to be rendered before virtual break token + return; + } + // Otherwise we will return a break token as per below + } + // renderedNode is actually the last unbroken box that does not overflow. + // Break Token is therefore the next sibling of renderedNode within source node. + node = findElement(renderedNode, source).nextSibling; + offset = 0; + } else { + node = findElement(renderedNode, source); + offset = 0; + } + } else { + renderedNode = findElement(container, rendered); + + if (!renderedNode) { + renderedNode = findElement(prevValidNode(container), rendered); + } + + parent = findElement(renderedNode, source); + index = indexOfTextNode(temp, parent); + // No seperatation for the first textNode of an element + if(index === 0) { + node = parent; + offset = 0; + } else { + node = child(parent, index); + offset = 0; + } + } + } else { + renderedNode = findElement(container.parentNode, rendered); + + if (!renderedNode) { + renderedNode = findElement(prevValidNode(container.parentNode), rendered); + } + + parent = findElement(renderedNode, source); + index = indexOfTextNode(container, parent); + + if (index === -1) { + return; + } + + node = child(parent, index); + + offset += node.textContent.indexOf(container.textContent); + } + + if (!node) { + return; + } + + return new BreakToken( + node, + offset + ); + + } + + findBreakToken(rendered, source, bounds = this.bounds, prevBreakToken, extract = true) { + let overflow = this.findOverflow(rendered, bounds); + let breakToken, breakLetter; + + let overflowHooks = this.hooks.onOverflow.triggerSync(overflow, rendered, bounds, this); + overflowHooks.forEach((newOverflow) => { + if (typeof newOverflow != "undefined") { + overflow = newOverflow; + } + }); + + if (overflow) { + breakToken = this.createBreakToken(overflow, rendered, source); + // breakToken is nullable + let breakHooks = this.hooks.onBreakToken.triggerSync(breakToken, overflow, rendered, this); + breakHooks.forEach((newToken) => { + if (typeof newToken != "undefined") { + breakToken = newToken; + } + }); + + // Stop removal if we are in a loop + if (breakToken && breakToken.equals(prevBreakToken)) { + return breakToken; + } + + if (breakToken && breakToken["node"] && breakToken["offset"] && breakToken["node"].textContent) { + breakLetter = breakToken["node"].textContent.charAt(breakToken["offset"]); + } else { + breakLetter = undefined; + } + + if (breakToken && breakToken.node && extract) { + let removed = this.removeOverflow(overflow, breakLetter); + this.hooks && this.hooks.afterOverflowRemoved.trigger(removed, rendered, this); + } + + } + return breakToken; + } + + hasOverflow(element, bounds = this.bounds) { + let constrainingElement = element && element.parentNode; // this gets the element, instead of the wrapper for the width workaround + let {width} = element.getBoundingClientRect(); + let scrollWidth = constrainingElement ? constrainingElement.scrollWidth : 0; + return Math.max(Math.floor(width), scrollWidth) > Math.round(bounds.width); + } + + findOverflow(rendered, bounds = this.bounds) { + if (!this.hasOverflow(rendered, bounds)) return; + + let start = Math.round(bounds.left); + let end = Math.round(bounds.right); + let range; + + let walker = walk$2(rendered.firstChild, rendered); + + // Find Start + let next, done, node, offset, skip, breakAvoid, prev, br; + while (!done) { + next = walker.next(); + done = next.done; + node = next.value; + skip = false; + breakAvoid = false; + prev = undefined; + br = undefined; + + if (node) { + let pos = getBoundingClientRect(node); + let left = Math.round(pos.left); + let right = Math.floor(pos.right); + + if (!range && left >= end) { + // Check if it is a float + let isFloat = false; + + // Check if the node is inside a break-inside: avoid table cell + const insideTableCell = parentOf(node, "TD", rendered); + if (insideTableCell && window.getComputedStyle(insideTableCell)["break-inside"] === "avoid") { + // breaking inside a table cell produces unexpected result, as a workaround, we forcibly avoid break inside in a cell. + prev = insideTableCell; + } else if (isElement(node)) { + let styles = window.getComputedStyle(node); + isFloat = styles.getPropertyValue("float") !== "none"; + skip = styles.getPropertyValue("break-inside") === "avoid"; + breakAvoid = node.dataset.breakBefore === "avoid" || node.dataset.previousBreakAfter === "avoid"; + prev = breakAvoid && nodeBefore(node, rendered); + br = node.tagName === "BR" || node.tagName === "WBR"; + } + + let tableRow; + if (node.nodeName === "TR") { + tableRow = node; + } else { + tableRow = parentOf(node, "TR", rendered); + } + if (tableRow) { + // Check if the node is inside a row with a rowspan + const table = parentOf(tableRow, "TABLE", rendered); + if (table) { + let columnCount = 0; + for (const cell of Array.from(table.rows[0].cells)) { + columnCount += parseInt(cell.getAttribute("COLSPAN") || "1"); + } + if (tableRow.cells.length !== columnCount) { + let previousRow = tableRow.previousSibling; + let previousRowColumnCount; + while (previousRow !== null) { + previousRowColumnCount = 0; + for (const cell of Array.from(previousRow.cells)) { + previousRowColumnCount += parseInt(cell.getAttribute("COLSPAN") || "1"); + } + if (previousRowColumnCount === columnCount) { + break; + } + previousRow = previousRow.previousSibling; + } + if (previousRowColumnCount === columnCount) { + prev = previousRow; + } + } + } + } + + if (prev) { + range = document.createRange(); + range.selectNode(prev); + break; + } + + if (!br && !isFloat && isElement(node)) { + range = document.createRange(); + range.selectNode(node); + break; + } + + if (isText(node) && node.textContent.trim().length) { + range = document.createRange(); + range.selectNode(node); + break; + } + + } + + if (!range && isText(node) && + node.textContent.trim().length && + !breakInsideAvoidParentNode(node.parentNode)) { + + let rects = getClientRects(node); + let rect; + left = 0; + for (var i = 0; i != rects.length; i++) { + rect = rects[i]; + if (rect.width > 0 && (!left || rect.left > left)) { + left = rect.left; + } + } + + if (left >= end) { + range = document.createRange(); + offset = this.textBreak(node, start, end); + if (!offset) { + range = undefined; + } else { + range.setStart(node, offset); + } + break; + } + } + + // Skip children + if (skip || right <= end) { + next = nodeAfter(node, rendered); + if (next) { + walker = walk$2(next, rendered); + } + + } + + } + } + + // Find End + if (range) { + range.setEndAfter(rendered.lastChild); + return range; + } + + } + + findEndToken(rendered, source, bounds = this.bounds) { + if (rendered.childNodes.length === 0) { + return; + } + + let lastChild = rendered.lastChild; + + let lastNodeIndex; + while (lastChild && lastChild.lastChild) { + if (!validNode(lastChild)) { + // Only get elements with refs + lastChild = lastChild.previousSibling; + } else if (!validNode(lastChild.lastChild)) { + // Deal with invalid dom items + lastChild = prevValidNode(lastChild.lastChild); + break; + } else { + lastChild = lastChild.lastChild; + } + } + + if (isText(lastChild)) { + + if (lastChild.parentNode.dataset.ref) { + lastNodeIndex = indexOf$3(lastChild); + lastChild = lastChild.parentNode; + } else { + lastChild = lastChild.previousSibling; + } + } + + let original = findElement(lastChild, source); + + if (lastNodeIndex) { + original = original.childNodes[lastNodeIndex]; + } + + let after = nodeAfter(original); + + return this.breakAt(after); + } + + textBreak(node, start, end) { + let wordwalker = words(node); + let left = 0; + let right = 0; + let word, next, done, pos; + let offset; + while (!done) { + next = wordwalker.next(); + word = next.value; + done = next.done; + + if (!word) { + break; + } + + pos = getBoundingClientRect(word); + + left = Math.floor(pos.left); + right = Math.floor(pos.right); + + if (left >= end) { + offset = word.startOffset; + break; + } + + if (right > end) { + let letterwalker = letters(word); + let letter, nextLetter, doneLetter; + + while (!doneLetter) { + nextLetter = letterwalker.next(); + letter = nextLetter.value; + doneLetter = nextLetter.done; + + if (!letter) { + break; + } + + pos = getBoundingClientRect(letter); + left = Math.floor(pos.left); + + if (left >= end) { + offset = letter.startOffset; + done = true; + + break; + } + } + } + + } + + return offset; + } + + removeOverflow(overflow, breakLetter) { + let {startContainer} = overflow; + let extracted = overflow.extractContents(); + + this.hyphenateAtBreak(startContainer, breakLetter); + + return extracted; + } + + hyphenateAtBreak(startContainer, breakLetter) { + if (isText(startContainer)) { + let startText = startContainer.textContent; + let prevLetter = startText[startText.length - 1]; + + // Add a hyphen if previous character is a letter or soft hyphen + if ( + (breakLetter && /^\w|\u00AD$/.test(prevLetter) && /^\w|\u00AD$/.test(breakLetter)) || + (!breakLetter && /^\w|\u00AD$/.test(prevLetter)) + ) { + startContainer.parentNode.classList.add("pagedjs_hyphen"); + startContainer.textContent += this.settings.hyphenGlyph || "\u2011"; + } + } + } + + equalTokens(a, b) { + if (!a || !b) { + return false; + } + if (a["node"] && b["node"] && a["node"] !== b["node"]) { + return false; + } + if (a["offset"] && b["offset"] && a["offset"] !== b["offset"]) { + return false; + } + return true; + } + } + + EventEmitter(Layout.prototype); + + /** + * Render a page + * @class + */ + class Page { + constructor(pagesArea, pageTemplate, blank, hooks) { + this.pagesArea = pagesArea; + this.pageTemplate = pageTemplate; + this.blank = blank; + + this.width = undefined; + this.height = undefined; + + this.hooks = hooks; + + // this.element = this.create(this.pageTemplate); + } + + create(template, after) { + //let documentFragment = document.createRange().createContextualFragment( TEMPLATE ); + //let page = documentFragment.children[0]; + let clone = document.importNode(this.pageTemplate.content, true); + + let page, index; + if (after) { + this.pagesArea.insertBefore(clone, after.nextElementSibling); + index = Array.prototype.indexOf.call(this.pagesArea.children, after.nextElementSibling); + page = this.pagesArea.children[index]; + } else { + this.pagesArea.appendChild(clone); + page = this.pagesArea.lastChild; + } + + let pagebox = page.querySelector(".pagedjs_pagebox"); + let area = page.querySelector(".pagedjs_page_content"); + let footnotesArea = page.querySelector(".pagedjs_footnote_area"); + + + let size = area.getBoundingClientRect(); + + + area.style.columnWidth = Math.round(size.width) + "px"; + area.style.columnGap = "calc(var(--pagedjs-margin-right) + var(--pagedjs-margin-left))"; + // area.style.overflow = "scroll"; + + this.width = Math.round(size.width); + this.height = Math.round(size.height); + + this.element = page; + this.pagebox = pagebox; + this.area = area; + this.footnotesArea = footnotesArea; + + return page; + } + + createWrapper() { + let wrapper = document.createElement("div"); + + this.area.appendChild(wrapper); + + this.wrapper = wrapper; + + return wrapper; + } + + index(pgnum) { + this.position = pgnum; + + let page = this.element; + // let pagebox = this.pagebox; + + let index = pgnum + 1; + + let id = `page-${index}`; + + this.id = id; + + // page.dataset.pageNumber = index; + + page.dataset.pageNumber = index; + page.setAttribute("id", id); + + if (this.name) { + page.classList.add("pagedjs_" + this.name + "_page"); + } + + if (this.blank) { + page.classList.add("pagedjs_blank_page"); + } + + if (pgnum === 0) { + page.classList.add("pagedjs_first_page"); + } + + if (pgnum % 2 !== 1) { + page.classList.remove("pagedjs_left_page"); + page.classList.add("pagedjs_right_page"); + } else { + page.classList.remove("pagedjs_right_page"); + page.classList.add("pagedjs_left_page"); + } + } + + /* + size(width, height) { + if (width === this.width && height === this.height) { + return; + } + this.width = width; + this.height = height; + + this.element.style.width = Math.round(width) + "px"; + this.element.style.height = Math.round(height) + "px"; + this.element.style.columnWidth = Math.round(width) + "px"; + } + */ + + async layout(contents, breakToken, maxChars) { + + this.clear(); + + this.startToken = breakToken; + + this.layoutMethod = new Layout(this.area, this.hooks, maxChars); + + let renderResult = await this.layoutMethod.renderTo(this.wrapper, contents, breakToken); + let newBreakToken = renderResult.breakToken; + + this.addListeners(contents); + + this.endToken = newBreakToken; + + return newBreakToken; + } + + async append(contents, breakToken) { + + if (!this.layoutMethod) { + return this.layout(contents, breakToken); + } + + let renderResult = await this.layoutMethod.renderTo(this.wrapper, contents, breakToken); + let newBreakToken = renderResult.breakToken; + + this.endToken = newBreakToken; + + return newBreakToken; + } + + getByParent(ref, entries) { + let e; + for (var i = 0; i < entries.length; i++) { + e = entries[i]; + if (e.dataset.ref === ref) { + return e; + } + } + } + + onOverflow(func) { + this._onOverflow = func; + } + + onUnderflow(func) { + this._onUnderflow = func; + } + + clear() { + this.removeListeners(); + this.wrapper && this.wrapper.remove(); + this.createWrapper(); + } + + addListeners(contents) { + if (typeof ResizeObserver !== "undefined") { + this.addResizeObserver(contents); + } else { + this._checkOverflowAfterResize = this.checkOverflowAfterResize.bind(this, contents); + this.element.addEventListener("overflow", this._checkOverflowAfterResize, false); + this.element.addEventListener("underflow", this._checkOverflowAfterResize, false); + } + // TODO: fall back to mutation observer? + + this._onScroll = function () { + if (this.listening) { + this.element.scrollLeft = 0; + } + }.bind(this); + + // Keep scroll left from changing + this.element.addEventListener("scroll", this._onScroll); + + this.listening = true; + + return true; + } + + removeListeners() { + this.listening = false; + + if (typeof ResizeObserver !== "undefined" && this.ro) { + this.ro.disconnect(); + } else if (this.element) { + this.element.removeEventListener("overflow", this._checkOverflowAfterResize, false); + this.element.removeEventListener("underflow", this._checkOverflowAfterResize, false); + } + + this.element && this.element.removeEventListener("scroll", this._onScroll); + + } + + addResizeObserver(contents) { + let wrapper = this.wrapper; + let prevHeight = wrapper.getBoundingClientRect().height; + this.ro = new ResizeObserver(entries => { + + if (!this.listening) { + return; + } + requestAnimationFrame(() => { + for (let entry of entries) { + const cr = entry.contentRect; + + if (cr.height > prevHeight) { + this.checkOverflowAfterResize(contents); + prevHeight = wrapper.getBoundingClientRect().height; + } else if (cr.height < prevHeight) { // TODO: calc line height && (prevHeight - cr.height) >= 22 + this.checkUnderflowAfterResize(contents); + prevHeight = cr.height; + } + } + }); + }); + + this.ro.observe(wrapper); + } + + checkOverflowAfterResize(contents) { + if (!this.listening || !this.layoutMethod) { + return; + } + + let newBreakToken = this.layoutMethod.findBreakToken(this.wrapper, contents, this.startToken); + + if (newBreakToken) { + this.endToken = newBreakToken; + this._onOverflow && this._onOverflow(newBreakToken); + } + } + + checkUnderflowAfterResize(contents) { + if (!this.listening || !this.layoutMethod) { + return; + } + + let endToken = this.layoutMethod.findEndToken(this.wrapper, contents); + + if (endToken) { + this._onUnderflow && this._onUnderflow(endToken); + } + } + + + destroy() { + this.removeListeners(); + + this.element.remove(); + + this.element = undefined; + this.wrapper = undefined; + } + } + + EventEmitter(Page.prototype); + + /** + * Render a flow of text offscreen + * @class + */ + class ContentParser { + + constructor(content, cb) { + if (content && content.nodeType) { + // handle dom + this.dom = this.add(content); + } else if (typeof content === "string") { + this.dom = this.parse(content); + } + + return this.dom; + } + + parse(markup, mime) { + let range = document.createRange(); + let fragment = range.createContextualFragment(markup); + + this.addRefs(fragment); + + return fragment; + } + + add(contents) { + // let fragment = document.createDocumentFragment(); + // + // let children = [...contents.childNodes]; + // for (let child of children) { + // let clone = child.cloneNode(true); + // fragment.appendChild(clone); + // } + + this.addRefs(contents); + + return contents; + } + + addRefs(content) { + var treeWalker = document.createTreeWalker( + content, + NodeFilter.SHOW_ELEMENT, + null, + false + ); + + let node = treeWalker.nextNode(); + while(node) { + + if (!node.hasAttribute("data-ref")) { + let uuid = UUID(); + node.setAttribute("data-ref", uuid); + } + + if (node.id) { + node.setAttribute("data-id", node.id); + } + + // node.setAttribute("data-children", node.childNodes.length); + + // node.setAttribute("data-text", node.textContent.trim().length); + node = treeWalker.nextNode(); + } + } + + find(ref) { + return this.refs[ref]; + } + + destroy() { + this.refs = undefined; + this.dom = undefined; + } + } + + /** + * Queue for handling tasks one at a time + * @class + * @param {scope} context what this will resolve to in the tasks + */ + class Queue { + constructor(context){ + this._q = []; + this.context = context; + this.tick = requestAnimationFrame; + this.running = false; + this.paused = false; + } + + /** + * Add an item to the queue + * @return {Promise} enqueued + */ + enqueue() { + var deferred, promise; + var queued; + var task = [].shift.call(arguments); + var args = arguments; + + // Handle single args without context + // if(args && !Array.isArray(args)) { + // args = [args]; + // } + if(!task) { + throw new Error("No Task Provided"); + } + + if(typeof task === "function"){ + + deferred = new defer(); + promise = deferred.promise; + + queued = { + "task" : task, + "args" : args, + //"context" : context, + "deferred" : deferred, + "promise" : promise + }; + + } else { + // Task is a promise + queued = { + "promise" : task + }; + + } + + this._q.push(queued); + + // Wait to start queue flush + if (this.paused == false && !this.running) { + this.run(); + } + + return queued.promise; + } + + /** + * Run one item + * @return {Promise} dequeued + */ + dequeue(){ + var inwait, task, result; + + if(this._q.length && !this.paused) { + inwait = this._q.shift(); + task = inwait.task; + if(task){ + // console.log(task) + + result = task.apply(this.context, inwait.args); + + if(result && typeof result["then"] === "function") { + // Task is a function that returns a promise + return result.then(function(){ + inwait.deferred.resolve.apply(this.context, arguments); + }.bind(this), function() { + inwait.deferred.reject.apply(this.context, arguments); + }.bind(this)); + } else { + // Task resolves immediately + inwait.deferred.resolve.apply(this.context, result); + return inwait.promise; + } + + + + } else if(inwait.promise) { + // Task is a promise + return inwait.promise; + } + + } else { + inwait = new defer(); + inwait.deferred.resolve(); + return inwait.promise; + } + + } + + // Run All Immediately + dump(){ + while(this._q.length) { + this.dequeue(); + } + } + + /** + * Run all tasks sequentially, at convince + * @return {Promise} all run + */ + run(){ + + if(!this.running){ + this.running = true; + this.defered = new defer(); + } + + this.tick.call(window, () => { + + if(this._q.length) { + + this.dequeue() + .then(function(){ + this.run(); + }.bind(this)); + + } else { + this.defered.resolve(); + this.running = undefined; + } + + }); + + // Unpause + if(this.paused == true) { + this.paused = false; + } + + return this.defered.promise; + } + + /** + * Flush all, as quickly as possible + * @return {Promise} ran + */ + flush(){ + + if(this.running){ + return this.running; + } + + if(this._q.length) { + this.running = this.dequeue() + .then(function(){ + this.running = undefined; + return this.flush(); + }.bind(this)); + + return this.running; + } + + } + + /** + * Clear all items in wait + * @return {void} + */ + clear(){ + this._q = []; + } + + /** + * Get the number of tasks in the queue + * @return {number} tasks + */ + length(){ + return this._q.length; + } + + /** + * Pause a running queue + * @return {void} + */ + pause(){ + this.paused = true; + } + + /** + * End the queue + * @return {void} + */ + stop(){ + this._q = []; + this.running = false; + this.paused = true; + } + } + + const TEMPLATE = ` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
`; + + /** + * Chop up text into flows + * @class + */ + class Chunker { + constructor(content, renderTo, options) { + // this.preview = preview; + + this.settings = options || {}; + + this.hooks = {}; + this.hooks.beforeParsed = new Hook(this); + this.hooks.filter = new Hook(this); + this.hooks.afterParsed = new Hook(this); + this.hooks.beforePageLayout = new Hook(this); + this.hooks.layout = new Hook(this); + this.hooks.renderNode = new Hook(this); + this.hooks.layoutNode = new Hook(this); + this.hooks.onOverflow = new Hook(this); + this.hooks.afterOverflowRemoved = new Hook(this); + this.hooks.onBreakToken = new Hook(); + this.hooks.afterPageLayout = new Hook(this); + this.hooks.afterRendered = new Hook(this); + + this.pages = []; + this.total = 0; + + this.q = new Queue(this); + this.stopped = false; + this.rendered = false; + + this.content = content; + + this.charsPerBreak = []; + this.maxChars; + + if (content) { + this.flow(content, renderTo); + } + } + + setup(renderTo) { + this.pagesArea = document.createElement("div"); + this.pagesArea.classList.add("pagedjs_pages"); + + if (renderTo) { + renderTo.appendChild(this.pagesArea); + } else { + document.querySelector("body").appendChild(this.pagesArea); + } + + this.pageTemplate = document.createElement("template"); + this.pageTemplate.innerHTML = TEMPLATE; + + } + + async flow(content, renderTo) { + let parsed; + + await this.hooks.beforeParsed.trigger(content, this); + + parsed = new ContentParser(content); + + this.hooks.filter.triggerSync(parsed); + + this.source = parsed; + this.breakToken = undefined; + + if (this.pagesArea && this.pageTemplate) { + this.q.clear(); + this.removePages(); + } else { + this.setup(renderTo); + } + + this.emit("rendering", parsed); + + await this.hooks.afterParsed.trigger(parsed, this); + + await this.loadFonts(); + + let rendered = await this.render(parsed, this.breakToken); + while (rendered.canceled) { + this.start(); + rendered = await this.render(parsed, this.breakToken); + } + + this.rendered = true; + this.pagesArea.style.setProperty("--pagedjs-page-count", this.total); + + await this.hooks.afterRendered.trigger(this.pages, this); + + this.emit("rendered", this.pages); + + + + return this; + } + + // oversetPages() { + // let overset = []; + // for (let i = 0; i < this.pages.length; i++) { + // let page = this.pages[i]; + // if (page.overset) { + // overset.push(page); + // // page.overset = false; + // } + // } + // return overset; + // } + // + // async handleOverset(parsed) { + // let overset = this.oversetPages(); + // if (overset.length) { + // console.log("overset", overset); + // let index = this.pages.indexOf(overset[0]) + 1; + // console.log("INDEX", index); + // + // // Remove pages + // // this.removePages(index); + // + // // await this.render(parsed, overset[0].overset); + // + // // return this.handleOverset(parsed); + // } + // } + + async render(parsed, startAt) { + let renderer = this.layout(parsed, startAt, this.settings); + + let done = false; + let result; + while (!done) { + result = await this.q.enqueue(() => { return this.renderAsync(renderer); }); + done = result.done; + } + + return result; + } + + start() { + this.rendered = false; + this.stopped = false; + } + + stop() { + this.stopped = true; + // this.q.clear(); + } + + renderOnIdle(renderer) { + return new Promise(resolve => { + requestIdleCallback(async () => { + if (this.stopped) { + return resolve({ done: true, canceled: true }); + } + let result = await renderer.next(); + if (this.stopped) { + resolve({ done: true, canceled: true }); + } else { + resolve(result); + } + }); + }); + } + + async renderAsync(renderer) { + if (this.stopped) { + return { done: true, canceled: true }; + } + let result = await renderer.next(); + if (this.stopped) { + return { done: true, canceled: true }; + } else { + return result; + } + } + + async handleBreaks(node, force) { + let currentPage = this.total + 1; + let currentPosition = currentPage % 2 === 0 ? "left" : "right"; + // TODO: Recto and Verso should reverse for rtl languages + let currentSide = currentPage % 2 === 0 ? "verso" : "recto"; + let previousBreakAfter; + let breakBefore; + let page; + + if (currentPage === 1) { + return; + } + + if (node && + typeof node.dataset !== "undefined" && + typeof node.dataset.previousBreakAfter !== "undefined") { + previousBreakAfter = node.dataset.previousBreakAfter; + } + + if (node && + typeof node.dataset !== "undefined" && + typeof node.dataset.breakBefore !== "undefined") { + breakBefore = node.dataset.breakBefore; + } + + if (force) { + page = this.addPage(true); + } else if( previousBreakAfter && + (previousBreakAfter === "left" || previousBreakAfter === "right") && + previousBreakAfter !== currentPosition) { + page = this.addPage(true); + } else if( previousBreakAfter && + (previousBreakAfter === "verso" || previousBreakAfter === "recto") && + previousBreakAfter !== currentSide) { + page = this.addPage(true); + } else if( breakBefore && + (breakBefore === "left" || breakBefore === "right") && + breakBefore !== currentPosition) { + page = this.addPage(true); + } else if( breakBefore && + (breakBefore === "verso" || breakBefore === "recto") && + breakBefore !== currentSide) { + page = this.addPage(true); + } + + if (page) { + await this.hooks.beforePageLayout.trigger(page, undefined, undefined, this); + this.emit("page", page); + // await this.hooks.layout.trigger(page.element, page, undefined, this); + await this.hooks.afterPageLayout.trigger(page.element, page, undefined, this); + this.emit("renderedPage", page); + } + } + + async *layout(content, startAt) { + let breakToken = startAt || false; + + while (breakToken !== undefined && (true)) { + + if (breakToken && breakToken.node) { + await this.handleBreaks(breakToken.node); + } else { + await this.handleBreaks(content.firstChild); + } + + let page = this.addPage(); + + await this.hooks.beforePageLayout.trigger(page, content, breakToken, this); + this.emit("page", page); + + // Layout content in the page, starting from the breakToken + breakToken = await page.layout(content, breakToken, this.maxChars); + + await this.hooks.afterPageLayout.trigger(page.element, page, breakToken, this); + this.emit("renderedPage", page); + + this.recoredCharLength(page.wrapper.textContent.length); + + yield breakToken; + + // Stop if we get undefined, showing we have reached the end of the content + } + + + } + + recoredCharLength(length) { + if (length === 0) { + return; + } + + this.charsPerBreak.push(length); + + // Keep the length of the last few breaks + if (this.charsPerBreak.length > 4) { + this.charsPerBreak.shift(); + } + + this.maxChars = this.charsPerBreak.reduce((a, b) => a + b, 0) / (this.charsPerBreak.length); + } + + removePages(fromIndex=0) { + + if (fromIndex >= this.pages.length) { + return; + } + + // Remove pages + for (let i = fromIndex; i < this.pages.length; i++) { + this.pages[i].destroy(); + } + + if (fromIndex > 0) { + this.pages.splice(fromIndex); + } else { + this.pages = []; + } + + this.total = this.pages.length; + } + + addPage(blank) { + let lastPage = this.pages[this.pages.length - 1]; + // Create a new page from the template + let page = new Page(this.pagesArea, this.pageTemplate, blank, this.hooks); + + this.pages.push(page); + + // Create the pages + page.create(undefined, lastPage && lastPage.element); + + page.index(this.total); + + if (!blank) { + // Listen for page overflow + page.onOverflow((overflowToken) => { + console.warn("overflow on", page.id, overflowToken); + + // Only reflow while rendering + if (this.rendered) { + return; + } + + let index = this.pages.indexOf(page) + 1; + + // Stop the rendering + this.stop(); + + // Set the breakToken to resume at + this.breakToken = overflowToken; + + // Remove pages + this.removePages(index); + + if (this.rendered === true) { + this.rendered = false; + + this.q.enqueue(async () => { + + this.start(); + + await this.render(this.source, this.breakToken); + + this.rendered = true; + + }); + } + + + }); + + page.onUnderflow((overflowToken) => { + // console.log("underflow on", page.id, overflowToken); + + // page.append(this.source, overflowToken); + + }); + } + + this.total = this.pages.length; + + return page; + } + /* + insertPage(index, blank) { + let lastPage = this.pages[index]; + // Create a new page from the template + let page = new Page(this.pagesArea, this.pageTemplate, blank, this.hooks); + + let total = this.pages.splice(index, 0, page); + + // Create the pages + page.create(undefined, lastPage && lastPage.element); + + page.index(index + 1); + + for (let i = index + 2; i < this.pages.length; i++) { + this.pages[i].index(i); + } + + if (!blank) { + // Listen for page overflow + page.onOverflow((overflowToken) => { + if (total < this.pages.length) { + this.pages[total].layout(this.source, overflowToken); + } else { + let newPage = this.addPage(); + newPage.layout(this.source, overflowToken); + } + }); + + page.onUnderflow(() => { + // console.log("underflow on", page.id); + }); + } + + this.total += 1; + + return page; + } + */ + + async clonePage(originalPage) { + let lastPage = this.pages[this.pages.length - 1]; + + let page = new Page(this.pagesArea, this.pageTemplate, false, this.hooks); + + this.pages.push(page); + + // Create the pages + page.create(undefined, lastPage && lastPage.element); + + page.index(this.total); + + await this.hooks.beforePageLayout.trigger(page, undefined, undefined, this); + this.emit("page", page); + + for (const className of originalPage.element.classList) { + if (className !== "pagedjs_left_page" && className !== "pagedjs_right_page") { + page.element.classList.add(className); + } + } + + await this.hooks.afterPageLayout.trigger(page.element, page, undefined, this); + this.emit("renderedPage", page); + } + + loadFonts() { + let fontPromises = []; + (document.fonts || []).forEach((fontFace) => { + if (fontFace.status !== "loaded") { + let fontLoaded = fontFace.load().then((r) => { + return fontFace.family; + }, (r) => { + console.warn("Failed to preload font-family:", fontFace.family); + return fontFace.family; + }); + fontPromises.push(fontLoaded); + } + }); + return Promise.all(fontPromises).catch((err) => { + console.warn(err); + }); + } + + destroy() { + this.pagesArea.remove(); + this.pageTemplate.remove(); + } + + } + + EventEmitter(Chunker.prototype); + + var syntax = {exports: {}}; + + var create$5 = {}; + + // + // list + // ┌──────┐ + // ┌──────────────┼─head │ + // │ │ tail─┼──────────────┐ + // │ └──────┘ │ + // ▼ ▼ + // item item item item + // ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ + // null ◀──┼─prev │◀───┼─prev │◀───┼─prev │◀───┼─prev │ + // │ next─┼───▶│ next─┼───▶│ next─┼───▶│ next─┼──▶ null + // ├──────┤ ├──────┤ ├──────┤ ├──────┤ + // │ data │ │ data │ │ data │ │ data │ + // └──────┘ └──────┘ └──────┘ └──────┘ + // + + function createItem(data) { + return { + prev: null, + next: null, + data: data + }; + } + + function allocateCursor(node, prev, next) { + var cursor; + + if (cursors !== null) { + cursor = cursors; + cursors = cursors.cursor; + cursor.prev = prev; + cursor.next = next; + cursor.cursor = node.cursor; + } else { + cursor = { + prev: prev, + next: next, + cursor: node.cursor + }; + } + + node.cursor = cursor; + + return cursor; + } + + function releaseCursor(node) { + var cursor = node.cursor; + + node.cursor = cursor.cursor; + cursor.prev = null; + cursor.next = null; + cursor.cursor = cursors; + cursors = cursor; + } + + var cursors = null; + var List$6 = function() { + this.cursor = null; + this.head = null; + this.tail = null; + }; + + List$6.createItem = createItem; + List$6.prototype.createItem = createItem; + + List$6.prototype.updateCursors = function(prevOld, prevNew, nextOld, nextNew) { + var cursor = this.cursor; + + while (cursor !== null) { + if (cursor.prev === prevOld) { + cursor.prev = prevNew; + } + + if (cursor.next === nextOld) { + cursor.next = nextNew; + } + + cursor = cursor.cursor; + } + }; + + List$6.prototype.getSize = function() { + var size = 0; + var cursor = this.head; + + while (cursor) { + size++; + cursor = cursor.next; + } + + return size; + }; + + List$6.prototype.fromArray = function(array) { + var cursor = null; + + this.head = null; + + for (var i = 0; i < array.length; i++) { + var item = createItem(array[i]); + + if (cursor !== null) { + cursor.next = item; + } else { + this.head = item; + } + + item.prev = cursor; + cursor = item; + } + + this.tail = cursor; + + return this; + }; + + List$6.prototype.toArray = function() { + var cursor = this.head; + var result = []; + + while (cursor) { + result.push(cursor.data); + cursor = cursor.next; + } + + return result; + }; + + List$6.prototype.toJSON = List$6.prototype.toArray; + + List$6.prototype.isEmpty = function() { + return this.head === null; + }; + + List$6.prototype.first = function() { + return this.head && this.head.data; + }; + + List$6.prototype.last = function() { + return this.tail && this.tail.data; + }; + + List$6.prototype.each = function(fn, context) { + var item; + + if (context === undefined) { + context = this; + } + + // push cursor + var cursor = allocateCursor(this, null, this.head); + + while (cursor.next !== null) { + item = cursor.next; + cursor.next = item.next; + + fn.call(context, item.data, item, this); + } + + // pop cursor + releaseCursor(this); + }; + + List$6.prototype.forEach = List$6.prototype.each; + + List$6.prototype.eachRight = function(fn, context) { + var item; + + if (context === undefined) { + context = this; + } + + // push cursor + var cursor = allocateCursor(this, this.tail, null); + + while (cursor.prev !== null) { + item = cursor.prev; + cursor.prev = item.prev; + + fn.call(context, item.data, item, this); + } + + // pop cursor + releaseCursor(this); + }; + + List$6.prototype.forEachRight = List$6.prototype.eachRight; + + List$6.prototype.reduce = function(fn, initialValue, context) { + var item; + + if (context === undefined) { + context = this; + } + + // push cursor + var cursor = allocateCursor(this, null, this.head); + var acc = initialValue; + + while (cursor.next !== null) { + item = cursor.next; + cursor.next = item.next; + + acc = fn.call(context, acc, item.data, item, this); + } + + // pop cursor + releaseCursor(this); + + return acc; + }; + + List$6.prototype.reduceRight = function(fn, initialValue, context) { + var item; + + if (context === undefined) { + context = this; + } + + // push cursor + var cursor = allocateCursor(this, this.tail, null); + var acc = initialValue; + + while (cursor.prev !== null) { + item = cursor.prev; + cursor.prev = item.prev; + + acc = fn.call(context, acc, item.data, item, this); + } + + // pop cursor + releaseCursor(this); + + return acc; + }; + + List$6.prototype.nextUntil = function(start, fn, context) { + if (start === null) { + return; + } + + var item; + + if (context === undefined) { + context = this; + } + + // push cursor + var cursor = allocateCursor(this, null, start); + + while (cursor.next !== null) { + item = cursor.next; + cursor.next = item.next; + + if (fn.call(context, item.data, item, this)) { + break; + } + } + + // pop cursor + releaseCursor(this); + }; + + List$6.prototype.prevUntil = function(start, fn, context) { + if (start === null) { + return; + } + + var item; + + if (context === undefined) { + context = this; + } + + // push cursor + var cursor = allocateCursor(this, start, null); + + while (cursor.prev !== null) { + item = cursor.prev; + cursor.prev = item.prev; + + if (fn.call(context, item.data, item, this)) { + break; + } + } + + // pop cursor + releaseCursor(this); + }; + + List$6.prototype.some = function(fn, context) { + var cursor = this.head; + + if (context === undefined) { + context = this; + } + + while (cursor !== null) { + if (fn.call(context, cursor.data, cursor, this)) { + return true; + } + + cursor = cursor.next; + } + + return false; + }; + + List$6.prototype.map = function(fn, context) { + var result = new List$6(); + var cursor = this.head; + + if (context === undefined) { + context = this; + } + + while (cursor !== null) { + result.appendData(fn.call(context, cursor.data, cursor, this)); + cursor = cursor.next; + } + + return result; + }; + + List$6.prototype.filter = function(fn, context) { + var result = new List$6(); + var cursor = this.head; + + if (context === undefined) { + context = this; + } + + while (cursor !== null) { + if (fn.call(context, cursor.data, cursor, this)) { + result.appendData(cursor.data); + } + cursor = cursor.next; + } + + return result; + }; + + List$6.prototype.clear = function() { + this.head = null; + this.tail = null; + }; + + List$6.prototype.copy = function() { + var result = new List$6(); + var cursor = this.head; + + while (cursor !== null) { + result.insert(createItem(cursor.data)); + cursor = cursor.next; + } + + return result; + }; + + List$6.prototype.prepend = function(item) { + // head + // ^ + // item + this.updateCursors(null, item, this.head, item); + + // insert to the beginning of the list + if (this.head !== null) { + // new item <- first item + this.head.prev = item; + + // new item -> first item + item.next = this.head; + } else { + // if list has no head, then it also has no tail + // in this case tail points to the new item + this.tail = item; + } + + // head always points to new item + this.head = item; + + return this; + }; + + List$6.prototype.prependData = function(data) { + return this.prepend(createItem(data)); + }; + + List$6.prototype.append = function(item) { + return this.insert(item); + }; + + List$6.prototype.appendData = function(data) { + return this.insert(createItem(data)); + }; + + List$6.prototype.insert = function(item, before) { + if (before !== undefined && before !== null) { + // prev before + // ^ + // item + this.updateCursors(before.prev, item, before, item); + + if (before.prev === null) { + // insert to the beginning of list + if (this.head !== before) { + throw new Error('before doesn\'t belong to list'); + } + + // since head points to before therefore list doesn't empty + // no need to check tail + this.head = item; + before.prev = item; + item.next = before; + + this.updateCursors(null, item); + } else { + + // insert between two items + before.prev.next = item; + item.prev = before.prev; + + before.prev = item; + item.next = before; + } + } else { + // tail + // ^ + // item + this.updateCursors(this.tail, item, null, item); + + // insert to the ending of the list + if (this.tail !== null) { + // last item -> new item + this.tail.next = item; + + // last item <- new item + item.prev = this.tail; + } else { + // if list has no tail, then it also has no head + // in this case head points to new item + this.head = item; + } + + // tail always points to new item + this.tail = item; + } + + return this; + }; + + List$6.prototype.insertData = function(data, before) { + return this.insert(createItem(data), before); + }; + + List$6.prototype.remove = function(item) { + // item + // ^ + // prev next + this.updateCursors(item, item.prev, item, item.next); + + if (item.prev !== null) { + item.prev.next = item.next; + } else { + if (this.head !== item) { + throw new Error('item doesn\'t belong to list'); + } + + this.head = item.next; + } + + if (item.next !== null) { + item.next.prev = item.prev; + } else { + if (this.tail !== item) { + throw new Error('item doesn\'t belong to list'); + } + + this.tail = item.prev; + } + + item.prev = null; + item.next = null; + + return item; + }; + + List$6.prototype.push = function(data) { + this.insert(createItem(data)); + }; + + List$6.prototype.pop = function() { + if (this.tail !== null) { + return this.remove(this.tail); + } + }; + + List$6.prototype.unshift = function(data) { + this.prepend(createItem(data)); + }; + + List$6.prototype.shift = function() { + if (this.head !== null) { + return this.remove(this.head); + } + }; + + List$6.prototype.prependList = function(list) { + return this.insertList(list, this.head); + }; + + List$6.prototype.appendList = function(list) { + return this.insertList(list); + }; + + List$6.prototype.insertList = function(list, before) { + // ignore empty lists + if (list.head === null) { + return this; + } + + if (before !== undefined && before !== null) { + this.updateCursors(before.prev, list.tail, before, list.head); + + // insert in the middle of dist list + if (before.prev !== null) { + // before.prev <-> list.head + before.prev.next = list.head; + list.head.prev = before.prev; + } else { + this.head = list.head; + } + + before.prev = list.tail; + list.tail.next = before; + } else { + this.updateCursors(this.tail, list.tail, null, list.head); + + // insert to end of the list + if (this.tail !== null) { + // if destination list has a tail, then it also has a head, + // but head doesn't change + + // dest tail -> source head + this.tail.next = list.head; + + // dest tail <- source head + list.head.prev = this.tail; + } else { + // if list has no a tail, then it also has no a head + // in this case points head to new item + this.head = list.head; + } + + // tail always start point to new item + this.tail = list.tail; + } + + list.head = null; + list.tail = null; + + return this; + }; + + List$6.prototype.replace = function(oldItem, newItemOrList) { + if ('head' in newItemOrList) { + this.insertList(newItemOrList, oldItem); + } else { + this.insert(newItemOrList, oldItem); + } + + this.remove(oldItem); + }; + + var List_1 = List$6; + + var createCustomError$3 = function createCustomError(name, message) { + // use Object.create(), because some VMs prevent setting line/column otherwise + // (iOS Safari 10 even throws an exception) + var error = Object.create(SyntaxError.prototype); + var errorStack = new Error(); + + error.name = name; + error.message = message; + + Object.defineProperty(error, 'stack', { + get: function() { + return (errorStack.stack || '').replace(/^(.+\n){1,3}/, name + ': ' + message + '\n'); + } + }); + + return error; + }; + + var createCustomError$2 = createCustomError$3; + var MAX_LINE_LENGTH = 100; + var OFFSET_CORRECTION = 60; + var TAB_REPLACEMENT = ' '; + + function sourceFragment(error, extraLines) { + function processLines(start, end) { + return lines.slice(start, end).map(function(line, idx) { + var num = String(start + idx + 1); + + while (num.length < maxNumLength) { + num = ' ' + num; + } + + return num + ' |' + line; + }).join('\n'); + } + + var lines = error.source.split(/\r\n?|\n|\f/); + var line = error.line; + var column = error.column; + var startLine = Math.max(1, line - extraLines) - 1; + var endLine = Math.min(line + extraLines, lines.length + 1); + var maxNumLength = Math.max(4, String(endLine).length) + 1; + var cutLeft = 0; + + // column correction according to replaced tab before column + column += (TAB_REPLACEMENT.length - 1) * (lines[line - 1].substr(0, column - 1).match(/\t/g) || []).length; + + if (column > MAX_LINE_LENGTH) { + cutLeft = column - OFFSET_CORRECTION + 3; + column = OFFSET_CORRECTION - 2; + } + + for (var i = startLine; i <= endLine; i++) { + if (i >= 0 && i < lines.length) { + lines[i] = lines[i].replace(/\t/g, TAB_REPLACEMENT); + lines[i] = + (cutLeft > 0 && lines[i].length > cutLeft ? '\u2026' : '') + + lines[i].substr(cutLeft, MAX_LINE_LENGTH - 2) + + (lines[i].length > cutLeft + MAX_LINE_LENGTH - 1 ? '\u2026' : ''); + } + } + + return [ + processLines(startLine, line), + new Array(column + maxNumLength + 2).join('-') + '^', + processLines(line, endLine) + ].filter(Boolean).join('\n'); + } + + var SyntaxError$4 = function(message, source, offset, line, column) { + var error = createCustomError$2('SyntaxError', message); + + error.source = source; + error.offset = offset; + error.line = line; + error.column = column; + + error.sourceFragment = function(extraLines) { + return sourceFragment(error, isNaN(extraLines) ? 0 : extraLines); + }; + Object.defineProperty(error, 'formattedMessage', { + get: function() { + return ( + 'Parse error: ' + error.message + '\n' + + sourceFragment(error, 2) + ); + } + }); + + // for backward capability + error.parseError = { + offset: offset, + line: line, + column: column + }; + + return error; + }; + + var _SyntaxError$1 = SyntaxError$4; + + // CSS Syntax Module Level 3 + // https://www.w3.org/TR/css-syntax-3/ + var TYPE$H = { + EOF: 0, // + Ident: 1, // + Function: 2, // + AtKeyword: 3, // + Hash: 4, // + String: 5, // + BadString: 6, // + Url: 7, // + BadUrl: 8, // + Delim: 9, // + Number: 10, // + Percentage: 11, // + Dimension: 12, // + WhiteSpace: 13, // + CDO: 14, // + CDC: 15, // + Colon: 16, // : + Semicolon: 17, // ; + Comma: 18, // , + LeftSquareBracket: 19, // <[-token> + RightSquareBracket: 20, // <]-token> + LeftParenthesis: 21, // <(-token> + RightParenthesis: 22, // <)-token> + LeftCurlyBracket: 23, // <{-token> + RightCurlyBracket: 24, // <}-token> + Comment: 25 + }; + + var NAME$3 = Object.keys(TYPE$H).reduce(function(result, key) { + result[TYPE$H[key]] = key; + return result; + }, {}); + + var _const = { + TYPE: TYPE$H, + NAME: NAME$3 + }; + + var EOF$1 = 0; + + // https://drafts.csswg.org/css-syntax-3/ + // § 4.2. Definitions + + // digit + // A code point between U+0030 DIGIT ZERO (0) and U+0039 DIGIT NINE (9). + function isDigit$5(code) { + return code >= 0x0030 && code <= 0x0039; + } + + // hex digit + // A digit, or a code point between U+0041 LATIN CAPITAL LETTER A (A) and U+0046 LATIN CAPITAL LETTER F (F), + // or a code point between U+0061 LATIN SMALL LETTER A (a) and U+0066 LATIN SMALL LETTER F (f). + function isHexDigit$4(code) { + return ( + isDigit$5(code) || // 0 .. 9 + (code >= 0x0041 && code <= 0x0046) || // A .. F + (code >= 0x0061 && code <= 0x0066) // a .. f + ); + } + + // uppercase letter + // A code point between U+0041 LATIN CAPITAL LETTER A (A) and U+005A LATIN CAPITAL LETTER Z (Z). + function isUppercaseLetter$1(code) { + return code >= 0x0041 && code <= 0x005A; + } + + // lowercase letter + // A code point between U+0061 LATIN SMALL LETTER A (a) and U+007A LATIN SMALL LETTER Z (z). + function isLowercaseLetter(code) { + return code >= 0x0061 && code <= 0x007A; + } + + // letter + // An uppercase letter or a lowercase letter. + function isLetter(code) { + return isUppercaseLetter$1(code) || isLowercaseLetter(code); + } + + // non-ASCII code point + // A code point with a value equal to or greater than U+0080 . + function isNonAscii(code) { + return code >= 0x0080; + } + + // name-start code point + // A letter, a non-ASCII code point, or U+005F LOW LINE (_). + function isNameStart(code) { + return isLetter(code) || isNonAscii(code) || code === 0x005F; + } + + // name code point + // A name-start code point, a digit, or U+002D HYPHEN-MINUS (-). + function isName$2(code) { + return isNameStart(code) || isDigit$5(code) || code === 0x002D; + } + + // non-printable code point + // A code point between U+0000 NULL and U+0008 BACKSPACE, or U+000B LINE TABULATION, + // or a code point between U+000E SHIFT OUT and U+001F INFORMATION SEPARATOR ONE, or U+007F DELETE. + function isNonPrintable(code) { + return ( + (code >= 0x0000 && code <= 0x0008) || + (code === 0x000B) || + (code >= 0x000E && code <= 0x001F) || + (code === 0x007F) + ); + } + + // newline + // U+000A LINE FEED. Note that U+000D CARRIAGE RETURN and U+000C FORM FEED are not included in this definition, + // as they are converted to U+000A LINE FEED during preprocessing. + // TODO: we doesn't do a preprocessing, so check a code point for U+000D CARRIAGE RETURN and U+000C FORM FEED + function isNewline$1(code) { + return code === 0x000A || code === 0x000D || code === 0x000C; + } + + // whitespace + // A newline, U+0009 CHARACTER TABULATION, or U+0020 SPACE. + function isWhiteSpace$2(code) { + return isNewline$1(code) || code === 0x0020 || code === 0x0009; + } + + // § 4.3.8. Check if two code points are a valid escape + function isValidEscape$2(first, second) { + // If the first code point is not U+005C REVERSE SOLIDUS (\), return false. + if (first !== 0x005C) { + return false; + } + + // Otherwise, if the second code point is a newline or EOF, return false. + if (isNewline$1(second) || second === EOF$1) { + return false; + } + + // Otherwise, return true. + return true; + } + + // § 4.3.9. Check if three code points would start an identifier + function isIdentifierStart$2(first, second, third) { + // Look at the first code point: + + // U+002D HYPHEN-MINUS + if (first === 0x002D) { + // If the second code point is a name-start code point or a U+002D HYPHEN-MINUS, + // or the second and third code points are a valid escape, return true. Otherwise, return false. + return ( + isNameStart(second) || + second === 0x002D || + isValidEscape$2(second, third) + ); + } + + // name-start code point + if (isNameStart(first)) { + // Return true. + return true; + } + + // U+005C REVERSE SOLIDUS (\) + if (first === 0x005C) { + // If the first and second code points are a valid escape, return true. Otherwise, return false. + return isValidEscape$2(first, second); + } + + // anything else + // Return false. + return false; + } + + // § 4.3.10. Check if three code points would start a number + function isNumberStart$1(first, second, third) { + // Look at the first code point: + + // U+002B PLUS SIGN (+) + // U+002D HYPHEN-MINUS (-) + if (first === 0x002B || first === 0x002D) { + // If the second code point is a digit, return true. + if (isDigit$5(second)) { + return 2; + } + + // Otherwise, if the second code point is a U+002E FULL STOP (.) + // and the third code point is a digit, return true. + // Otherwise, return false. + return second === 0x002E && isDigit$5(third) ? 3 : 0; + } + + // U+002E FULL STOP (.) + if (first === 0x002E) { + // If the second code point is a digit, return true. Otherwise, return false. + return isDigit$5(second) ? 2 : 0; + } + + // digit + if (isDigit$5(first)) { + // Return true. + return 1; + } + + // anything else + // Return false. + return 0; + } + + // + // Misc + // + + // detect BOM (https://en.wikipedia.org/wiki/Byte_order_mark) + function isBOM$2(code) { + // UTF-16BE + if (code === 0xFEFF) { + return 1; + } + + // UTF-16LE + if (code === 0xFFFE) { + return 1; + } + + return 0; + } + + // Fast code category + // + // https://drafts.csswg.org/css-syntax/#tokenizer-definitions + // > non-ASCII code point + // > A code point with a value equal to or greater than U+0080 + // > name-start code point + // > A letter, a non-ASCII code point, or U+005F LOW LINE (_). + // > name code point + // > A name-start code point, a digit, or U+002D HYPHEN-MINUS (-) + // That means only ASCII code points has a special meaning and we define a maps for 0..127 codes only + var CATEGORY = new Array(0x80); + charCodeCategory$1.Eof = 0x80; + charCodeCategory$1.WhiteSpace = 0x82; + charCodeCategory$1.Digit = 0x83; + charCodeCategory$1.NameStart = 0x84; + charCodeCategory$1.NonPrintable = 0x85; + + for (var i = 0; i < CATEGORY.length; i++) { + switch (true) { + case isWhiteSpace$2(i): + CATEGORY[i] = charCodeCategory$1.WhiteSpace; + break; + + case isDigit$5(i): + CATEGORY[i] = charCodeCategory$1.Digit; + break; + + case isNameStart(i): + CATEGORY[i] = charCodeCategory$1.NameStart; + break; + + case isNonPrintable(i): + CATEGORY[i] = charCodeCategory$1.NonPrintable; + break; + + default: + CATEGORY[i] = i || charCodeCategory$1.Eof; + } + } + + function charCodeCategory$1(code) { + return code < 0x80 ? CATEGORY[code] : charCodeCategory$1.NameStart; + } + var charCodeDefinitions$1 = { + isDigit: isDigit$5, + isHexDigit: isHexDigit$4, + isUppercaseLetter: isUppercaseLetter$1, + isLowercaseLetter: isLowercaseLetter, + isLetter: isLetter, + isNonAscii: isNonAscii, + isNameStart: isNameStart, + isName: isName$2, + isNonPrintable: isNonPrintable, + isNewline: isNewline$1, + isWhiteSpace: isWhiteSpace$2, + isValidEscape: isValidEscape$2, + isIdentifierStart: isIdentifierStart$2, + isNumberStart: isNumberStart$1, + + isBOM: isBOM$2, + charCodeCategory: charCodeCategory$1 + }; + + var charCodeDef = charCodeDefinitions$1; + var isDigit$4 = charCodeDef.isDigit; + var isHexDigit$3 = charCodeDef.isHexDigit; + var isUppercaseLetter = charCodeDef.isUppercaseLetter; + var isName$1 = charCodeDef.isName; + var isWhiteSpace$1 = charCodeDef.isWhiteSpace; + var isValidEscape$1 = charCodeDef.isValidEscape; + + function getCharCode(source, offset) { + return offset < source.length ? source.charCodeAt(offset) : 0; + } + + function getNewlineLength$1(source, offset, code) { + if (code === 13 /* \r */ && getCharCode(source, offset + 1) === 10 /* \n */) { + return 2; + } + + return 1; + } + + function cmpChar$5(testStr, offset, referenceCode) { + var code = testStr.charCodeAt(offset); + + // code.toLowerCase() for A..Z + if (isUppercaseLetter(code)) { + code = code | 32; + } + + return code === referenceCode; + } + + function cmpStr$6(testStr, start, end, referenceStr) { + if (end - start !== referenceStr.length) { + return false; + } + + if (start < 0 || end > testStr.length) { + return false; + } + + for (var i = start; i < end; i++) { + var testCode = testStr.charCodeAt(i); + var referenceCode = referenceStr.charCodeAt(i - start); + + // testCode.toLowerCase() for A..Z + if (isUppercaseLetter(testCode)) { + testCode = testCode | 32; + } + + if (testCode !== referenceCode) { + return false; + } + } + + return true; + } + + function findWhiteSpaceStart$1(source, offset) { + for (; offset >= 0; offset--) { + if (!isWhiteSpace$1(source.charCodeAt(offset))) { + break; + } + } + + return offset + 1; + } + + function findWhiteSpaceEnd$1(source, offset) { + for (; offset < source.length; offset++) { + if (!isWhiteSpace$1(source.charCodeAt(offset))) { + break; + } + } + + return offset; + } + + function findDecimalNumberEnd(source, offset) { + for (; offset < source.length; offset++) { + if (!isDigit$4(source.charCodeAt(offset))) { + break; + } + } + + return offset; + } + + // § 4.3.7. Consume an escaped code point + function consumeEscaped$1(source, offset) { + // It assumes that the U+005C REVERSE SOLIDUS (\) has already been consumed and + // that the next input code point has already been verified to be part of a valid escape. + offset += 2; + + // hex digit + if (isHexDigit$3(getCharCode(source, offset - 1))) { + // Consume as many hex digits as possible, but no more than 5. + // Note that this means 1-6 hex digits have been consumed in total. + for (var maxOffset = Math.min(source.length, offset + 5); offset < maxOffset; offset++) { + if (!isHexDigit$3(getCharCode(source, offset))) { + break; + } + } + + // If the next input code point is whitespace, consume it as well. + var code = getCharCode(source, offset); + if (isWhiteSpace$1(code)) { + offset += getNewlineLength$1(source, offset, code); + } + } + + return offset; + } + + // §4.3.11. Consume a name + // Note: This algorithm does not do the verification of the first few code points that are necessary + // to ensure the returned code points would constitute an . If that is the intended use, + // ensure that the stream starts with an identifier before calling this algorithm. + function consumeName$1(source, offset) { + // Let result initially be an empty string. + // Repeatedly consume the next input code point from the stream: + for (; offset < source.length; offset++) { + var code = source.charCodeAt(offset); + + // name code point + if (isName$1(code)) { + // Append the code point to result. + continue; + } + + // the stream starts with a valid escape + if (isValidEscape$1(code, getCharCode(source, offset + 1))) { + // Consume an escaped code point. Append the returned code point to result. + offset = consumeEscaped$1(source, offset) - 1; + continue; + } + + // anything else + // Reconsume the current input code point. Return result. + break; + } + + return offset; + } + + // §4.3.12. Consume a number + function consumeNumber$5(source, offset) { + var code = source.charCodeAt(offset); + + // 2. If the next input code point is U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-), + // consume it and append it to repr. + if (code === 0x002B || code === 0x002D) { + code = source.charCodeAt(offset += 1); + } + + // 3. While the next input code point is a digit, consume it and append it to repr. + if (isDigit$4(code)) { + offset = findDecimalNumberEnd(source, offset + 1); + code = source.charCodeAt(offset); + } + + // 4. If the next 2 input code points are U+002E FULL STOP (.) followed by a digit, then: + if (code === 0x002E && isDigit$4(source.charCodeAt(offset + 1))) { + // 4.1 Consume them. + // 4.2 Append them to repr. + code = source.charCodeAt(offset += 2); + + // 4.3 Set type to "number". + // TODO + + // 4.4 While the next input code point is a digit, consume it and append it to repr. + + offset = findDecimalNumberEnd(source, offset); + } + + // 5. If the next 2 or 3 input code points are U+0045 LATIN CAPITAL LETTER E (E) + // or U+0065 LATIN SMALL LETTER E (e), ... , followed by a digit, then: + if (cmpChar$5(source, offset, 101 /* e */)) { + var sign = 0; + code = source.charCodeAt(offset + 1); + + // ... optionally followed by U+002D HYPHEN-MINUS (-) or U+002B PLUS SIGN (+) ... + if (code === 0x002D || code === 0x002B) { + sign = 1; + code = source.charCodeAt(offset + 2); + } + + // ... followed by a digit + if (isDigit$4(code)) { + // 5.1 Consume them. + // 5.2 Append them to repr. + + // 5.3 Set type to "number". + // TODO + + // 5.4 While the next input code point is a digit, consume it and append it to repr. + offset = findDecimalNumberEnd(source, offset + 1 + sign + 1); + } + } + + return offset; + } + + // § 4.3.14. Consume the remnants of a bad url + // ... its sole use is to consume enough of the input stream to reach a recovery point + // where normal tokenizing can resume. + function consumeBadUrlRemnants$1(source, offset) { + // Repeatedly consume the next input code point from the stream: + for (; offset < source.length; offset++) { + var code = source.charCodeAt(offset); + + // U+0029 RIGHT PARENTHESIS ()) + // EOF + if (code === 0x0029) { + // Return. + offset++; + break; + } + + if (isValidEscape$1(code, getCharCode(source, offset + 1))) { + // Consume an escaped code point. + // Note: This allows an escaped right parenthesis ("\)") to be encountered + // without ending the . This is otherwise identical to + // the "anything else" clause. + offset = consumeEscaped$1(source, offset); + } + } + + return offset; + } + + var utils$2 = { + consumeEscaped: consumeEscaped$1, + consumeName: consumeName$1, + consumeNumber: consumeNumber$5, + consumeBadUrlRemnants: consumeBadUrlRemnants$1, + + cmpChar: cmpChar$5, + cmpStr: cmpStr$6, + + getNewlineLength: getNewlineLength$1, + findWhiteSpaceStart: findWhiteSpaceStart$1, + findWhiteSpaceEnd: findWhiteSpaceEnd$1 + }; + + var constants$2 = _const; + var TYPE$G = constants$2.TYPE; + var NAME$2 = constants$2.NAME; + + var utils$1 = utils$2; + var cmpStr$5 = utils$1.cmpStr; + + var EOF = TYPE$G.EOF; + var WHITESPACE$c = TYPE$G.WhiteSpace; + var COMMENT$a = TYPE$G.Comment; + + var OFFSET_MASK$1 = 0x00FFFFFF; + var TYPE_SHIFT$1 = 24; + + var TokenStream$4 = function() { + this.offsetAndType = null; + this.balance = null; + + this.reset(); + }; + + TokenStream$4.prototype = { + reset: function() { + this.eof = false; + this.tokenIndex = -1; + this.tokenType = 0; + this.tokenStart = this.firstCharOffset; + this.tokenEnd = this.firstCharOffset; + }, + + lookupType: function(offset) { + offset += this.tokenIndex; + + if (offset < this.tokenCount) { + return this.offsetAndType[offset] >> TYPE_SHIFT$1; + } + + return EOF; + }, + lookupOffset: function(offset) { + offset += this.tokenIndex; + + if (offset < this.tokenCount) { + return this.offsetAndType[offset - 1] & OFFSET_MASK$1; + } + + return this.source.length; + }, + lookupValue: function(offset, referenceStr) { + offset += this.tokenIndex; + + if (offset < this.tokenCount) { + return cmpStr$5( + this.source, + this.offsetAndType[offset - 1] & OFFSET_MASK$1, + this.offsetAndType[offset] & OFFSET_MASK$1, + referenceStr + ); + } + + return false; + }, + getTokenStart: function(tokenIndex) { + if (tokenIndex === this.tokenIndex) { + return this.tokenStart; + } + + if (tokenIndex > 0) { + return tokenIndex < this.tokenCount + ? this.offsetAndType[tokenIndex - 1] & OFFSET_MASK$1 + : this.offsetAndType[this.tokenCount] & OFFSET_MASK$1; + } + + return this.firstCharOffset; + }, + + // TODO: -> skipUntilBalanced + getRawLength: function(startToken, mode) { + var cursor = startToken; + var balanceEnd; + var offset = this.offsetAndType[Math.max(cursor - 1, 0)] & OFFSET_MASK$1; + var type; + + loop: + for (; cursor < this.tokenCount; cursor++) { + balanceEnd = this.balance[cursor]; + + // stop scanning on balance edge that points to offset before start token + if (balanceEnd < startToken) { + break loop; + } + + type = this.offsetAndType[cursor] >> TYPE_SHIFT$1; + + // check token is stop type + switch (mode(type, this.source, offset)) { + case 1: + break loop; + + case 2: + cursor++; + break loop; + + default: + // fast forward to the end of balanced block + if (this.balance[balanceEnd] === cursor) { + cursor = balanceEnd; + } + + offset = this.offsetAndType[cursor] & OFFSET_MASK$1; + } + } + + return cursor - this.tokenIndex; + }, + isBalanceEdge: function(pos) { + return this.balance[this.tokenIndex] < pos; + }, + isDelim: function(code, offset) { + if (offset) { + return ( + this.lookupType(offset) === TYPE$G.Delim && + this.source.charCodeAt(this.lookupOffset(offset)) === code + ); + } + + return ( + this.tokenType === TYPE$G.Delim && + this.source.charCodeAt(this.tokenStart) === code + ); + }, + + getTokenValue: function() { + return this.source.substring(this.tokenStart, this.tokenEnd); + }, + getTokenLength: function() { + return this.tokenEnd - this.tokenStart; + }, + substrToCursor: function(start) { + return this.source.substring(start, this.tokenStart); + }, + + skipWS: function() { + for (var i = this.tokenIndex, skipTokenCount = 0; i < this.tokenCount; i++, skipTokenCount++) { + if ((this.offsetAndType[i] >> TYPE_SHIFT$1) !== WHITESPACE$c) { + break; + } + } + + if (skipTokenCount > 0) { + this.skip(skipTokenCount); + } + }, + skipSC: function() { + while (this.tokenType === WHITESPACE$c || this.tokenType === COMMENT$a) { + this.next(); + } + }, + skip: function(tokenCount) { + var next = this.tokenIndex + tokenCount; + + if (next < this.tokenCount) { + this.tokenIndex = next; + this.tokenStart = this.offsetAndType[next - 1] & OFFSET_MASK$1; + next = this.offsetAndType[next]; + this.tokenType = next >> TYPE_SHIFT$1; + this.tokenEnd = next & OFFSET_MASK$1; + } else { + this.tokenIndex = this.tokenCount; + this.next(); + } + }, + next: function() { + var next = this.tokenIndex + 1; + + if (next < this.tokenCount) { + this.tokenIndex = next; + this.tokenStart = this.tokenEnd; + next = this.offsetAndType[next]; + this.tokenType = next >> TYPE_SHIFT$1; + this.tokenEnd = next & OFFSET_MASK$1; + } else { + this.tokenIndex = this.tokenCount; + this.eof = true; + this.tokenType = EOF; + this.tokenStart = this.tokenEnd = this.source.length; + } + }, + + forEachToken(fn) { + for (var i = 0, offset = this.firstCharOffset; i < this.tokenCount; i++) { + var start = offset; + var item = this.offsetAndType[i]; + var end = item & OFFSET_MASK$1; + var type = item >> TYPE_SHIFT$1; + + offset = end; + + fn(type, start, end, i); + } + }, + + dump() { + var tokens = new Array(this.tokenCount); + + this.forEachToken((type, start, end, index) => { + tokens[index] = { + idx: index, + type: NAME$2[type], + chunk: this.source.substring(start, end), + balance: this.balance[index] + }; + }); + + return tokens; + } + }; + + var TokenStream_1 = TokenStream$4; + + function noop$3(value) { + return value; + } + + function generateMultiplier(multiplier) { + if (multiplier.min === 0 && multiplier.max === 0) { + return '*'; + } + + if (multiplier.min === 0 && multiplier.max === 1) { + return '?'; + } + + if (multiplier.min === 1 && multiplier.max === 0) { + return multiplier.comma ? '#' : '+'; + } + + if (multiplier.min === 1 && multiplier.max === 1) { + return ''; + } + + return ( + (multiplier.comma ? '#' : '') + + (multiplier.min === multiplier.max + ? '{' + multiplier.min + '}' + : '{' + multiplier.min + ',' + (multiplier.max !== 0 ? multiplier.max : '') + '}' + ) + ); + } + + function generateTypeOpts(node) { + switch (node.type) { + case 'Range': + return ( + ' [' + + (node.min === null ? '-∞' : node.min) + + ',' + + (node.max === null ? '∞' : node.max) + + ']' + ); + + default: + throw new Error('Unknown node type `' + node.type + '`'); + } + } + + function generateSequence(node, decorate, forceBraces, compact) { + var combinator = node.combinator === ' ' || compact ? node.combinator : ' ' + node.combinator + ' '; + var result = node.terms.map(function(term) { + return generate$2(term, decorate, forceBraces, compact); + }).join(combinator); + + if (node.explicit || forceBraces) { + result = (compact || result[0] === ',' ? '[' : '[ ') + result + (compact ? ']' : ' ]'); + } + + return result; + } + + function generate$2(node, decorate, forceBraces, compact) { + var result; + + switch (node.type) { + case 'Group': + result = + generateSequence(node, decorate, forceBraces, compact) + + (node.disallowEmpty ? '!' : ''); + break; + + case 'Multiplier': + // return since node is a composition + return ( + generate$2(node.term, decorate, forceBraces, compact) + + decorate(generateMultiplier(node), node) + ); + + case 'Type': + result = '<' + node.name + (node.opts ? decorate(generateTypeOpts(node.opts), node.opts) : '') + '>'; + break; + + case 'Property': + result = '<\'' + node.name + '\'>'; + break; + + case 'Keyword': + result = node.name; + break; + + case 'AtKeyword': + result = '@' + node.name; + break; + + case 'Function': + result = node.name + '('; + break; + + case 'String': + case 'Token': + result = node.value; + break; + + case 'Comma': + result = ','; + break; + + default: + throw new Error('Unknown node type `' + node.type + '`'); + } + + return decorate(result, node); + } + + var generate_1 = function(node, options) { + var decorate = noop$3; + var forceBraces = false; + var compact = false; + + if (typeof options === 'function') { + decorate = options; + } else if (options) { + forceBraces = Boolean(options.forceBraces); + compact = Boolean(options.compact); + if (typeof options.decorate === 'function') { + decorate = options.decorate; + } + } + + return generate$2(node, decorate, forceBraces, compact); + }; + + const createCustomError$1 = createCustomError$3; + const generate$1 = generate_1; + const defaultLoc = { offset: 0, line: 1, column: 1 }; + + function locateMismatch(matchResult, node) { + const tokens = matchResult.tokens; + const longestMatch = matchResult.longestMatch; + const mismatchNode = longestMatch < tokens.length ? tokens[longestMatch].node || null : null; + const badNode = mismatchNode !== node ? mismatchNode : null; + let mismatchOffset = 0; + let mismatchLength = 0; + let entries = 0; + let css = ''; + let start; + let end; + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i].value; + + if (i === longestMatch) { + mismatchLength = token.length; + mismatchOffset = css.length; + } + + if (badNode !== null && tokens[i].node === badNode) { + if (i <= longestMatch) { + entries++; + } else { + entries = 0; + } + } + + css += token; + } + + if (longestMatch === tokens.length || entries > 1) { // last + start = fromLoc(badNode || node, 'end') || buildLoc(defaultLoc, css); + end = buildLoc(start); + } else { + start = fromLoc(badNode, 'start') || + buildLoc(fromLoc(node, 'start') || defaultLoc, css.slice(0, mismatchOffset)); + end = fromLoc(badNode, 'end') || + buildLoc(start, css.substr(mismatchOffset, mismatchLength)); + } + + return { + css, + mismatchOffset, + mismatchLength, + start, + end + }; + } + + function fromLoc(node, point) { + const value = node && node.loc && node.loc[point]; + + if (value) { + return 'line' in value ? buildLoc(value) : value; + } + + return null; + } + + function buildLoc({ offset, line, column }, extra) { + const loc = { + offset, + line, + column + }; + + if (extra) { + const lines = extra.split(/\n|\r\n?|\f/); + + loc.offset += extra.length; + loc.line += lines.length - 1; + loc.column = lines.length === 1 ? loc.column + extra.length : lines.pop().length + 1; + } + + return loc; + } + + const SyntaxReferenceError$1 = function(type, referenceName) { + const error = createCustomError$1( + 'SyntaxReferenceError', + type + (referenceName ? ' `' + referenceName + '`' : '') + ); + + error.reference = referenceName; + + return error; + }; + + const SyntaxMatchError$1 = function(message, syntax, node, matchResult) { + const error = createCustomError$1('SyntaxMatchError', message); + const { + css, + mismatchOffset, + mismatchLength, + start, + end + } = locateMismatch(matchResult, node); + + error.rawMessage = message; + error.syntax = syntax ? generate$1(syntax) : ''; + error.css = css; + error.mismatchOffset = mismatchOffset; + error.mismatchLength = mismatchLength; + error.message = message + '\n' + + ' syntax: ' + error.syntax + '\n' + + ' value: ' + (css || '') + '\n' + + ' --------' + new Array(error.mismatchOffset + 1).join('-') + '^'; + + Object.assign(error, start); + error.loc = { + source: (node && node.loc && node.loc.source) || '', + start, + end + }; + + return error; + }; + + var error = { + SyntaxReferenceError: SyntaxReferenceError$1, + SyntaxMatchError: SyntaxMatchError$1 + }; + + var hasOwnProperty$7 = Object.prototype.hasOwnProperty; + var keywords$1 = Object.create(null); + var properties$1 = Object.create(null); + var HYPHENMINUS$5 = 45; // '-'.charCodeAt() + + function isCustomProperty$1(str, offset) { + offset = offset || 0; + + return str.length - offset >= 2 && + str.charCodeAt(offset) === HYPHENMINUS$5 && + str.charCodeAt(offset + 1) === HYPHENMINUS$5; + } + + function getVendorPrefix(str, offset) { + offset = offset || 0; + + // verdor prefix should be at least 3 chars length + if (str.length - offset >= 3) { + // vendor prefix starts with hyper minus following non-hyper minus + if (str.charCodeAt(offset) === HYPHENMINUS$5 && + str.charCodeAt(offset + 1) !== HYPHENMINUS$5) { + // vendor prefix should contain a hyper minus at the ending + var secondDashIndex = str.indexOf('-', offset + 2); + + if (secondDashIndex !== -1) { + return str.substring(offset, secondDashIndex + 1); + } + } + } + + return ''; + } + + function getKeywordDescriptor(keyword) { + if (hasOwnProperty$7.call(keywords$1, keyword)) { + return keywords$1[keyword]; + } + + var name = keyword.toLowerCase(); + + if (hasOwnProperty$7.call(keywords$1, name)) { + return keywords$1[keyword] = keywords$1[name]; + } + + var custom = isCustomProperty$1(name, 0); + var vendor = !custom ? getVendorPrefix(name, 0) : ''; + + return keywords$1[keyword] = Object.freeze({ + basename: name.substr(vendor.length), + name: name, + vendor: vendor, + prefix: vendor, + custom: custom + }); + } + + function getPropertyDescriptor(property) { + if (hasOwnProperty$7.call(properties$1, property)) { + return properties$1[property]; + } + + var name = property; + var hack = property[0]; + + if (hack === '/') { + hack = property[1] === '/' ? '//' : '/'; + } else if (hack !== '_' && + hack !== '*' && + hack !== '$' && + hack !== '#' && + hack !== '+' && + hack !== '&') { + hack = ''; + } + + var custom = isCustomProperty$1(name, hack.length); + + // re-use result when possible (the same as for lower case) + if (!custom) { + name = name.toLowerCase(); + if (hasOwnProperty$7.call(properties$1, name)) { + return properties$1[property] = properties$1[name]; + } + } + + var vendor = !custom ? getVendorPrefix(name, hack.length) : ''; + var prefix = name.substr(0, hack.length + vendor.length); + + return properties$1[property] = Object.freeze({ + basename: name.substr(prefix.length), + name: name.substr(hack.length), + hack: hack, + vendor: vendor, + prefix: prefix, + custom: custom + }); + } + + var names$2 = { + keyword: getKeywordDescriptor, + property: getPropertyDescriptor, + isCustomProperty: isCustomProperty$1, + vendorPrefix: getVendorPrefix + }; + + var MIN_SIZE = 16 * 1024; + var SafeUint32Array = typeof Uint32Array !== 'undefined' ? Uint32Array : Array; // fallback on Array when TypedArray is not supported + + var adoptBuffer$2 = function adoptBuffer(buffer, size) { + if (buffer === null || buffer.length < size) { + return new SafeUint32Array(Math.max(size + 1024, MIN_SIZE)); + } + + return buffer; + }; + + var TokenStream$3 = TokenStream_1; + var adoptBuffer$1 = adoptBuffer$2; + + var constants$1 = _const; + var TYPE$F = constants$1.TYPE; + + var charCodeDefinitions = charCodeDefinitions$1; + var isNewline = charCodeDefinitions.isNewline; + var isName = charCodeDefinitions.isName; + var isValidEscape = charCodeDefinitions.isValidEscape; + var isNumberStart = charCodeDefinitions.isNumberStart; + var isIdentifierStart$1 = charCodeDefinitions.isIdentifierStart; + var charCodeCategory = charCodeDefinitions.charCodeCategory; + var isBOM$1 = charCodeDefinitions.isBOM; + + var utils = utils$2; + var cmpStr$4 = utils.cmpStr; + var getNewlineLength = utils.getNewlineLength; + var findWhiteSpaceEnd = utils.findWhiteSpaceEnd; + var consumeEscaped = utils.consumeEscaped; + var consumeName = utils.consumeName; + var consumeNumber$4 = utils.consumeNumber; + var consumeBadUrlRemnants = utils.consumeBadUrlRemnants; + + var OFFSET_MASK = 0x00FFFFFF; + var TYPE_SHIFT = 24; + + function tokenize$3(source, stream) { + function getCharCode(offset) { + return offset < sourceLength ? source.charCodeAt(offset) : 0; + } + + // § 4.3.3. Consume a numeric token + function consumeNumericToken() { + // Consume a number and let number be the result. + offset = consumeNumber$4(source, offset); + + // If the next 3 input code points would start an identifier, then: + if (isIdentifierStart$1(getCharCode(offset), getCharCode(offset + 1), getCharCode(offset + 2))) { + // Create a with the same value and type flag as number, and a unit set initially to the empty string. + // Consume a name. Set the ’s unit to the returned value. + // Return the . + type = TYPE$F.Dimension; + offset = consumeName(source, offset); + return; + } + + // Otherwise, if the next input code point is U+0025 PERCENTAGE SIGN (%), consume it. + if (getCharCode(offset) === 0x0025) { + // Create a with the same value as number, and return it. + type = TYPE$F.Percentage; + offset++; + return; + } + + // Otherwise, create a with the same value and type flag as number, and return it. + type = TYPE$F.Number; + } + + // § 4.3.4. Consume an ident-like token + function consumeIdentLikeToken() { + const nameStartOffset = offset; + + // Consume a name, and let string be the result. + offset = consumeName(source, offset); + + // If string’s value is an ASCII case-insensitive match for "url", + // and the next input code point is U+0028 LEFT PARENTHESIS ((), consume it. + if (cmpStr$4(source, nameStartOffset, offset, 'url') && getCharCode(offset) === 0x0028) { + // While the next two input code points are whitespace, consume the next input code point. + offset = findWhiteSpaceEnd(source, offset + 1); + + // If the next one or two input code points are U+0022 QUOTATION MARK ("), U+0027 APOSTROPHE ('), + // or whitespace followed by U+0022 QUOTATION MARK (") or U+0027 APOSTROPHE ('), + // then create a with its value set to string and return it. + if (getCharCode(offset) === 0x0022 || + getCharCode(offset) === 0x0027) { + type = TYPE$F.Function; + offset = nameStartOffset + 4; + return; + } + + // Otherwise, consume a url token, and return it. + consumeUrlToken(); + return; + } + + // Otherwise, if the next input code point is U+0028 LEFT PARENTHESIS ((), consume it. + // Create a with its value set to string and return it. + if (getCharCode(offset) === 0x0028) { + type = TYPE$F.Function; + offset++; + return; + } + + // Otherwise, create an with its value set to string and return it. + type = TYPE$F.Ident; + } + + // § 4.3.5. Consume a string token + function consumeStringToken(endingCodePoint) { + // This algorithm may be called with an ending code point, which denotes the code point + // that ends the string. If an ending code point is not specified, + // the current input code point is used. + if (!endingCodePoint) { + endingCodePoint = getCharCode(offset++); + } + + // Initially create a with its value set to the empty string. + type = TYPE$F.String; + + // Repeatedly consume the next input code point from the stream: + for (; offset < source.length; offset++) { + var code = source.charCodeAt(offset); + + switch (charCodeCategory(code)) { + // ending code point + case endingCodePoint: + // Return the . + offset++; + return; + + // EOF + case charCodeCategory.Eof: + // This is a parse error. Return the . + return; + + // newline + case charCodeCategory.WhiteSpace: + if (isNewline(code)) { + // This is a parse error. Reconsume the current input code point, + // create a , and return it. + offset += getNewlineLength(source, offset, code); + type = TYPE$F.BadString; + return; + } + break; + + // U+005C REVERSE SOLIDUS (\) + case 0x005C: + // If the next input code point is EOF, do nothing. + if (offset === source.length - 1) { + break; + } + + var nextCode = getCharCode(offset + 1); + + // Otherwise, if the next input code point is a newline, consume it. + if (isNewline(nextCode)) { + offset += getNewlineLength(source, offset + 1, nextCode); + } else if (isValidEscape(code, nextCode)) { + // Otherwise, (the stream starts with a valid escape) consume + // an escaped code point and append the returned code point to + // the ’s value. + offset = consumeEscaped(source, offset) - 1; + } + break; + + // anything else + // Append the current input code point to the ’s value. + } + } + } + + // § 4.3.6. Consume a url token + // Note: This algorithm assumes that the initial "url(" has already been consumed. + // This algorithm also assumes that it’s being called to consume an "unquoted" value, like url(foo). + // A quoted value, like url("foo"), is parsed as a . Consume an ident-like token + // automatically handles this distinction; this algorithm shouldn’t be called directly otherwise. + function consumeUrlToken() { + // Initially create a with its value set to the empty string. + type = TYPE$F.Url; + + // Consume as much whitespace as possible. + offset = findWhiteSpaceEnd(source, offset); + + // Repeatedly consume the next input code point from the stream: + for (; offset < source.length; offset++) { + var code = source.charCodeAt(offset); + + switch (charCodeCategory(code)) { + // U+0029 RIGHT PARENTHESIS ()) + case 0x0029: + // Return the . + offset++; + return; + + // EOF + case charCodeCategory.Eof: + // This is a parse error. Return the . + return; + + // whitespace + case charCodeCategory.WhiteSpace: + // Consume as much whitespace as possible. + offset = findWhiteSpaceEnd(source, offset); + + // If the next input code point is U+0029 RIGHT PARENTHESIS ()) or EOF, + // consume it and return the + // (if EOF was encountered, this is a parse error); + if (getCharCode(offset) === 0x0029 || offset >= source.length) { + if (offset < source.length) { + offset++; + } + return; + } + + // otherwise, consume the remnants of a bad url, create a , + // and return it. + offset = consumeBadUrlRemnants(source, offset); + type = TYPE$F.BadUrl; + return; + + // U+0022 QUOTATION MARK (") + // U+0027 APOSTROPHE (') + // U+0028 LEFT PARENTHESIS (() + // non-printable code point + case 0x0022: + case 0x0027: + case 0x0028: + case charCodeCategory.NonPrintable: + // This is a parse error. Consume the remnants of a bad url, + // create a , and return it. + offset = consumeBadUrlRemnants(source, offset); + type = TYPE$F.BadUrl; + return; + + // U+005C REVERSE SOLIDUS (\) + case 0x005C: + // If the stream starts with a valid escape, consume an escaped code point and + // append the returned code point to the ’s value. + if (isValidEscape(code, getCharCode(offset + 1))) { + offset = consumeEscaped(source, offset) - 1; + break; + } + + // Otherwise, this is a parse error. Consume the remnants of a bad url, + // create a , and return it. + offset = consumeBadUrlRemnants(source, offset); + type = TYPE$F.BadUrl; + return; + + // anything else + // Append the current input code point to the ’s value. + } + } + } + + if (!stream) { + stream = new TokenStream$3(); + } + + // ensure source is a string + source = String(source || ''); + + var sourceLength = source.length; + var offsetAndType = adoptBuffer$1(stream.offsetAndType, sourceLength + 1); // +1 because of eof-token + var balance = adoptBuffer$1(stream.balance, sourceLength + 1); + var tokenCount = 0; + var start = isBOM$1(getCharCode(0)); + var offset = start; + var balanceCloseType = 0; + var balanceStart = 0; + var balancePrev = 0; + + // https://drafts.csswg.org/css-syntax-3/#consume-token + // § 4.3.1. Consume a token + while (offset < sourceLength) { + var code = source.charCodeAt(offset); + var type = 0; + + balance[tokenCount] = sourceLength; + + switch (charCodeCategory(code)) { + // whitespace + case charCodeCategory.WhiteSpace: + // Consume as much whitespace as possible. Return a . + type = TYPE$F.WhiteSpace; + offset = findWhiteSpaceEnd(source, offset + 1); + break; + + // U+0022 QUOTATION MARK (") + case 0x0022: + // Consume a string token and return it. + consumeStringToken(); + break; + + // U+0023 NUMBER SIGN (#) + case 0x0023: + // If the next input code point is a name code point or the next two input code points are a valid escape, then: + if (isName(getCharCode(offset + 1)) || isValidEscape(getCharCode(offset + 1), getCharCode(offset + 2))) { + // Create a . + type = TYPE$F.Hash; + + // If the next 3 input code points would start an identifier, set the ’s type flag to "id". + // if (isIdentifierStart(getCharCode(offset + 1), getCharCode(offset + 2), getCharCode(offset + 3))) { + // // TODO: set id flag + // } + + // Consume a name, and set the ’s value to the returned string. + offset = consumeName(source, offset + 1); + + // Return the . + } else { + // Otherwise, return a with its value set to the current input code point. + type = TYPE$F.Delim; + offset++; + } + + break; + + // U+0027 APOSTROPHE (') + case 0x0027: + // Consume a string token and return it. + consumeStringToken(); + break; + + // U+0028 LEFT PARENTHESIS (() + case 0x0028: + // Return a <(-token>. + type = TYPE$F.LeftParenthesis; + offset++; + break; + + // U+0029 RIGHT PARENTHESIS ()) + case 0x0029: + // Return a <)-token>. + type = TYPE$F.RightParenthesis; + offset++; + break; + + // U+002B PLUS SIGN (+) + case 0x002B: + // If the input stream starts with a number, ... + if (isNumberStart(code, getCharCode(offset + 1), getCharCode(offset + 2))) { + // ... reconsume the current input code point, consume a numeric token, and return it. + consumeNumericToken(); + } else { + // Otherwise, return a with its value set to the current input code point. + type = TYPE$F.Delim; + offset++; + } + break; + + // U+002C COMMA (,) + case 0x002C: + // Return a . + type = TYPE$F.Comma; + offset++; + break; + + // U+002D HYPHEN-MINUS (-) + case 0x002D: + // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it. + if (isNumberStart(code, getCharCode(offset + 1), getCharCode(offset + 2))) { + consumeNumericToken(); + } else { + // Otherwise, if the next 2 input code points are U+002D HYPHEN-MINUS U+003E GREATER-THAN SIGN (->), consume them and return a . + if (getCharCode(offset + 1) === 0x002D && + getCharCode(offset + 2) === 0x003E) { + type = TYPE$F.CDC; + offset = offset + 3; + } else { + // Otherwise, if the input stream starts with an identifier, ... + if (isIdentifierStart$1(code, getCharCode(offset + 1), getCharCode(offset + 2))) { + // ... reconsume the current input code point, consume an ident-like token, and return it. + consumeIdentLikeToken(); + } else { + // Otherwise, return a with its value set to the current input code point. + type = TYPE$F.Delim; + offset++; + } + } + } + break; + + // U+002E FULL STOP (.) + case 0x002E: + // If the input stream starts with a number, ... + if (isNumberStart(code, getCharCode(offset + 1), getCharCode(offset + 2))) { + // ... reconsume the current input code point, consume a numeric token, and return it. + consumeNumericToken(); + } else { + // Otherwise, return a with its value set to the current input code point. + type = TYPE$F.Delim; + offset++; + } + + break; + + // U+002F SOLIDUS (/) + case 0x002F: + // If the next two input code point are U+002F SOLIDUS (/) followed by a U+002A ASTERISK (*), + if (getCharCode(offset + 1) === 0x002A) { + // ... consume them and all following code points up to and including the first U+002A ASTERISK (*) + // followed by a U+002F SOLIDUS (/), or up to an EOF code point. + type = TYPE$F.Comment; + offset = source.indexOf('*/', offset + 2) + 2; + if (offset === 1) { + offset = source.length; + } + } else { + type = TYPE$F.Delim; + offset++; + } + break; + + // U+003A COLON (:) + case 0x003A: + // Return a . + type = TYPE$F.Colon; + offset++; + break; + + // U+003B SEMICOLON (;) + case 0x003B: + // Return a . + type = TYPE$F.Semicolon; + offset++; + break; + + // U+003C LESS-THAN SIGN (<) + case 0x003C: + // If the next 3 input code points are U+0021 EXCLAMATION MARK U+002D HYPHEN-MINUS U+002D HYPHEN-MINUS (!--), ... + if (getCharCode(offset + 1) === 0x0021 && + getCharCode(offset + 2) === 0x002D && + getCharCode(offset + 3) === 0x002D) { + // ... consume them and return a . + type = TYPE$F.CDO; + offset = offset + 4; + } else { + // Otherwise, return a with its value set to the current input code point. + type = TYPE$F.Delim; + offset++; + } + + break; + + // U+0040 COMMERCIAL AT (@) + case 0x0040: + // If the next 3 input code points would start an identifier, ... + if (isIdentifierStart$1(getCharCode(offset + 1), getCharCode(offset + 2), getCharCode(offset + 3))) { + // ... consume a name, create an with its value set to the returned value, and return it. + type = TYPE$F.AtKeyword; + offset = consumeName(source, offset + 1); + } else { + // Otherwise, return a with its value set to the current input code point. + type = TYPE$F.Delim; + offset++; + } + + break; + + // U+005B LEFT SQUARE BRACKET ([) + case 0x005B: + // Return a <[-token>. + type = TYPE$F.LeftSquareBracket; + offset++; + break; + + // U+005C REVERSE SOLIDUS (\) + case 0x005C: + // If the input stream starts with a valid escape, ... + if (isValidEscape(code, getCharCode(offset + 1))) { + // ... reconsume the current input code point, consume an ident-like token, and return it. + consumeIdentLikeToken(); + } else { + // Otherwise, this is a parse error. Return a with its value set to the current input code point. + type = TYPE$F.Delim; + offset++; + } + break; + + // U+005D RIGHT SQUARE BRACKET (]) + case 0x005D: + // Return a <]-token>. + type = TYPE$F.RightSquareBracket; + offset++; + break; + + // U+007B LEFT CURLY BRACKET ({) + case 0x007B: + // Return a <{-token>. + type = TYPE$F.LeftCurlyBracket; + offset++; + break; + + // U+007D RIGHT CURLY BRACKET (}) + case 0x007D: + // Return a <}-token>. + type = TYPE$F.RightCurlyBracket; + offset++; + break; + + // digit + case charCodeCategory.Digit: + // Reconsume the current input code point, consume a numeric token, and return it. + consumeNumericToken(); + break; + + // name-start code point + case charCodeCategory.NameStart: + // Reconsume the current input code point, consume an ident-like token, and return it. + consumeIdentLikeToken(); + break; + + // EOF + case charCodeCategory.Eof: + // Return an . + break; + + // anything else + default: + // Return a with its value set to the current input code point. + type = TYPE$F.Delim; + offset++; + } + + switch (type) { + case balanceCloseType: + balancePrev = balanceStart & OFFSET_MASK; + balanceStart = balance[balancePrev]; + balanceCloseType = balanceStart >> TYPE_SHIFT; + balance[tokenCount] = balancePrev; + balance[balancePrev++] = tokenCount; + for (; balancePrev < tokenCount; balancePrev++) { + if (balance[balancePrev] === sourceLength) { + balance[balancePrev] = tokenCount; + } + } + break; + + case TYPE$F.LeftParenthesis: + case TYPE$F.Function: + balance[tokenCount] = balanceStart; + balanceCloseType = TYPE$F.RightParenthesis; + balanceStart = (balanceCloseType << TYPE_SHIFT) | tokenCount; + break; + + case TYPE$F.LeftSquareBracket: + balance[tokenCount] = balanceStart; + balanceCloseType = TYPE$F.RightSquareBracket; + balanceStart = (balanceCloseType << TYPE_SHIFT) | tokenCount; + break; + + case TYPE$F.LeftCurlyBracket: + balance[tokenCount] = balanceStart; + balanceCloseType = TYPE$F.RightCurlyBracket; + balanceStart = (balanceCloseType << TYPE_SHIFT) | tokenCount; + break; + } + + offsetAndType[tokenCount++] = (type << TYPE_SHIFT) | offset; + } + + // finalize buffers + offsetAndType[tokenCount] = (TYPE$F.EOF << TYPE_SHIFT) | offset; // + balance[tokenCount] = sourceLength; + balance[sourceLength] = sourceLength; // prevents false positive balance match with any token + while (balanceStart !== 0) { + balancePrev = balanceStart & OFFSET_MASK; + balanceStart = balance[balancePrev]; + balance[balancePrev] = sourceLength; + } + + // update stream + stream.source = source; + stream.firstCharOffset = start; + stream.offsetAndType = offsetAndType; + stream.tokenCount = tokenCount; + stream.balance = balance; + stream.reset(); + stream.next(); + + return stream; + } + + // extend tokenizer with constants + Object.keys(constants$1).forEach(function(key) { + tokenize$3[key] = constants$1[key]; + }); + + // extend tokenizer with static methods from utils + Object.keys(charCodeDefinitions).forEach(function(key) { + tokenize$3[key] = charCodeDefinitions[key]; + }); + Object.keys(utils).forEach(function(key) { + tokenize$3[key] = utils[key]; + }); + + var tokenizer$3 = tokenize$3; + + var isDigit$3 = tokenizer$3.isDigit; + var cmpChar$4 = tokenizer$3.cmpChar; + var TYPE$E = tokenizer$3.TYPE; + + var DELIM$6 = TYPE$E.Delim; + var WHITESPACE$b = TYPE$E.WhiteSpace; + var COMMENT$9 = TYPE$E.Comment; + var IDENT$i = TYPE$E.Ident; + var NUMBER$9 = TYPE$E.Number; + var DIMENSION$7 = TYPE$E.Dimension; + var PLUSSIGN$8 = 0x002B; // U+002B PLUS SIGN (+) + var HYPHENMINUS$4 = 0x002D; // U+002D HYPHEN-MINUS (-) + var N$4 = 0x006E; // U+006E LATIN SMALL LETTER N (n) + var DISALLOW_SIGN$1 = true; + var ALLOW_SIGN$1 = false; + + function isDelim$1(token, code) { + return token !== null && token.type === DELIM$6 && token.value.charCodeAt(0) === code; + } + + function skipSC(token, offset, getNextToken) { + while (token !== null && (token.type === WHITESPACE$b || token.type === COMMENT$9)) { + token = getNextToken(++offset); + } + + return offset; + } + + function checkInteger$1(token, valueOffset, disallowSign, offset) { + if (!token) { + return 0; + } + + var code = token.value.charCodeAt(valueOffset); + + if (code === PLUSSIGN$8 || code === HYPHENMINUS$4) { + if (disallowSign) { + // Number sign is not allowed + return 0; + } + valueOffset++; + } + + for (; valueOffset < token.value.length; valueOffset++) { + if (!isDigit$3(token.value.charCodeAt(valueOffset))) { + // Integer is expected + return 0; + } + } + + return offset + 1; + } + + // ... + // ... ['+' | '-'] + function consumeB$1(token, offset_, getNextToken) { + var sign = false; + var offset = skipSC(token, offset_, getNextToken); + + token = getNextToken(offset); + + if (token === null) { + return offset_; + } + + if (token.type !== NUMBER$9) { + if (isDelim$1(token, PLUSSIGN$8) || isDelim$1(token, HYPHENMINUS$4)) { + sign = true; + offset = skipSC(getNextToken(++offset), offset, getNextToken); + token = getNextToken(offset); + + if (token === null && token.type !== NUMBER$9) { + return 0; + } + } else { + return offset_; + } + } + + if (!sign) { + var code = token.value.charCodeAt(0); + if (code !== PLUSSIGN$8 && code !== HYPHENMINUS$4) { + // Number sign is expected + return 0; + } + } + + return checkInteger$1(token, sign ? 0 : 1, sign, offset); + } + + // An+B microsyntax https://www.w3.org/TR/css-syntax-3/#anb + var genericAnPlusB = function anPlusB(token, getNextToken) { + /* eslint-disable brace-style*/ + var offset = 0; + + if (!token) { + return 0; + } + + // + if (token.type === NUMBER$9) { + return checkInteger$1(token, 0, ALLOW_SIGN$1, offset); // b + } + + // -n + // -n + // -n ['+' | '-'] + // -n- + // + else if (token.type === IDENT$i && token.value.charCodeAt(0) === HYPHENMINUS$4) { + // expect 1st char is N + if (!cmpChar$4(token.value, 1, N$4)) { + return 0; + } + + switch (token.value.length) { + // -n + // -n + // -n ['+' | '-'] + case 2: + return consumeB$1(getNextToken(++offset), offset, getNextToken); + + // -n- + case 3: + if (token.value.charCodeAt(2) !== HYPHENMINUS$4) { + return 0; + } + + offset = skipSC(getNextToken(++offset), offset, getNextToken); + token = getNextToken(offset); + + return checkInteger$1(token, 0, DISALLOW_SIGN$1, offset); + + // + default: + if (token.value.charCodeAt(2) !== HYPHENMINUS$4) { + return 0; + } + + return checkInteger$1(token, 3, DISALLOW_SIGN$1, offset); + } + } + + // '+'? n + // '+'? n + // '+'? n ['+' | '-'] + // '+'? n- + // '+'? + else if (token.type === IDENT$i || (isDelim$1(token, PLUSSIGN$8) && getNextToken(offset + 1).type === IDENT$i)) { + // just ignore a plus + if (token.type !== IDENT$i) { + token = getNextToken(++offset); + } + + if (token === null || !cmpChar$4(token.value, 0, N$4)) { + return 0; + } + + switch (token.value.length) { + // '+'? n + // '+'? n + // '+'? n ['+' | '-'] + case 1: + return consumeB$1(getNextToken(++offset), offset, getNextToken); + + // '+'? n- + case 2: + if (token.value.charCodeAt(1) !== HYPHENMINUS$4) { + return 0; + } + + offset = skipSC(getNextToken(++offset), offset, getNextToken); + token = getNextToken(offset); + + return checkInteger$1(token, 0, DISALLOW_SIGN$1, offset); + + // '+'? + default: + if (token.value.charCodeAt(1) !== HYPHENMINUS$4) { + return 0; + } + + return checkInteger$1(token, 2, DISALLOW_SIGN$1, offset); + } + } + + // + // + // + // + // ['+' | '-'] + else if (token.type === DIMENSION$7) { + var code = token.value.charCodeAt(0); + var sign = code === PLUSSIGN$8 || code === HYPHENMINUS$4 ? 1 : 0; + + for (var i = sign; i < token.value.length; i++) { + if (!isDigit$3(token.value.charCodeAt(i))) { + break; + } + } + + if (i === sign) { + // Integer is expected + return 0; + } + + if (!cmpChar$4(token.value, i, N$4)) { + return 0; + } + + // + // + // ['+' | '-'] + if (i + 1 === token.value.length) { + return consumeB$1(getNextToken(++offset), offset, getNextToken); + } else { + if (token.value.charCodeAt(i + 1) !== HYPHENMINUS$4) { + return 0; + } + + // + if (i + 2 === token.value.length) { + offset = skipSC(getNextToken(++offset), offset, getNextToken); + token = getNextToken(offset); + + return checkInteger$1(token, 0, DISALLOW_SIGN$1, offset); + } + // + else { + return checkInteger$1(token, i + 2, DISALLOW_SIGN$1, offset); + } + } + } + + return 0; + }; + + var isHexDigit$2 = tokenizer$3.isHexDigit; + var cmpChar$3 = tokenizer$3.cmpChar; + var TYPE$D = tokenizer$3.TYPE; + + var IDENT$h = TYPE$D.Ident; + var DELIM$5 = TYPE$D.Delim; + var NUMBER$8 = TYPE$D.Number; + var DIMENSION$6 = TYPE$D.Dimension; + var PLUSSIGN$7 = 0x002B; // U+002B PLUS SIGN (+) + var HYPHENMINUS$3 = 0x002D; // U+002D HYPHEN-MINUS (-) + var QUESTIONMARK$2 = 0x003F; // U+003F QUESTION MARK (?) + var U$2 = 0x0075; // U+0075 LATIN SMALL LETTER U (u) + + function isDelim(token, code) { + return token !== null && token.type === DELIM$5 && token.value.charCodeAt(0) === code; + } + + function startsWith$1(token, code) { + return token.value.charCodeAt(0) === code; + } + + function hexSequence(token, offset, allowDash) { + for (var pos = offset, hexlen = 0; pos < token.value.length; pos++) { + var code = token.value.charCodeAt(pos); + + if (code === HYPHENMINUS$3 && allowDash && hexlen !== 0) { + if (hexSequence(token, offset + hexlen + 1, false) > 0) { + return 6; // dissallow following question marks + } + + return 0; // dash at the ending of a hex sequence is not allowed + } + + if (!isHexDigit$2(code)) { + return 0; // not a hex digit + } + + if (++hexlen > 6) { + return 0; // too many hex digits + } } + + return hexlen; + } + + function withQuestionMarkSequence(consumed, length, getNextToken) { + if (!consumed) { + return 0; // nothing consumed + } + + while (isDelim(getNextToken(length), QUESTIONMARK$2)) { + if (++consumed > 6) { + return 0; // too many question marks + } + + length++; + } + + return length; + } + + // https://drafts.csswg.org/css-syntax/#urange + // Informally, the production has three forms: + // U+0001 + // Defines a range consisting of a single code point, in this case the code point "1". + // U+0001-00ff + // Defines a range of codepoints between the first and the second value, in this case + // the range between "1" and "ff" (255 in decimal) inclusive. + // U+00?? + // Defines a range of codepoints where the "?" characters range over all hex digits, + // in this case defining the same as the value U+0000-00ff. + // In each form, a maximum of 6 digits is allowed for each hexadecimal number (if you treat "?" as a hexadecimal digit). + // + // = + // u '+' '?'* | + // u '?'* | + // u '?'* | + // u | + // u | + // u '+' '?'+ + var genericUrange = function urange(token, getNextToken) { + var length = 0; + + // should start with `u` or `U` + if (token === null || token.type !== IDENT$h || !cmpChar$3(token.value, 0, U$2)) { + return 0; + } + + token = getNextToken(++length); + if (token === null) { + return 0; + } + + // u '+' '?'* + // u '+' '?'+ + if (isDelim(token, PLUSSIGN$7)) { + token = getNextToken(++length); + if (token === null) { + return 0; + } + + if (token.type === IDENT$h) { + // u '+' '?'* + return withQuestionMarkSequence(hexSequence(token, 0, true), ++length, getNextToken); + } + + if (isDelim(token, QUESTIONMARK$2)) { + // u '+' '?'+ + return withQuestionMarkSequence(1, ++length, getNextToken); + } + + // Hex digit or question mark is expected + return 0; + } + + // u '?'* + // u + // u + if (token.type === NUMBER$8) { + if (!startsWith$1(token, PLUSSIGN$7)) { + return 0; + } + + var consumedHexLength = hexSequence(token, 1, true); + if (consumedHexLength === 0) { + return 0; + } + + token = getNextToken(++length); + if (token === null) { + // u + return length; + } + + if (token.type === DIMENSION$6 || token.type === NUMBER$8) { + // u + // u + if (!startsWith$1(token, HYPHENMINUS$3) || !hexSequence(token, 1, false)) { + return 0; + } + + return length + 1; + } + + // u '?'* + return withQuestionMarkSequence(consumedHexLength, length, getNextToken); + } + + // u '?'* + if (token.type === DIMENSION$6) { + if (!startsWith$1(token, PLUSSIGN$7)) { + return 0; + } + + return withQuestionMarkSequence(hexSequence(token, 1, true), ++length, getNextToken); + } + + return 0; + }; + + var tokenizer$2 = tokenizer$3; + var isIdentifierStart = tokenizer$2.isIdentifierStart; + var isHexDigit$1 = tokenizer$2.isHexDigit; + var isDigit$2 = tokenizer$2.isDigit; + var cmpStr$3 = tokenizer$2.cmpStr; + var consumeNumber$3 = tokenizer$2.consumeNumber; + var TYPE$C = tokenizer$2.TYPE; + var anPlusB = genericAnPlusB; + var urange = genericUrange; + + var cssWideKeywords$1 = ['unset', 'initial', 'inherit']; + var calcFunctionNames = ['calc(', '-moz-calc(', '-webkit-calc(']; + + // https://www.w3.org/TR/css-values-3/#lengths + var LENGTH = { + // absolute length units + 'px': true, + 'mm': true, + 'cm': true, + 'in': true, + 'pt': true, + 'pc': true, + 'q': true, + + // relative length units + 'em': true, + 'ex': true, + 'ch': true, + 'rem': true, + + // viewport-percentage lengths + 'vh': true, + 'vw': true, + 'vmin': true, + 'vmax': true, + 'vm': true + }; + + var ANGLE = { + 'deg': true, + 'grad': true, + 'rad': true, + 'turn': true + }; + + var TIME = { + 's': true, + 'ms': true + }; + + var FREQUENCY = { + 'hz': true, + 'khz': true + }; + + // https://www.w3.org/TR/css-values-3/#resolution (https://drafts.csswg.org/css-values/#resolution) + var RESOLUTION = { + 'dpi': true, + 'dpcm': true, + 'dppx': true, + 'x': true // https://github.com/w3c/csswg-drafts/issues/461 + }; + + // https://drafts.csswg.org/css-grid/#fr-unit + var FLEX = { + 'fr': true + }; + + // https://www.w3.org/TR/css3-speech/#mixing-props-voice-volume + var DECIBEL = { + 'db': true + }; + + // https://www.w3.org/TR/css3-speech/#voice-props-voice-pitch + var SEMITONES = { + 'st': true + }; + + // safe char code getter + function charCode(str, index) { + return index < str.length ? str.charCodeAt(index) : 0; + } + + function eqStr(actual, expected) { + return cmpStr$3(actual, 0, actual.length, expected); + } + + function eqStrAny(actual, expected) { + for (var i = 0; i < expected.length; i++) { + if (eqStr(actual, expected[i])) { + return true; + } + } + + return false; + } + + // IE postfix hack, i.e. 123\0 or 123px\9 + function isPostfixIeHack(str, offset) { + if (offset !== str.length - 2) { + return false; + } + + return ( + str.charCodeAt(offset) === 0x005C && // U+005C REVERSE SOLIDUS (\) + isDigit$2(str.charCodeAt(offset + 1)) + ); + } + + function outOfRange(opts, value, numEnd) { + if (opts && opts.type === 'Range') { + var num = Number( + numEnd !== undefined && numEnd !== value.length + ? value.substr(0, numEnd) + : value + ); + + if (isNaN(num)) { + return true; + } + + if (opts.min !== null && num < opts.min) { + return true; + } + + if (opts.max !== null && num > opts.max) { + return true; + } + } + + return false; + } + + function consumeFunction(token, getNextToken) { + var startIdx = token.index; + var length = 0; + + // balanced token consuming + do { + length++; + + if (token.balance <= startIdx) { + break; + } + } while (token = getNextToken(length)); + + return length; + } + + // TODO: implement + // can be used wherever , , ,